From 69884b388e650675448e0a1f26ac752e389ca1ce Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Sat, 11 Feb 2023 12:05:18 +0100 Subject: [PATCH 1/2] flake8-use-pathlib autofix and new rules PTH200-204: - Simplify path constructor `Path(".")` to `Path()` - Replace `os.path.getsize` with `Path.stat()` Autofix for: - PTH200: `Path(".")` to `Path()` - PTH109: `os.path.getcwd(x)` to `Path(x).cwd()` Promote `get_member_import_name_alias` from `pylint` to `ast.helpers` --- README.md | 9 +- .../fixtures/flake8_use_pathlib/full_name.py | 1 + .../fixtures/flake8_use_pathlib/guarded.py | 34 +++ .../fixtures/flake8_use_pathlib/import_as.py | 1 + .../flake8_use_pathlib/import_from.py | 1 + .../flake8_use_pathlib/import_from_as.py | 1 + .../simplify_pathlib_constructor.py | 12 + .../test/fixtures/flake8_use_pathlib/stat.py | 19 ++ .../flake8_use_pathlib/use_pathlib.py | 15 + crates/ruff/src/ast/helpers.rs | 60 ++++ crates/ruff/src/checkers/ast.rs | 14 +- crates/ruff/src/registry.rs | 5 + .../src/rules/flake8_use_pathlib/fixes.rs | 67 +++++ .../src/rules/flake8_use_pathlib/helpers.rs | 24 +- .../ruff/src/rules/flake8_use_pathlib/mod.rs | 10 + .../src/rules/flake8_use_pathlib/rules/mod.rs | 3 + .../rules/simplify_path_constructor.rs | 74 +++++ ...ake8_use_pathlib__tests__full_name.py.snap | 124 ++++---- ...flake8_use_pathlib__tests__guarded.py.snap | 265 ++++++++++++++++++ ...ake8_use_pathlib__tests__import_as.py.snap | 103 +++---- ...e8_use_pathlib__tests__import_from.py.snap | 111 ++++---- ...use_pathlib__tests__import_from_as.py.snap | 103 +++---- ...ests__simplify_pathlib_constructor.py.snap | 39 +++ ...s__flake8_use_pathlib__tests__stat.py.snap | 135 +++++++++ ...e8_use_pathlib__tests__use_pathlib.py.snap | 71 ++++- .../rules/flake8_use_pathlib/violations.rs | 78 +++++- .../pylint/rules/consider_using_sys_exit.rs | 59 +--- .../path-constructor-current-directory.md | 26 ++ docs/rules/pathlib-getcwd.md | 28 ++ ruff.schema.json | 7 + 30 files changed, 1225 insertions(+), 274 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/flake8_use_pathlib/guarded.py create mode 100644 crates/ruff/resources/test/fixtures/flake8_use_pathlib/simplify_pathlib_constructor.py create mode 100644 crates/ruff/resources/test/fixtures/flake8_use_pathlib/stat.py create mode 100644 crates/ruff/src/rules/flake8_use_pathlib/fixes.rs create mode 100644 crates/ruff/src/rules/flake8_use_pathlib/rules/mod.rs create mode 100644 crates/ruff/src/rules/flake8_use_pathlib/rules/simplify_path_constructor.rs create mode 100644 crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__guarded.py.snap create mode 100644 crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__simplify_pathlib_constructor.py.snap create mode 100644 crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__stat.py.snap create mode 100644 docs/rules/path-constructor-current-directory.md create mode 100644 docs/rules/pathlib-getcwd.md diff --git a/README.md b/README.md index e65529b44c9cc8..5eaaf116a40667 100644 --- a/README.md +++ b/README.md @@ -1290,7 +1290,7 @@ For more, see [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) | PTH106 | pathlib-rmdir | `os.rmdir` should be replaced by `.rmdir()` | | | PTH107 | pathlib-remove | `os.remove` should be replaced by `.unlink()` | | | PTH108 | pathlib-unlink | `os.unlink` should be replaced by `.unlink()` | | -| PTH109 | pathlib-getcwd | `os.getcwd` should be replaced by `Path.cwd()` | | +| PTH109 | [pathlib-getcwd](https://github.com/charliermarsh/ruff/blob/main/docs/rules/pathlib-getcwd.md) | `os.getcwd` should be replaced by `Path.cwd()` | 🛠 | | PTH110 | pathlib-exists | `os.path.exists` should be replaced by `.exists()` | | | PTH111 | pathlib-expanduser | `os.path.expanduser` should be replaced by `.expanduser()` | | | PTH112 | pathlib-is-dir | `os.path.isdir` should be replaced by `.is_dir()` | | @@ -1299,13 +1299,18 @@ For more, see [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) | PTH115 | pathlib-readlink | `os.readlink` should be replaced by `.readlink()` | | | PTH116 | pathlib-stat | `os.stat` should be replaced by `.stat()` or `.owner()` or `.group()` | | | PTH117 | pathlib-is-abs | `os.path.isabs` should be replaced by `.is_absolute()` | | -| PTH118 | pathlib-join | `os.path.join` should be replaced by foo_path / "bar" | | +| PTH118 | pathlib-join | `os.path.join` should be replaced by `foo_path / "bar"` | | | PTH119 | pathlib-basename | `os.path.basename` should be replaced by `.name` | | | PTH120 | pathlib-dirname | `os.path.dirname` should be replaced by `.parent` | | | PTH121 | pathlib-samefile | `os.path.samefile` should be replaced by `.samefile()` | | | PTH122 | pathlib-splitext | `os.path.splitext` should be replaced by `.suffix` | | | PTH123 | pathlib-open | `open("foo")` should be replaced by `Path("foo").open()` | | | PTH124 | pathlib-py-path | `py.path` is in maintenance mode, use `pathlib` instead | | +| PTH200 | [path-constructor-current-directory](https://github.com/charliermarsh/ruff/blob/main/docs/rules/path-constructor-current-directory.md) | Do not pass the current directory explicitly to `Path` | 🛠 | +| PTH201 | pathlib-getsize | `os.path.getsize` should be replaced by `stat().st_size` | | +| PTH202 | pathlib-getatime | `os.path.getatime` should be replaced by `stat().st_atime` | | +| PTH203 | pathlib-getmtime | `os.path.getmtime` should be replaced by `stat().st_mtime` | | +| PTH204 | pathlib-getctime | `os.path.getctime` should be replaced by `stat().st_ctime` | | ### eradicate (ERA) diff --git a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/full_name.py b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/full_name.py index 12371193cb4122..65064c8d57fc4b 100644 --- a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/full_name.py +++ b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/full_name.py @@ -1,5 +1,6 @@ import os import os.path +import pathlib p = "/foo" diff --git a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/guarded.py b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/guarded.py new file mode 100644 index 00000000000000..4bad4242666f88 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/guarded.py @@ -0,0 +1,34 @@ +# ensure that no fixes are applied when`pathlib` is not imported +# (needed until this item is resolved: https://github.com/charliermarsh/ruff/issues/835) +import os +import os.path + +p = "/foo" + +a = os.path.abspath(p) +aa = os.chmod(p) +aaa = os.mkdir(p) +os.makedirs(p) +os.rename(p) +os.replace(p) +os.rmdir(p) +os.remove(p) +os.unlink(p) +os.getcwd(p) +b = os.path.exists(p) +bb = os.path.expanduser(p) +bbb = os.path.isdir(p) +bbbb = os.path.isfile(p) +bbbbb = os.path.islink(p) +os.readlink(p) +os.stat(p) +os.path.isabs(p) +os.path.join(p) +os.path.basename(p) +os.path.dirname(p) +os.path.samefile(p) +os.path.splitext(p) +with open(p) as fp: + fp.read() +open(p).close() +os.getcwdb(p) diff --git a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_as.py b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_as.py index 446412f18de676..5c04829ba59b38 100644 --- a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_as.py +++ b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_as.py @@ -1,5 +1,6 @@ import os as foo import os.path as foo_p +import pathlib as pth p = "/foo" diff --git a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_from.py b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_from.py index 9e4dc71a2d6470..59c90ebb5413b3 100644 --- a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_from.py +++ b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_from.py @@ -2,6 +2,7 @@ from os import remove, unlink, getcwd, readlink, stat from os.path import abspath, exists, expanduser, isdir, isfile, islink from os.path import isabs, join, basename, dirname, samefile, splitext +from pathlib import Path p = "/foo" diff --git a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_from_as.py b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_from_as.py index 2beff6d7e7bb55..43617aae1329ec 100644 --- a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_from_as.py +++ b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/import_from_as.py @@ -7,6 +7,7 @@ from os.path import isfile as xisfile, islink as xislink, isabs as xisabs from os.path import join as xjoin, basename as xbasename, dirname as xdirname from os.path import samefile as xsamefile, splitext as xsplitext +from pathlib import Path as pth p = "/foo" diff --git a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/simplify_pathlib_constructor.py b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/simplify_pathlib_constructor.py new file mode 100644 index 00000000000000..27c0cdd48c3ca9 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/simplify_pathlib_constructor.py @@ -0,0 +1,12 @@ +from pathlib import Path +from pathlib import Path as pth + +# match +_ = Path(".") +_ = pth(".") + +# no match +_ = Path() +print(".") +Path("file.txt") +Path(".", "folder") diff --git a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/stat.py b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/stat.py new file mode 100644 index 00000000000000..8a69bf87064b33 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/stat.py @@ -0,0 +1,19 @@ +import os +from pathlib import Path + +os.path.getatime("filename") +os.path.getatime(b"filename") +os.path.getatime(Path("filename")) + +os.path.getmtime("filename") +os.path.getmtime(b"filename") +os.path.getmtime(Path("filename")) + +os.path.getctime("filename") +os.path.getctime(b"filename") +os.path.getctime(Path("filename")) + +os.path.getsize("filename") +os.path.getsize(b"filename") +os.path.getsize(Path("filename")) +os.path.getsize(__file__) diff --git a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/use_pathlib.py b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/use_pathlib.py index 748442f50a8067..8298db4b3aaaf2 100644 --- a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/use_pathlib.py +++ b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/use_pathlib.py @@ -1,3 +1,18 @@ +import os from pathlib import Path (Path("") / "").open() + +_ = Path(os.getcwd()) + +_ = Path( + os.\ + getcwd() +) + +_ = Path( + os.getcwdb(), +) + +# should not be unwrapped +_ = Path(os.getcwd(), hello='world') diff --git a/crates/ruff/src/ast/helpers.rs b/crates/ruff/src/ast/helpers.rs index 015327e1f987a6..89db23aa9b3770 100644 --- a/crates/ruff/src/ast/helpers.rs +++ b/crates/ruff/src/ast/helpers.rs @@ -1146,6 +1146,66 @@ pub fn is_logger_candidate(func: &Expr) -> bool { false } +/// Return the appropriate `sys.exit` reference based on the current set of +/// imports, or `None` is `sys.exit` hasn't been imported. +pub fn get_member_import_name_alias( + checker: &Checker, + module: &str, + member: &str, +) -> Option { + checker.current_scopes().find_map(|scope| { + scope + .bindings + .values() + .find_map(|index| match &checker.bindings[*index].kind { + // e.g. module=sys object=exit + // `import sys` -> `sys.exit` + // `import sys as sys2` -> `sys2.exit` + BindingKind::Importation(name, full_name) => { + if full_name == &module { + Some(format!("{name}.{member}")) + } else { + None + } + } + // e.g. module=os.path object=join + // `from os.path import join` -> `join` + // `from os.path import join as join2` -> `join2` + BindingKind::FromImportation(name, full_name) => { + let mut parts = full_name.split('.'); + if parts.next() == Some(module) + && parts.next() == Some(member) + && parts.next().is_none() + { + Some((*name).to_string()) + } else { + None + } + } + // e.g. module=os.path object=join + // `from os.path import *` -> `join` + BindingKind::StarImportation(_, name) => { + if name.as_ref().map(|name| name == module).unwrap_or_default() { + Some(member.to_string()) + } else { + None + } + } + // e.g. module=os.path object=join + // `import os.path ` -> `os.path.join` + BindingKind::SubmoduleImportation(_, full_name) => { + if full_name == &module { + Some(format!("{full_name}.{member}")) + } else { + None + } + } + // Non-imports. + _ => None, + }) + }) +} + #[cfg(test)] mod tests { use anyhow::Result; diff --git a/crates/ruff/src/checkers/ast.rs b/crates/ruff/src/checkers/ast.rs index 82f398e4920676..fb87b286690156 100644 --- a/crates/ruff/src/checkers/ast.rs +++ b/crates/ruff/src/checkers/ast.rs @@ -2756,7 +2756,19 @@ where || self.settings.rules.enabled(&Rule::PathlibOpen) || self.settings.rules.enabled(&Rule::PathlibPyPath) { - flake8_use_pathlib::helpers::replaceable_by_pathlib(self, func); + flake8_use_pathlib::helpers::replaceable_by_pathlib( + self, + func, + self.current_expr_parent().map(Into::into), + ); + } + + if self + .settings + .rules + .enabled(&Rule::PathConstructorCurrentDirectory) + { + flake8_use_pathlib::rules::simplify_path_constructor(self, expr, func); } // flake8-logging-format diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 0dc874533a92c7..781d51ef9888bf 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -534,6 +534,11 @@ ruff_macros::define_rule_mapping!( PTH122 => rules::flake8_use_pathlib::violations::PathlibSplitext, PTH123 => rules::flake8_use_pathlib::violations::PathlibOpen, PTH124 => rules::flake8_use_pathlib::violations::PathlibPyPath, + PTH200 => rules::flake8_use_pathlib::rules::PathConstructorCurrentDirectory, + PTH201 => rules::flake8_use_pathlib::violations::PathlibGetsize, + PTH202 => rules::flake8_use_pathlib::violations::PathlibGetatime, + PTH203 => rules::flake8_use_pathlib::violations::PathlibGetmtime, + PTH204 => rules::flake8_use_pathlib::violations::PathlibGetctime, // flake8-logging-format G001 => rules::flake8_logging_format::violations::LoggingStringFormat, G002 => rules::flake8_logging_format::violations::LoggingPercentFormat, diff --git a/crates/ruff/src/rules/flake8_use_pathlib/fixes.rs b/crates/ruff/src/rules/flake8_use_pathlib/fixes.rs new file mode 100644 index 00000000000000..74e67a003715c0 --- /dev/null +++ b/crates/ruff/src/rules/flake8_use_pathlib/fixes.rs @@ -0,0 +1,67 @@ +use rustpython_parser::ast::{Expr, ExprKind}; + +use crate::ast::helpers; +use crate::ast::types::Range; +use crate::autofix::apply_fix; +use crate::checkers::ast::Checker; +use crate::fix::Fix; +use crate::registry::DiagnosticKind; +use crate::source_code::Locator; + +pub fn pathlib_fix( + checker: &mut Checker, + diagnostic: &DiagnosticKind, + func: &Expr, + parent: Option<&Expr>, +) -> Option { + // Guard that Path is imported, `content` contains the name or aliaas + if let Some(content) = helpers::get_member_import_name_alias(checker, "pathlib", "Path") { + let mut fix = match diagnostic { + DiagnosticKind::PathlibGetcwd(_) => Some(Fix::replacement( + format!("{content}.cwd"), + func.location, + func.end_location.unwrap(), + )), + _ => None, + }; + + // Wrapped in a `Path()` call + if let Some(fixme) = fix.clone() { + if let Some(parent) = parent { + if checker + .resolve_call_path(parent) + .map_or(false, |call_path| { + call_path.as_slice() == ["pathlib", "Path"] + }) + { + if let ExprKind::Call { args, keywords, .. } = &parent.node { + if args.len() == 1 && keywords.is_empty() { + // Reset the line index + let fixme = Fix::replacement( + fixme.content.to_string(), + helpers::to_relative(fixme.location, func.location), + helpers::to_relative(fixme.end_location, func.location), + ); + + // Apply the fix + let arg = args.first().unwrap(); + let contents = checker.locator.slice_source_code_range(&Range::new( + arg.location, + arg.end_location.unwrap(), + )); + + fix = Some(Fix::replacement( + apply_fix(&fixme, &Locator::new(contents)), + parent.location, + parent.end_location.unwrap(), + )); + } + } + } + } + } + fix + } else { + None + } +} diff --git a/crates/ruff/src/rules/flake8_use_pathlib/helpers.rs b/crates/ruff/src/rules/flake8_use_pathlib/helpers.rs index 68277e13c79236..eb6e9c22c5da33 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/helpers.rs +++ b/crates/ruff/src/rules/flake8_use_pathlib/helpers.rs @@ -1,18 +1,20 @@ use rustpython_parser::ast::Expr; +use super::fixes::pathlib_fix; use crate::ast::types::Range; use crate::checkers::ast::Checker; use crate::registry::{Diagnostic, DiagnosticKind}; use crate::rules::flake8_use_pathlib::violations::{ PathlibAbspath, PathlibBasename, PathlibChmod, PathlibDirname, PathlibExists, - PathlibExpanduser, PathlibGetcwd, PathlibIsAbs, PathlibIsDir, PathlibIsFile, PathlibIsLink, - PathlibJoin, PathlibMakedirs, PathlibMkdir, PathlibOpen, PathlibPyPath, PathlibReadlink, - PathlibRemove, PathlibRename, PathlibReplace, PathlibRmdir, PathlibSamefile, PathlibSplitext, - PathlibStat, PathlibUnlink, + PathlibExpanduser, PathlibGetatime, PathlibGetctime, PathlibGetcwd, PathlibGetmtime, + PathlibGetsize, PathlibIsAbs, PathlibIsDir, PathlibIsFile, PathlibIsLink, PathlibJoin, + PathlibMakedirs, PathlibMkdir, PathlibOpen, PathlibPyPath, PathlibReadlink, PathlibRemove, + PathlibRename, PathlibReplace, PathlibRmdir, PathlibSamefile, PathlibSplitext, PathlibStat, + PathlibUnlink, }; use crate::settings::types::PythonVersion; -pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { +pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr, parent: Option<&Expr>) { if let Some(diagnostic_kind) = checker .resolve_call_path(expr) @@ -46,12 +48,20 @@ pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { ["os", "readlink"] if checker.settings.target_version >= PythonVersion::Py39 => { Some(PathlibReadlink.into()) } + ["os", "path", "getsize"] => Some(PathlibGetsize.into()), + ["os", "path", "getatime"] => Some(PathlibGetatime.into()), + ["os", "path", "getmtime"] => Some(PathlibGetmtime.into()), + ["os", "path", "getctime"] => Some(PathlibGetctime.into()), _ => None, }) { - let diagnostic = + let mut diagnostic = Diagnostic::new::(diagnostic_kind, Range::from_located(expr)); - + if checker.patch(diagnostic.kind.rule()) { + if let Some(fix) = pathlib_fix(checker, &diagnostic.kind, expr, parent) { + diagnostic.amend(fix); + } + } if checker.settings.rules.enabled(diagnostic.kind.rule()) { checker.diagnostics.push(diagnostic); } diff --git a/crates/ruff/src/rules/flake8_use_pathlib/mod.rs b/crates/ruff/src/rules/flake8_use_pathlib/mod.rs index 90f7bc395afc72..91bf482c4c0716 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/mod.rs +++ b/crates/ruff/src/rules/flake8_use_pathlib/mod.rs @@ -1,5 +1,7 @@ //! Rules from [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/). +pub(crate) mod fixes; pub(crate) mod helpers; +pub(crate) mod rules; pub(crate) mod violations; #[cfg(test)] @@ -18,6 +20,9 @@ mod tests { #[test_case(Path::new("import_from_as.py"); "PTH1_3")] #[test_case(Path::new("import_from.py"); "PTH1_4")] #[test_case(Path::new("use_pathlib.py"); "PTH1_5")] + #[test_case(Path::new("stat.py"); "PTH1_6")] + #[test_case(Path::new("simplify_pathlib_constructor.py"); "PTH1_7")] + #[test_case(Path::new("guarded.py"); "PTH1_8")] fn rules(path: &Path) -> Result<()> { let snapshot = format!("{}", path.to_string_lossy()); let diagnostics = test_path( @@ -47,6 +52,11 @@ mod tests { Rule::PathlibSamefile, Rule::PathlibSplitext, Rule::PathlibOpen, + Rule::PathlibGetsize, + Rule::PathlibGetatime, + Rule::PathlibGetmtime, + Rule::PathlibGetctime, + Rule::PathConstructorCurrentDirectory, ]), )?; insta::assert_yaml_snapshot!(snapshot, diagnostics); diff --git a/crates/ruff/src/rules/flake8_use_pathlib/rules/mod.rs b/crates/ruff/src/rules/flake8_use_pathlib/rules/mod.rs new file mode 100644 index 00000000000000..3c49b0c8aa22a5 --- /dev/null +++ b/crates/ruff/src/rules/flake8_use_pathlib/rules/mod.rs @@ -0,0 +1,3 @@ +pub use simplify_path_constructor::{simplify_path_constructor, PathConstructorCurrentDirectory}; + +mod simplify_path_constructor; diff --git a/crates/ruff/src/rules/flake8_use_pathlib/rules/simplify_path_constructor.rs b/crates/ruff/src/rules/flake8_use_pathlib/rules/simplify_path_constructor.rs new file mode 100644 index 00000000000000..a32cbed4ba41db --- /dev/null +++ b/crates/ruff/src/rules/flake8_use_pathlib/rules/simplify_path_constructor.rs @@ -0,0 +1,74 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::{Constant, Expr, ExprKind}; + +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::fix::Fix; +use crate::registry::Diagnostic; +use crate::violation::{AutofixKind, Availability, Violation}; + +define_violation!( + /// ## What it does + /// This rule detects pathlib's `Path` initializations with the default current directory argument. + /// + /// ## Why is this bad? + /// The `Path()` constructor defaults to the current directory, so don't pass the + /// current directory (`"."`) explicitly. + /// + /// ## Example + /// ```python + /// from pathlib import Path + /// + /// _ = Path(".") + /// ``` + /// + /// Use instead: + /// ```python + /// from pathlib import Path + /// + /// _ = Path() + /// ``` + pub struct PathConstructorCurrentDirectory; +); +impl Violation for PathConstructorCurrentDirectory { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + + #[derive_message_formats] + fn message(&self) -> String { + format!("Do not pass the current directory explicitly to `Path`") + } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathConstructorCurrentDirectory| format!("Replace `Path(\".\")` with `Path()`")) + } +} + +/// PTH200 +pub fn simplify_path_constructor(checker: &mut Checker, expr: &Expr, func: &Expr) { + if checker.resolve_call_path(func).map_or(false, |call_path| { + call_path.as_slice() == ["pathlib", "Path"] + }) { + if let ExprKind::Call { args, keywords, .. } = &expr.node { + if keywords.is_empty() && args.len() == 1 { + let arg = &args.first().unwrap(); + if let ExprKind::Constant { + value: Constant::Str(value), + .. + } = &arg.node + { + if value == "." { + let mut diagnostic = Diagnostic::new( + PathConstructorCurrentDirectory, + Range::from_located(expr), + ); + if checker.patch(diagnostic.kind.rule()) { + diagnostic + .amend(Fix::deletion(arg.location, arg.end_location.unwrap())); + } + checker.diagnostics.push(diagnostic); + }; + }; + } + } + } +} diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap index 4ab33e250a0392..2da1d596753c35 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap @@ -1,265 +1,279 @@ --- -source: src/rules/flake8_use_pathlib/mod.rs +source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs expression: diagnostics --- - kind: PathlibAbspath: ~ location: - row: 6 + row: 7 column: 4 end_location: - row: 6 + row: 7 column: 19 fix: ~ parent: ~ - kind: PathlibChmod: ~ location: - row: 7 + row: 8 column: 5 end_location: - row: 7 + row: 8 column: 13 fix: ~ parent: ~ - kind: PathlibMkdir: ~ location: - row: 8 + row: 9 column: 6 end_location: - row: 8 + row: 9 column: 14 fix: ~ parent: ~ - kind: PathlibMakedirs: ~ location: - row: 9 + row: 10 column: 0 end_location: - row: 9 + row: 10 column: 11 fix: ~ parent: ~ - kind: PathlibRename: ~ location: - row: 10 + row: 11 column: 0 end_location: - row: 10 + row: 11 column: 9 fix: ~ parent: ~ - kind: PathlibReplace: ~ location: - row: 11 + row: 12 column: 0 end_location: - row: 11 + row: 12 column: 10 fix: ~ parent: ~ - kind: PathlibRmdir: ~ location: - row: 12 + row: 13 column: 0 end_location: - row: 12 + row: 13 column: 8 fix: ~ parent: ~ - kind: PathlibRemove: ~ location: - row: 13 + row: 14 column: 0 end_location: - row: 13 + row: 14 column: 9 fix: ~ parent: ~ - kind: PathlibUnlink: ~ location: - row: 14 + row: 15 column: 0 end_location: - row: 14 + row: 15 column: 9 fix: ~ parent: ~ - kind: PathlibGetcwd: ~ location: - row: 15 + row: 16 column: 0 end_location: - row: 15 + row: 16 column: 9 - fix: ~ + fix: + content: pathlib.Path.cwd + location: + row: 16 + column: 0 + end_location: + row: 16 + column: 9 parent: ~ - kind: PathlibExists: ~ location: - row: 16 + row: 17 column: 4 end_location: - row: 16 + row: 17 column: 18 fix: ~ parent: ~ - kind: PathlibExpanduser: ~ location: - row: 17 + row: 18 column: 5 end_location: - row: 17 + row: 18 column: 23 fix: ~ parent: ~ - kind: PathlibIsDir: ~ location: - row: 18 + row: 19 column: 6 end_location: - row: 18 + row: 19 column: 19 fix: ~ parent: ~ - kind: PathlibIsFile: ~ location: - row: 19 + row: 20 column: 7 end_location: - row: 19 + row: 20 column: 21 fix: ~ parent: ~ - kind: PathlibIsLink: ~ location: - row: 20 + row: 21 column: 8 end_location: - row: 20 + row: 21 column: 22 fix: ~ parent: ~ - kind: PathlibReadlink: ~ location: - row: 21 + row: 22 column: 0 end_location: - row: 21 + row: 22 column: 11 fix: ~ parent: ~ - kind: PathlibStat: ~ location: - row: 22 + row: 23 column: 0 end_location: - row: 22 + row: 23 column: 7 fix: ~ parent: ~ - kind: PathlibIsAbs: ~ location: - row: 23 + row: 24 column: 0 end_location: - row: 23 + row: 24 column: 13 fix: ~ parent: ~ - kind: PathlibJoin: ~ location: - row: 24 + row: 25 column: 0 end_location: - row: 24 + row: 25 column: 12 fix: ~ parent: ~ - kind: PathlibBasename: ~ location: - row: 25 + row: 26 column: 0 end_location: - row: 25 + row: 26 column: 16 fix: ~ parent: ~ - kind: PathlibDirname: ~ location: - row: 26 + row: 27 column: 0 end_location: - row: 26 + row: 27 column: 15 fix: ~ parent: ~ - kind: PathlibSamefile: ~ location: - row: 27 + row: 28 column: 0 end_location: - row: 27 + row: 28 column: 16 fix: ~ parent: ~ - kind: PathlibSplitext: ~ location: - row: 28 + row: 29 column: 0 end_location: - row: 28 + row: 29 column: 16 fix: ~ parent: ~ - kind: PathlibOpen: ~ location: - row: 29 + row: 30 column: 5 end_location: - row: 29 + row: 30 column: 9 fix: ~ parent: ~ - kind: PathlibOpen: ~ location: - row: 31 + row: 32 column: 0 end_location: - row: 31 + row: 32 column: 4 fix: ~ parent: ~ - kind: PathlibGetcwd: ~ location: - row: 32 + row: 33 column: 0 end_location: - row: 32 + row: 33 column: 10 - fix: ~ + fix: + content: pathlib.Path.cwd + location: + row: 33 + column: 0 + end_location: + row: 33 + column: 10 parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__guarded.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__guarded.py.snap new file mode 100644 index 00000000000000..9de06dabe88d09 --- /dev/null +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__guarded.py.snap @@ -0,0 +1,265 @@ +--- +source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs +expression: diagnostics +--- +- kind: + PathlibAbspath: ~ + location: + row: 8 + column: 4 + end_location: + row: 8 + column: 19 + fix: ~ + parent: ~ +- kind: + PathlibChmod: ~ + location: + row: 9 + column: 5 + end_location: + row: 9 + column: 13 + fix: ~ + parent: ~ +- kind: + PathlibMkdir: ~ + location: + row: 10 + column: 6 + end_location: + row: 10 + column: 14 + fix: ~ + parent: ~ +- kind: + PathlibMakedirs: ~ + location: + row: 11 + column: 0 + end_location: + row: 11 + column: 11 + fix: ~ + parent: ~ +- kind: + PathlibRename: ~ + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibReplace: ~ + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 10 + fix: ~ + parent: ~ +- kind: + PathlibRmdir: ~ + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 8 + fix: ~ + parent: ~ +- kind: + PathlibRemove: ~ + location: + row: 15 + column: 0 + end_location: + row: 15 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibUnlink: ~ + location: + row: 16 + column: 0 + end_location: + row: 16 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 17 + column: 0 + end_location: + row: 17 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibExists: ~ + location: + row: 18 + column: 4 + end_location: + row: 18 + column: 18 + fix: ~ + parent: ~ +- kind: + PathlibExpanduser: ~ + location: + row: 19 + column: 5 + end_location: + row: 19 + column: 23 + fix: ~ + parent: ~ +- kind: + PathlibIsDir: ~ + location: + row: 20 + column: 6 + end_location: + row: 20 + column: 19 + fix: ~ + parent: ~ +- kind: + PathlibIsFile: ~ + location: + row: 21 + column: 7 + end_location: + row: 21 + column: 21 + fix: ~ + parent: ~ +- kind: + PathlibIsLink: ~ + location: + row: 22 + column: 8 + end_location: + row: 22 + column: 22 + fix: ~ + parent: ~ +- kind: + PathlibReadlink: ~ + location: + row: 23 + column: 0 + end_location: + row: 23 + column: 11 + fix: ~ + parent: ~ +- kind: + PathlibStat: ~ + location: + row: 24 + column: 0 + end_location: + row: 24 + column: 7 + fix: ~ + parent: ~ +- kind: + PathlibIsAbs: ~ + location: + row: 25 + column: 0 + end_location: + row: 25 + column: 13 + fix: ~ + parent: ~ +- kind: + PathlibJoin: ~ + location: + row: 26 + column: 0 + end_location: + row: 26 + column: 12 + fix: ~ + parent: ~ +- kind: + PathlibBasename: ~ + location: + row: 27 + column: 0 + end_location: + row: 27 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibDirname: ~ + location: + row: 28 + column: 0 + end_location: + row: 28 + column: 15 + fix: ~ + parent: ~ +- kind: + PathlibSamefile: ~ + location: + row: 29 + column: 0 + end_location: + row: 29 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibSplitext: ~ + location: + row: 30 + column: 0 + end_location: + row: 30 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibOpen: ~ + location: + row: 31 + column: 5 + end_location: + row: 31 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibOpen: ~ + location: + row: 33 + column: 0 + end_location: + row: 33 + column: 4 + fix: ~ + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 34 + column: 0 + end_location: + row: 34 + column: 10 + fix: ~ + parent: ~ + diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap index 956e8e62b6b9f2..fc9ab5ab10abc3 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap @@ -1,234 +1,241 @@ --- -source: src/rules/flake8_use_pathlib/mod.rs +source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs expression: diagnostics --- - kind: PathlibAbspath: ~ location: - row: 6 + row: 7 column: 4 end_location: - row: 6 + row: 7 column: 17 fix: ~ parent: ~ - kind: PathlibChmod: ~ location: - row: 7 + row: 8 column: 5 end_location: - row: 7 + row: 8 column: 14 fix: ~ parent: ~ - kind: PathlibMkdir: ~ location: - row: 8 + row: 9 column: 6 end_location: - row: 8 + row: 9 column: 15 fix: ~ parent: ~ - kind: PathlibMakedirs: ~ location: - row: 9 + row: 10 column: 0 end_location: - row: 9 + row: 10 column: 12 fix: ~ parent: ~ - kind: PathlibRename: ~ location: - row: 10 + row: 11 column: 0 end_location: - row: 10 + row: 11 column: 10 fix: ~ parent: ~ - kind: PathlibReplace: ~ location: - row: 11 + row: 12 column: 0 end_location: - row: 11 + row: 12 column: 11 fix: ~ parent: ~ - kind: PathlibRmdir: ~ location: - row: 12 + row: 13 column: 0 end_location: - row: 12 + row: 13 column: 9 fix: ~ parent: ~ - kind: PathlibRemove: ~ location: - row: 13 + row: 14 column: 0 end_location: - row: 13 + row: 14 column: 10 fix: ~ parent: ~ - kind: PathlibUnlink: ~ location: - row: 14 + row: 15 column: 0 end_location: - row: 14 + row: 15 column: 10 fix: ~ parent: ~ - kind: PathlibGetcwd: ~ location: - row: 15 + row: 16 column: 0 end_location: - row: 15 + row: 16 column: 10 - fix: ~ + fix: + content: pth.Path.cwd + location: + row: 16 + column: 0 + end_location: + row: 16 + column: 10 parent: ~ - kind: PathlibExists: ~ location: - row: 16 + row: 17 column: 4 end_location: - row: 16 + row: 17 column: 16 fix: ~ parent: ~ - kind: PathlibExpanduser: ~ location: - row: 17 + row: 18 column: 5 end_location: - row: 17 + row: 18 column: 21 fix: ~ parent: ~ - kind: PathlibIsDir: ~ location: - row: 18 + row: 19 column: 6 end_location: - row: 18 + row: 19 column: 17 fix: ~ parent: ~ - kind: PathlibIsFile: ~ location: - row: 19 + row: 20 column: 7 end_location: - row: 19 + row: 20 column: 19 fix: ~ parent: ~ - kind: PathlibIsLink: ~ location: - row: 20 + row: 21 column: 8 end_location: - row: 20 + row: 21 column: 20 fix: ~ parent: ~ - kind: PathlibReadlink: ~ location: - row: 21 + row: 22 column: 0 end_location: - row: 21 + row: 22 column: 12 fix: ~ parent: ~ - kind: PathlibStat: ~ location: - row: 22 + row: 23 column: 0 end_location: - row: 22 + row: 23 column: 8 fix: ~ parent: ~ - kind: PathlibIsAbs: ~ location: - row: 23 + row: 24 column: 0 end_location: - row: 23 + row: 24 column: 11 fix: ~ parent: ~ - kind: PathlibJoin: ~ location: - row: 24 + row: 25 column: 0 end_location: - row: 24 + row: 25 column: 10 fix: ~ parent: ~ - kind: PathlibBasename: ~ location: - row: 25 + row: 26 column: 0 end_location: - row: 25 + row: 26 column: 14 fix: ~ parent: ~ - kind: PathlibDirname: ~ location: - row: 26 + row: 27 column: 0 end_location: - row: 26 + row: 27 column: 13 fix: ~ parent: ~ - kind: PathlibSamefile: ~ location: - row: 27 + row: 28 column: 0 end_location: - row: 27 + row: 28 column: 14 fix: ~ parent: ~ - kind: PathlibSplitext: ~ location: - row: 28 + row: 29 column: 0 end_location: - row: 28 + row: 29 column: 14 fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap index a3f6ff105b4409..16d86722760afc 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap @@ -1,254 +1,261 @@ --- -source: src/rules/flake8_use_pathlib/mod.rs +source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs expression: diagnostics --- - kind: PathlibAbspath: ~ location: - row: 8 + row: 9 column: 4 end_location: - row: 8 + row: 9 column: 11 fix: ~ parent: ~ - kind: PathlibChmod: ~ location: - row: 9 + row: 10 column: 5 end_location: - row: 9 + row: 10 column: 10 fix: ~ parent: ~ - kind: PathlibMkdir: ~ location: - row: 10 + row: 11 column: 6 end_location: - row: 10 + row: 11 column: 11 fix: ~ parent: ~ - kind: PathlibMakedirs: ~ location: - row: 11 + row: 12 column: 0 end_location: - row: 11 + row: 12 column: 8 fix: ~ parent: ~ - kind: PathlibRename: ~ location: - row: 12 + row: 13 column: 0 end_location: - row: 12 + row: 13 column: 6 fix: ~ parent: ~ - kind: PathlibReplace: ~ location: - row: 13 + row: 14 column: 0 end_location: - row: 13 + row: 14 column: 7 fix: ~ parent: ~ - kind: PathlibRmdir: ~ location: - row: 14 + row: 15 column: 0 end_location: - row: 14 + row: 15 column: 5 fix: ~ parent: ~ - kind: PathlibRemove: ~ location: - row: 15 + row: 16 column: 0 end_location: - row: 15 + row: 16 column: 6 fix: ~ parent: ~ - kind: PathlibUnlink: ~ location: - row: 16 + row: 17 column: 0 end_location: - row: 16 + row: 17 column: 6 fix: ~ parent: ~ - kind: PathlibGetcwd: ~ location: - row: 17 + row: 18 column: 0 end_location: - row: 17 + row: 18 column: 6 - fix: ~ + fix: + content: Path.cwd + location: + row: 18 + column: 0 + end_location: + row: 18 + column: 6 parent: ~ - kind: PathlibExists: ~ location: - row: 18 + row: 19 column: 4 end_location: - row: 18 + row: 19 column: 10 fix: ~ parent: ~ - kind: PathlibExpanduser: ~ location: - row: 19 + row: 20 column: 5 end_location: - row: 19 + row: 20 column: 15 fix: ~ parent: ~ - kind: PathlibIsDir: ~ location: - row: 20 + row: 21 column: 6 end_location: - row: 20 + row: 21 column: 11 fix: ~ parent: ~ - kind: PathlibIsFile: ~ location: - row: 21 + row: 22 column: 7 end_location: - row: 21 + row: 22 column: 13 fix: ~ parent: ~ - kind: PathlibIsLink: ~ location: - row: 22 + row: 23 column: 8 end_location: - row: 22 + row: 23 column: 14 fix: ~ parent: ~ - kind: PathlibReadlink: ~ location: - row: 23 + row: 24 column: 0 end_location: - row: 23 + row: 24 column: 8 fix: ~ parent: ~ - kind: PathlibStat: ~ location: - row: 24 + row: 25 column: 0 end_location: - row: 24 + row: 25 column: 4 fix: ~ parent: ~ - kind: PathlibIsAbs: ~ location: - row: 25 + row: 26 column: 0 end_location: - row: 25 + row: 26 column: 5 fix: ~ parent: ~ - kind: PathlibJoin: ~ location: - row: 26 + row: 27 column: 0 end_location: - row: 26 + row: 27 column: 4 fix: ~ parent: ~ - kind: PathlibBasename: ~ location: - row: 27 + row: 28 column: 0 end_location: - row: 27 + row: 28 column: 8 fix: ~ parent: ~ - kind: PathlibDirname: ~ location: - row: 28 + row: 29 column: 0 end_location: - row: 28 + row: 29 column: 7 fix: ~ parent: ~ - kind: PathlibSamefile: ~ location: - row: 29 + row: 30 column: 0 end_location: - row: 29 + row: 30 column: 8 fix: ~ parent: ~ - kind: PathlibSplitext: ~ location: - row: 30 + row: 31 column: 0 end_location: - row: 30 + row: 31 column: 8 fix: ~ parent: ~ - kind: PathlibOpen: ~ location: - row: 31 + row: 32 column: 5 end_location: - row: 31 + row: 32 column: 9 fix: ~ parent: ~ - kind: PathlibOpen: ~ location: - row: 33 + row: 34 column: 0 end_location: - row: 33 + row: 34 column: 4 fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap index f0ae20f26d9909..0bdd522f746154 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap @@ -1,234 +1,241 @@ --- -source: src/rules/flake8_use_pathlib/mod.rs +source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs expression: diagnostics --- - kind: PathlibAbspath: ~ location: - row: 13 + row: 14 column: 4 end_location: - row: 13 + row: 14 column: 12 fix: ~ parent: ~ - kind: PathlibChmod: ~ location: - row: 14 + row: 15 column: 5 end_location: - row: 14 + row: 15 column: 11 fix: ~ parent: ~ - kind: PathlibMkdir: ~ location: - row: 15 + row: 16 column: 6 end_location: - row: 15 + row: 16 column: 12 fix: ~ parent: ~ - kind: PathlibMakedirs: ~ location: - row: 16 + row: 17 column: 0 end_location: - row: 16 + row: 17 column: 9 fix: ~ parent: ~ - kind: PathlibRename: ~ location: - row: 17 + row: 18 column: 0 end_location: - row: 17 + row: 18 column: 7 fix: ~ parent: ~ - kind: PathlibReplace: ~ location: - row: 18 + row: 19 column: 0 end_location: - row: 18 + row: 19 column: 8 fix: ~ parent: ~ - kind: PathlibRmdir: ~ location: - row: 19 + row: 20 column: 0 end_location: - row: 19 + row: 20 column: 6 fix: ~ parent: ~ - kind: PathlibRemove: ~ location: - row: 20 + row: 21 column: 0 end_location: - row: 20 + row: 21 column: 7 fix: ~ parent: ~ - kind: PathlibUnlink: ~ location: - row: 21 + row: 22 column: 0 end_location: - row: 21 + row: 22 column: 7 fix: ~ parent: ~ - kind: PathlibGetcwd: ~ location: - row: 22 + row: 23 column: 0 end_location: - row: 22 + row: 23 column: 7 - fix: ~ + fix: + content: pth.cwd + location: + row: 23 + column: 0 + end_location: + row: 23 + column: 7 parent: ~ - kind: PathlibExists: ~ location: - row: 23 + row: 24 column: 4 end_location: - row: 23 + row: 24 column: 11 fix: ~ parent: ~ - kind: PathlibExpanduser: ~ location: - row: 24 + row: 25 column: 5 end_location: - row: 24 + row: 25 column: 16 fix: ~ parent: ~ - kind: PathlibIsDir: ~ location: - row: 25 + row: 26 column: 6 end_location: - row: 25 + row: 26 column: 12 fix: ~ parent: ~ - kind: PathlibIsFile: ~ location: - row: 26 + row: 27 column: 7 end_location: - row: 26 + row: 27 column: 14 fix: ~ parent: ~ - kind: PathlibIsLink: ~ location: - row: 27 + row: 28 column: 8 end_location: - row: 27 + row: 28 column: 15 fix: ~ parent: ~ - kind: PathlibReadlink: ~ location: - row: 28 + row: 29 column: 0 end_location: - row: 28 + row: 29 column: 9 fix: ~ parent: ~ - kind: PathlibStat: ~ location: - row: 29 + row: 30 column: 0 end_location: - row: 29 + row: 30 column: 5 fix: ~ parent: ~ - kind: PathlibIsAbs: ~ location: - row: 30 + row: 31 column: 0 end_location: - row: 30 + row: 31 column: 6 fix: ~ parent: ~ - kind: PathlibJoin: ~ location: - row: 31 + row: 32 column: 0 end_location: - row: 31 + row: 32 column: 5 fix: ~ parent: ~ - kind: PathlibBasename: ~ location: - row: 32 + row: 33 column: 0 end_location: - row: 32 + row: 33 column: 9 fix: ~ parent: ~ - kind: PathlibDirname: ~ location: - row: 33 + row: 34 column: 0 end_location: - row: 33 + row: 34 column: 8 fix: ~ parent: ~ - kind: PathlibSamefile: ~ location: - row: 34 + row: 35 column: 0 end_location: - row: 34 + row: 35 column: 9 fix: ~ parent: ~ - kind: PathlibSplitext: ~ location: - row: 35 + row: 36 column: 0 end_location: - row: 35 + row: 36 column: 9 fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__simplify_pathlib_constructor.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__simplify_pathlib_constructor.py.snap new file mode 100644 index 00000000000000..6a0e43e180a8d5 --- /dev/null +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__simplify_pathlib_constructor.py.snap @@ -0,0 +1,39 @@ +--- +source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs +expression: diagnostics +--- +- kind: + PathConstructorCurrentDirectory: ~ + location: + row: 5 + column: 4 + end_location: + row: 5 + column: 13 + fix: + content: "" + location: + row: 5 + column: 9 + end_location: + row: 5 + column: 12 + parent: ~ +- kind: + PathConstructorCurrentDirectory: ~ + location: + row: 6 + column: 4 + end_location: + row: 6 + column: 12 + fix: + content: "" + location: + row: 6 + column: 8 + end_location: + row: 6 + column: 11 + parent: ~ + diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__stat.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__stat.py.snap new file mode 100644 index 00000000000000..908476ff1bc511 --- /dev/null +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__stat.py.snap @@ -0,0 +1,135 @@ +--- +source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs +expression: diagnostics +--- +- kind: + PathlibGetatime: ~ + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibGetatime: ~ + location: + row: 5 + column: 0 + end_location: + row: 5 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibGetatime: ~ + location: + row: 6 + column: 0 + end_location: + row: 6 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibGetmtime: ~ + location: + row: 8 + column: 0 + end_location: + row: 8 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibGetmtime: ~ + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibGetmtime: ~ + location: + row: 10 + column: 0 + end_location: + row: 10 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibGetctime: ~ + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibGetctime: ~ + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibGetctime: ~ + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibGetsize: ~ + location: + row: 16 + column: 0 + end_location: + row: 16 + column: 15 + fix: ~ + parent: ~ +- kind: + PathlibGetsize: ~ + location: + row: 17 + column: 0 + end_location: + row: 17 + column: 15 + fix: ~ + parent: ~ +- kind: + PathlibGetsize: ~ + location: + row: 18 + column: 0 + end_location: + row: 18 + column: 15 + fix: ~ + parent: ~ +- kind: + PathlibGetsize: ~ + location: + row: 19 + column: 0 + end_location: + row: 19 + column: 15 + fix: ~ + parent: ~ + diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__use_pathlib.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__use_pathlib.py.snap index 359d76f726403c..5c13987c28ea2f 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__use_pathlib.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__use_pathlib.py.snap @@ -1,6 +1,73 @@ --- -source: src/rules/flake8_use_pathlib/mod.rs +source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs expression: diagnostics --- -[] +- kind: + PathlibGetcwd: ~ + location: + row: 6 + column: 9 + end_location: + row: 6 + column: 18 + fix: + content: Path.cwd + location: + row: 6 + column: 9 + end_location: + row: 6 + column: 18 + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 9 + column: 4 + end_location: + row: 10 + column: 14 + fix: + content: Path.cwd + location: + row: 9 + column: 4 + end_location: + row: 10 + column: 14 + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 14 + column: 4 + end_location: + row: 14 + column: 14 + fix: + content: Path.cwd + location: + row: 14 + column: 4 + end_location: + row: 14 + column: 14 + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 18 + column: 9 + end_location: + row: 18 + column: 18 + fix: + content: Path.cwd + location: + row: 18 + column: 9 + end_location: + row: 18 + column: 18 + parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/violations.rs b/crates/ruff/src/rules/flake8_use_pathlib/violations.rs index 084887c2dbb683..42a8174a50a58f 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/violations.rs +++ b/crates/ruff/src/rules/flake8_use_pathlib/violations.rs @@ -1,6 +1,6 @@ use ruff_macros::{define_violation, derive_message_formats}; -use crate::violation::Violation; +use crate::violation::{AutofixKind, Availability, Violation}; // PTH100 define_violation!( @@ -103,13 +103,42 @@ impl Violation for PathlibUnlink { // PTH109 define_violation!( + /// ## What is does + /// Detects the use of `os.getcwd` and `os.getcwdb`. + /// Autofix is available when the `pathlib` module is imported. + /// + /// ## Why is this bad? + /// A modern alternative to `os.getcwd()` is the `Path.cwd()` function + /// + /// ## Examples + /// ```python + /// cwd = os.getcwd() + /// ``` + /// + /// Use instead: + /// ```python + /// cwd = Path.cwd() + /// ``` + /// + /// ## References + /// * [PEP 428](https://peps.python.org/pep-0428/) + /// * [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module) + /// * [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/) + /// * [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/) + pub struct PathlibGetcwd; ); impl Violation for PathlibGetcwd { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.getcwd` should be replaced by `Path.cwd()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibGetcwd| format!("Replace `os.getcwd` with `Path.cwd()`")) + } } // PTH110 @@ -207,7 +236,7 @@ define_violation!( impl Violation for PathlibJoin { #[derive_message_formats] fn message(&self) -> String { - format!("`os.path.join` should be replaced by foo_path / \"bar\"") + format!("`os.path.join` should be replaced by `foo_path / \"bar\"`") } } @@ -276,3 +305,48 @@ impl Violation for PathlibPyPath { format!("`py.path` is in maintenance mode, use `pathlib` instead") } } + +// TODO: add documentation +// PTH201 +define_violation!( + pub struct PathlibGetsize; +); +impl Violation for PathlibGetsize { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.getsize` should be replaced by `stat().st_size`") + } +} + +// PTH202 +define_violation!( + pub struct PathlibGetatime; +); +impl Violation for PathlibGetatime { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.getatime` should be replaced by `stat().st_atime`") + } +} + +// PTH203 +define_violation!( + pub struct PathlibGetmtime; +); +impl Violation for PathlibGetmtime { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.getmtime` should be replaced by `stat().st_mtime`") + } +} + +// PTH204 +define_violation!( + pub struct PathlibGetctime; +); +impl Violation for PathlibGetctime { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.getctime` should be replaced by `stat().st_ctime`") + } +} diff --git a/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs b/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs index 6275ce8d174897..06969de468a0e8 100644 --- a/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs +++ b/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs @@ -1,6 +1,7 @@ use ruff_macros::{define_violation, derive_message_formats}; use rustpython_parser::ast::{Expr, ExprKind}; +use crate::ast::helpers; use crate::ast::types::{BindingKind, Range}; use crate::checkers::ast::Checker; use crate::fix::Fix; @@ -39,62 +40,6 @@ fn is_module_star_imported(checker: &Checker, module: &str) -> bool { }) } -/// Return the appropriate `sys.exit` reference based on the current set of -/// imports, or `None` is `sys.exit` hasn't been imported. -fn get_member_import_name_alias(checker: &Checker, module: &str, member: &str) -> Option { - checker.current_scopes().find_map(|scope| { - scope - .bindings - .values() - .find_map(|index| match &checker.bindings[*index].kind { - // e.g. module=sys object=exit - // `import sys` -> `sys.exit` - // `import sys as sys2` -> `sys2.exit` - BindingKind::Importation(name, full_name) => { - if full_name == &module { - Some(format!("{name}.{member}")) - } else { - None - } - } - // e.g. module=os.path object=join - // `from os.path import join` -> `join` - // `from os.path import join as join2` -> `join2` - BindingKind::FromImportation(name, full_name) => { - let mut parts = full_name.split('.'); - if parts.next() == Some(module) - && parts.next() == Some(member) - && parts.next().is_none() - { - Some((*name).to_string()) - } else { - None - } - } - // e.g. module=os.path object=join - // `from os.path import *` -> `join` - BindingKind::StarImportation(_, name) => { - if name.as_ref().map(|name| name == module).unwrap_or_default() { - Some(member.to_string()) - } else { - None - } - } - // e.g. module=os.path object=join - // `import os.path ` -> `os.path.join` - BindingKind::SubmoduleImportation(_, full_name) => { - if full_name == &module { - Some(format!("{full_name}.{member}")) - } else { - None - } - } - // Non-imports. - _ => None, - }) - }) -} - /// RUF004 pub fn consider_using_sys_exit(checker: &mut Checker, func: &Expr) { let ExprKind::Name { id, .. } = &func.node else { @@ -117,7 +62,7 @@ pub fn consider_using_sys_exit(checker: &mut Checker, func: &Expr) { Range::from_located(func), ); if checker.patch(diagnostic.kind.rule()) { - if let Some(content) = get_member_import_name_alias(checker, "sys", "exit") { + if let Some(content) = helpers::get_member_import_name_alias(checker, "sys", "exit") { diagnostic.amend(Fix::replacement( content, func.location, diff --git a/docs/rules/path-constructor-current-directory.md b/docs/rules/path-constructor-current-directory.md new file mode 100644 index 00000000000000..8a756dcab7dabe --- /dev/null +++ b/docs/rules/path-constructor-current-directory.md @@ -0,0 +1,26 @@ +# path-constructor-current-directory (PTH200) + +Derived from the **flake8-use-pathlib** linter. + +Autofix is sometimes available. + +## What it does +This rule detects pathlib's `Path` initializations with the default current directory argument. + +## Why is this bad? +The `Path()` constructor defaults to the current directory, so don't pass the +current directory (`"."`) explicitly. + +## Example +```python +from pathlib import Path + +_ = Path(".") +``` + +Use instead: +```python +from pathlib import Path + +_ = Path() +``` \ No newline at end of file diff --git a/docs/rules/pathlib-getcwd.md b/docs/rules/pathlib-getcwd.md new file mode 100644 index 00000000000000..37be84dd926b29 --- /dev/null +++ b/docs/rules/pathlib-getcwd.md @@ -0,0 +1,28 @@ +# pathlib-getcwd (PTH109) + +Derived from the **flake8-use-pathlib** linter. + +Autofix is sometimes available. + +## What is does +Detects the use of `os.getcwd` and `os.getcwdb`. +Autofix is available when the `pathlib` module is imported. + +## Why is this bad? +A modern alternative to `os.getcwd()` is the `Path.cwd()` function + +## Examples +```python +cwd = os.getcwd() +``` + +Use instead: +```python +cwd = Path.cwd() +``` + +## References +* [PEP 428](https://peps.python.org/pep-0428/) +* [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module) +* [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/) +* [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/) \ No newline at end of file diff --git a/ruff.schema.json b/ruff.schema.json index 53afb04fb57a25..b464b55b47cfab 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1804,6 +1804,13 @@ "PTH122", "PTH123", "PTH124", + "PTH2", + "PTH20", + "PTH200", + "PTH201", + "PTH202", + "PTH203", + "PTH204", "PYI", "PYI0", "PYI00", From e8285f84526932f574889a8234f36ba1e628c244 Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Sun, 12 Feb 2023 00:50:28 +0100 Subject: [PATCH 2/2] progress --- README.md | 32 +-- .../fixtures/flake8_use_pathlib/full_name.py | 10 +- .../flake8_use_pathlib/use_pathlib.py | 3 + crates/ruff/src/checkers/ast.rs | 1 + .../src/rules/flake8_use_pathlib/fixes.rs | 133 +++++++---- .../src/rules/flake8_use_pathlib/helpers.rs | 11 +- ...e_pathlib__tests__PTH124_py_path_1.py.snap | 4 +- ...e_pathlib__tests__PTH124_py_path_2.py.snap | 4 +- ...ake8_use_pathlib__tests__full_name.py.snap | 214 +++++++++++++----- ...flake8_use_pathlib__tests__guarded.py.snap | 52 ++--- ...ake8_use_pathlib__tests__import_as.py.snap | 194 ++++++++++++---- ...e8_use_pathlib__tests__import_from.py.snap | 198 ++++++++++++---- ...use_pathlib__tests__import_from_as.py.snap | 194 ++++++++++++---- ...s__flake8_use_pathlib__tests__stat.py.snap | 26 +-- ...e8_use_pathlib__tests__use_pathlib.py.snap | 55 ++--- .../rules/flake8_use_pathlib/violations.rs | 131 ++++++++++- docs/rules/pathlib-getcwd.md | 3 + docs/rules/pathlib-readlink.md | 32 +++ resources/test/popmon | 1 + 19 files changed, 975 insertions(+), 323 deletions(-) create mode 100644 docs/rules/pathlib-readlink.md create mode 160000 resources/test/popmon diff --git a/README.md b/README.md index 5eaaf116a40667..51dfebd8f9f0c5 100644 --- a/README.md +++ b/README.md @@ -1281,28 +1281,28 @@ For more, see [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) | Code | Name | Message | Fix | | ---- | ---- | ------- | --- | -| PTH100 | pathlib-abspath | `os.path.abspath` should be replaced by `.resolve()` | | -| PTH101 | pathlib-chmod | `os.chmod` should be replaced by `.chmod()` | | -| PTH102 | pathlib-mkdir | `os.mkdir` should be replaced by `.mkdir()` | | +| PTH100 | pathlib-abspath | `os.path.abspath` should be replaced by `.resolve()` | 🛠 | +| PTH101 | pathlib-chmod | `os.chmod` should be replaced by `.chmod()` | 🛠 | +| PTH102 | pathlib-mkdir | `os.mkdir` should be replaced by `.mkdir()` | 🛠 | | PTH103 | pathlib-makedirs | `os.makedirs` should be replaced by `.mkdir(parents=True)` | | -| PTH104 | pathlib-rename | `os.rename` should be replaced by `.rename()` | | -| PTH105 | pathlib-replace | `os.replace`should be replaced by `.replace()` | | -| PTH106 | pathlib-rmdir | `os.rmdir` should be replaced by `.rmdir()` | | -| PTH107 | pathlib-remove | `os.remove` should be replaced by `.unlink()` | | -| PTH108 | pathlib-unlink | `os.unlink` should be replaced by `.unlink()` | | +| PTH104 | pathlib-rename | `os.rename` should be replaced by `.rename()` | 🛠 | +| PTH105 | pathlib-replace | `os.replace`should be replaced by `.replace()` | 🛠 | +| PTH106 | pathlib-rmdir | `os.rmdir` should be replaced by `.rmdir()` | 🛠 | +| PTH107 | pathlib-remove | `os.remove` should be replaced by `.unlink()` | 🛠 | +| PTH108 | pathlib-unlink | `os.unlink` should be replaced by `.unlink()` | 🛠 | | PTH109 | [pathlib-getcwd](https://github.com/charliermarsh/ruff/blob/main/docs/rules/pathlib-getcwd.md) | `os.getcwd` should be replaced by `Path.cwd()` | 🛠 | -| PTH110 | pathlib-exists | `os.path.exists` should be replaced by `.exists()` | | -| PTH111 | pathlib-expanduser | `os.path.expanduser` should be replaced by `.expanduser()` | | -| PTH112 | pathlib-is-dir | `os.path.isdir` should be replaced by `.is_dir()` | | -| PTH113 | pathlib-is-file | `os.path.isfile` should be replaced by `.is_file()` | | -| PTH114 | pathlib-is-link | `os.path.islink` should be replaced by `.is_symlink()` | | -| PTH115 | pathlib-readlink | `os.readlink` should be replaced by `.readlink()` | | +| PTH110 | pathlib-exists | `os.path.exists` should be replaced by `.exists()` | 🛠 | +| PTH111 | pathlib-expanduser | `os.path.expanduser` should be replaced by `.expanduser()` | 🛠 | +| PTH112 | pathlib-is-dir | `os.path.isdir` should be replaced by `.is_dir()` | 🛠 | +| PTH113 | pathlib-is-file | `os.path.isfile` should be replaced by `.is_file()` | 🛠 | +| PTH114 | pathlib-is-link | `os.path.islink` should be replaced by `.is_symlink()` | 🛠 | +| PTH115 | [pathlib-readlink](https://github.com/charliermarsh/ruff/blob/main/docs/rules/pathlib-readlink.md) | `os.readlink` should be replaced by `.readlink()` | 🛠 | | PTH116 | pathlib-stat | `os.stat` should be replaced by `.stat()` or `.owner()` or `.group()` | | -| PTH117 | pathlib-is-abs | `os.path.isabs` should be replaced by `.is_absolute()` | | +| PTH117 | pathlib-is-abs | `os.path.isabs` should be replaced by `.is_absolute()` | 🛠 | | PTH118 | pathlib-join | `os.path.join` should be replaced by `foo_path / "bar"` | | | PTH119 | pathlib-basename | `os.path.basename` should be replaced by `.name` | | | PTH120 | pathlib-dirname | `os.path.dirname` should be replaced by `.parent` | | -| PTH121 | pathlib-samefile | `os.path.samefile` should be replaced by `.samefile()` | | +| PTH121 | pathlib-samefile | `os.path.samefile` should be replaced by `.samefile()` | 🛠 | | PTH122 | pathlib-splitext | `os.path.splitext` should be replaced by `.suffix` | | | PTH123 | pathlib-open | `open("foo")` should be replaced by `Path("foo").open()` | | | PTH124 | pathlib-py-path | `py.path` is in maintenance mode, use `pathlib` instead | | diff --git a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/full_name.py b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/full_name.py index 65064c8d57fc4b..4e45512a3ffe42 100644 --- a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/full_name.py +++ b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/full_name.py @@ -5,15 +5,15 @@ p = "/foo" a = os.path.abspath(p) -aa = os.chmod(p) +aa = os.chmod(p, mode=511) aaa = os.mkdir(p) os.makedirs(p) -os.rename(p) -os.replace(p) +os.rename(p, q) +os.replace(p, q) os.rmdir(p) os.remove(p) os.unlink(p) -os.getcwd(p) +os.getcwd() b = os.path.exists(p) bb = os.path.expanduser(p) bbb = os.path.isdir(p) @@ -30,4 +30,4 @@ with open(p) as fp: fp.read() open(p).close() -os.getcwdb(p) +os.getcwdb() diff --git a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/use_pathlib.py b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/use_pathlib.py index 8298db4b3aaaf2..462ad304cd8e6b 100644 --- a/crates/ruff/resources/test/fixtures/flake8_use_pathlib/use_pathlib.py +++ b/crates/ruff/resources/test/fixtures/flake8_use_pathlib/use_pathlib.py @@ -16,3 +16,6 @@ # should not be unwrapped _ = Path(os.getcwd(), hello='world') + +# other cases +os.path.abspath("../../hello.py") diff --git a/crates/ruff/src/checkers/ast.rs b/crates/ruff/src/checkers/ast.rs index fb87b286690156..2030747047c17d 100644 --- a/crates/ruff/src/checkers/ast.rs +++ b/crates/ruff/src/checkers/ast.rs @@ -2758,6 +2758,7 @@ where { flake8_use_pathlib::helpers::replaceable_by_pathlib( self, + expr, func, self.current_expr_parent().map(Into::into), ); diff --git a/crates/ruff/src/rules/flake8_use_pathlib/fixes.rs b/crates/ruff/src/rules/flake8_use_pathlib/fixes.rs index 74e67a003715c0..fb093e95ed5a20 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/fixes.rs +++ b/crates/ruff/src/rules/flake8_use_pathlib/fixes.rs @@ -1,6 +1,6 @@ -use rustpython_parser::ast::{Expr, ExprKind}; +use rustpython_parser::ast::{Expr, ExprContext, ExprKind}; -use crate::ast::helpers; +use crate::ast::helpers::{self, create_expr, unparse_expr}; use crate::ast::types::Range; use crate::autofix::apply_fix; use crate::checkers::ast::Checker; @@ -11,56 +11,111 @@ use crate::source_code::Locator; pub fn pathlib_fix( checker: &mut Checker, diagnostic: &DiagnosticKind, + expr: &Expr, func: &Expr, parent: Option<&Expr>, ) -> Option { // Guard that Path is imported, `content` contains the name or aliaas if let Some(content) = helpers::get_member_import_name_alias(checker, "pathlib", "Path") { - let mut fix = match diagnostic { - DiagnosticKind::PathlibGetcwd(_) => Some(Fix::replacement( - format!("{content}.cwd"), - func.location, - func.end_location.unwrap(), - )), - _ => None, - }; + if let ExprKind::Call { args, keywords, .. } = &expr.node { + // TODO: validate args/keywords, possibly map + // TODO: add non-call replacements + let replacement = match diagnostic { + DiagnosticKind::PathlibAbspath(_) => "resolve", + DiagnosticKind::PathlibChmod(_) => "chmod", + DiagnosticKind::PathlibMkdir(_) => "mkdir", + // Makedirs + DiagnosticKind::PathlibRename(_) => "rename", + DiagnosticKind::PathlibReplace(_) => "replace", + DiagnosticKind::PathlibRmdir(_) => "rmdir", + DiagnosticKind::PathlibRemove(_) => "unlink", + DiagnosticKind::PathlibUnlink(_) => "unlink", + DiagnosticKind::PathlibGetcwd(_) => "cwd", + DiagnosticKind::PathlibExists(_) => "exists", + DiagnosticKind::PathlibExpanduser(_) => "expanduser", + DiagnosticKind::PathlibIsDir(_) => "is_dir", + DiagnosticKind::PathlibIsFile(_) => "is_file", + DiagnosticKind::PathlibIsLink(_) => "is_symlink", + DiagnosticKind::PathlibReadlink(_) => "readlink", + // Stat + DiagnosticKind::PathlibIsAbs(_) => "is_absolute", + // Join + // Basename + // Dirname + DiagnosticKind::PathlibSamefile(_) => "samefile", + // Splitext + // Open + _ => return None, + }; - // Wrapped in a `Path()` call - if let Some(fixme) = fix.clone() { - if let Some(parent) = parent { - if checker - .resolve_call_path(parent) - .map_or(false, |call_path| { - call_path.as_slice() == ["pathlib", "Path"] - }) - { - if let ExprKind::Call { args, keywords, .. } = &parent.node { - if args.len() == 1 && keywords.is_empty() { - // Reset the line index - let fixme = Fix::replacement( - fixme.content.to_string(), - helpers::to_relative(fixme.location, func.location), - helpers::to_relative(fixme.end_location, func.location), - ); + if let Some((head, tail)) = args.clone().split_first() { + let fix_str = unparse_expr( + &create_expr(ExprKind::Call { + func: Box::new(create_expr(ExprKind::Attribute { + value: Box::new(create_expr(ExprKind::Call { + func: Box::new(create_expr(ExprKind::Name { + id: content, + ctx: ExprContext::Load, + })), + args: vec![head.clone()], + keywords: vec![], + })), + attr: replacement.to_string(), + ctx: ExprContext::Load, + })), + args: tail.to_vec(), + keywords: keywords.clone(), + }), + checker.stylist, + ); - // Apply the fix - let arg = args.first().unwrap(); - let contents = checker.locator.slice_source_code_range(&Range::new( - arg.location, - arg.end_location.unwrap(), - )); + let mut fix = Some(Fix::replacement( + fix_str, + expr.location, + expr.end_location.unwrap(), + )); - fix = Some(Fix::replacement( - apply_fix(&fixme, &Locator::new(contents)), - parent.location, - parent.end_location.unwrap(), - )); + // Wrapped in a `Path()` call + if let Some(fixme) = fix.clone() { + if let Some(parent) = parent { + if checker + .resolve_call_path(parent) + .map_or(false, |call_path| { + call_path.as_slice() == ["pathlib", "Path"] + }) + { + if let ExprKind::Call { args, keywords, .. } = &parent.node { + if args.len() == 1 && keywords.is_empty() { + // Reset the line index + let fixme = Fix::replacement( + fixme.content.to_string(), + helpers::to_relative(fixme.location, func.location), + helpers::to_relative(fixme.end_location, func.location), + ); + + // Apply the fix + let arg = args.first().unwrap(); + let contents = checker.locator.slice_source_code_range( + &Range::new(arg.location, arg.end_location.unwrap()), + ); + + fix = Some(Fix::replacement( + apply_fix(&fixme, &Locator::new(contents)), + parent.location, + parent.end_location.unwrap(), + )); + } + } } } } + fix + } else { + None } + } else { + None } - fix } else { None } diff --git a/crates/ruff/src/rules/flake8_use_pathlib/helpers.rs b/crates/ruff/src/rules/flake8_use_pathlib/helpers.rs index eb6e9c22c5da33..f8aa328f9e1f6f 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/helpers.rs +++ b/crates/ruff/src/rules/flake8_use_pathlib/helpers.rs @@ -14,10 +14,15 @@ use crate::rules::flake8_use_pathlib::violations::{ }; use crate::settings::types::PythonVersion; -pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr, parent: Option<&Expr>) { +pub fn replaceable_by_pathlib( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + parent: Option<&Expr>, +) { if let Some(diagnostic_kind) = checker - .resolve_call_path(expr) + .resolve_call_path(func) .and_then(|call_path| match call_path.as_slice() { ["os", "path", "abspath"] => Some(PathlibAbspath.into()), ["os", "chmod"] => Some(PathlibChmod.into()), @@ -58,7 +63,7 @@ pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr, parent: Option let mut diagnostic = Diagnostic::new::(diagnostic_kind, Range::from_located(expr)); if checker.patch(diagnostic.kind.rule()) { - if let Some(fix) = pathlib_fix(checker, &diagnostic.kind, expr, parent) { + if let Some(fix) = pathlib_fix(checker, &diagnostic.kind, expr, func, parent) { diagnostic.amend(fix); } } diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_1.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_1.py.snap index b27363952472b1..800fbbd4780737 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_1.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_1.py.snap @@ -1,5 +1,5 @@ --- -source: src/rules/flake8_use_pathlib/mod.rs +source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs expression: diagnostics --- - kind: @@ -9,7 +9,7 @@ expression: diagnostics column: 4 end_location: row: 3 - column: 17 + column: 27 fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_2.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_2.py.snap index c5f6ad5501c67c..7a7a6f0440a543 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_2.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_2.py.snap @@ -1,5 +1,5 @@ --- -source: src/rules/flake8_use_pathlib/mod.rs +source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs expression: diagnostics --- - kind: @@ -9,7 +9,7 @@ expression: diagnostics column: 4 end_location: row: 3 - column: 8 + column: 16 fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap index 2da1d596753c35..4af258a54f2b3b 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap @@ -9,8 +9,15 @@ expression: diagnostics column: 4 end_location: row: 7 - column: 19 - fix: ~ + column: 22 + fix: + content: pathlib.Path(p).resolve() + location: + row: 7 + column: 4 + end_location: + row: 7 + column: 22 parent: ~ - kind: PathlibChmod: ~ @@ -19,8 +26,15 @@ expression: diagnostics column: 5 end_location: row: 8 - column: 13 - fix: ~ + column: 26 + fix: + content: pathlib.Path(p).chmod(mode=511) + location: + row: 8 + column: 5 + end_location: + row: 8 + column: 26 parent: ~ - kind: PathlibMkdir: ~ @@ -29,8 +43,15 @@ expression: diagnostics column: 6 end_location: row: 9 - column: 14 - fix: ~ + column: 17 + fix: + content: pathlib.Path(p).mkdir() + location: + row: 9 + column: 6 + end_location: + row: 9 + column: 17 parent: ~ - kind: PathlibMakedirs: ~ @@ -39,7 +60,7 @@ expression: diagnostics column: 0 end_location: row: 10 - column: 11 + column: 14 fix: ~ parent: ~ - kind: @@ -49,8 +70,15 @@ expression: diagnostics column: 0 end_location: row: 11 - column: 9 - fix: ~ + column: 15 + fix: + content: pathlib.Path(p).rename(q) + location: + row: 11 + column: 0 + end_location: + row: 11 + column: 15 parent: ~ - kind: PathlibReplace: ~ @@ -59,8 +87,15 @@ expression: diagnostics column: 0 end_location: row: 12 - column: 10 - fix: ~ + column: 16 + fix: + content: pathlib.Path(p).replace(q) + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 16 parent: ~ - kind: PathlibRmdir: ~ @@ -69,8 +104,15 @@ expression: diagnostics column: 0 end_location: row: 13 - column: 8 - fix: ~ + column: 11 + fix: + content: pathlib.Path(p).rmdir() + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 11 parent: ~ - kind: PathlibRemove: ~ @@ -79,8 +121,15 @@ expression: diagnostics column: 0 end_location: row: 14 - column: 9 - fix: ~ + column: 12 + fix: + content: pathlib.Path(p).unlink() + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 12 parent: ~ - kind: PathlibUnlink: ~ @@ -89,8 +138,15 @@ expression: diagnostics column: 0 end_location: row: 15 - column: 9 - fix: ~ + column: 12 + fix: + content: pathlib.Path(p).unlink() + location: + row: 15 + column: 0 + end_location: + row: 15 + column: 12 parent: ~ - kind: PathlibGetcwd: ~ @@ -99,15 +155,8 @@ expression: diagnostics column: 0 end_location: row: 16 - column: 9 - fix: - content: pathlib.Path.cwd - location: - row: 16 - column: 0 - end_location: - row: 16 - column: 9 + column: 11 + fix: ~ parent: ~ - kind: PathlibExists: ~ @@ -116,8 +165,15 @@ expression: diagnostics column: 4 end_location: row: 17 - column: 18 - fix: ~ + column: 21 + fix: + content: pathlib.Path(p).exists() + location: + row: 17 + column: 4 + end_location: + row: 17 + column: 21 parent: ~ - kind: PathlibExpanduser: ~ @@ -126,8 +182,15 @@ expression: diagnostics column: 5 end_location: row: 18 - column: 23 - fix: ~ + column: 26 + fix: + content: pathlib.Path(p).expanduser() + location: + row: 18 + column: 5 + end_location: + row: 18 + column: 26 parent: ~ - kind: PathlibIsDir: ~ @@ -136,8 +199,15 @@ expression: diagnostics column: 6 end_location: row: 19 - column: 19 - fix: ~ + column: 22 + fix: + content: pathlib.Path(p).is_dir() + location: + row: 19 + column: 6 + end_location: + row: 19 + column: 22 parent: ~ - kind: PathlibIsFile: ~ @@ -146,8 +216,15 @@ expression: diagnostics column: 7 end_location: row: 20 - column: 21 - fix: ~ + column: 24 + fix: + content: pathlib.Path(p).is_file() + location: + row: 20 + column: 7 + end_location: + row: 20 + column: 24 parent: ~ - kind: PathlibIsLink: ~ @@ -156,8 +233,15 @@ expression: diagnostics column: 8 end_location: row: 21 - column: 22 - fix: ~ + column: 25 + fix: + content: pathlib.Path(p).is_symlink() + location: + row: 21 + column: 8 + end_location: + row: 21 + column: 25 parent: ~ - kind: PathlibReadlink: ~ @@ -166,8 +250,15 @@ expression: diagnostics column: 0 end_location: row: 22 - column: 11 - fix: ~ + column: 14 + fix: + content: pathlib.Path(p).readlink() + location: + row: 22 + column: 0 + end_location: + row: 22 + column: 14 parent: ~ - kind: PathlibStat: ~ @@ -176,7 +267,7 @@ expression: diagnostics column: 0 end_location: row: 23 - column: 7 + column: 10 fix: ~ parent: ~ - kind: @@ -186,8 +277,15 @@ expression: diagnostics column: 0 end_location: row: 24 - column: 13 - fix: ~ + column: 16 + fix: + content: pathlib.Path(p).is_absolute() + location: + row: 24 + column: 0 + end_location: + row: 24 + column: 16 parent: ~ - kind: PathlibJoin: ~ @@ -196,7 +294,7 @@ expression: diagnostics column: 0 end_location: row: 25 - column: 12 + column: 15 fix: ~ parent: ~ - kind: @@ -206,7 +304,7 @@ expression: diagnostics column: 0 end_location: row: 26 - column: 16 + column: 19 fix: ~ parent: ~ - kind: @@ -216,7 +314,7 @@ expression: diagnostics column: 0 end_location: row: 27 - column: 15 + column: 18 fix: ~ parent: ~ - kind: @@ -226,8 +324,15 @@ expression: diagnostics column: 0 end_location: row: 28 - column: 16 - fix: ~ + column: 19 + fix: + content: pathlib.Path(p).samefile() + location: + row: 28 + column: 0 + end_location: + row: 28 + column: 19 parent: ~ - kind: PathlibSplitext: ~ @@ -236,7 +341,7 @@ expression: diagnostics column: 0 end_location: row: 29 - column: 16 + column: 19 fix: ~ parent: ~ - kind: @@ -246,7 +351,7 @@ expression: diagnostics column: 5 end_location: row: 30 - column: 9 + column: 12 fix: ~ parent: ~ - kind: @@ -256,7 +361,7 @@ expression: diagnostics column: 0 end_location: row: 32 - column: 4 + column: 7 fix: ~ parent: ~ - kind: @@ -266,14 +371,7 @@ expression: diagnostics column: 0 end_location: row: 33 - column: 10 - fix: - content: pathlib.Path.cwd - location: - row: 33 - column: 0 - end_location: - row: 33 - column: 10 + column: 12 + fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__guarded.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__guarded.py.snap index 9de06dabe88d09..1bebc9f83ae576 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__guarded.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__guarded.py.snap @@ -9,7 +9,7 @@ expression: diagnostics column: 4 end_location: row: 8 - column: 19 + column: 22 fix: ~ parent: ~ - kind: @@ -19,7 +19,7 @@ expression: diagnostics column: 5 end_location: row: 9 - column: 13 + column: 16 fix: ~ parent: ~ - kind: @@ -29,7 +29,7 @@ expression: diagnostics column: 6 end_location: row: 10 - column: 14 + column: 17 fix: ~ parent: ~ - kind: @@ -39,7 +39,7 @@ expression: diagnostics column: 0 end_location: row: 11 - column: 11 + column: 14 fix: ~ parent: ~ - kind: @@ -49,7 +49,7 @@ expression: diagnostics column: 0 end_location: row: 12 - column: 9 + column: 12 fix: ~ parent: ~ - kind: @@ -59,7 +59,7 @@ expression: diagnostics column: 0 end_location: row: 13 - column: 10 + column: 13 fix: ~ parent: ~ - kind: @@ -69,7 +69,7 @@ expression: diagnostics column: 0 end_location: row: 14 - column: 8 + column: 11 fix: ~ parent: ~ - kind: @@ -79,7 +79,7 @@ expression: diagnostics column: 0 end_location: row: 15 - column: 9 + column: 12 fix: ~ parent: ~ - kind: @@ -89,7 +89,7 @@ expression: diagnostics column: 0 end_location: row: 16 - column: 9 + column: 12 fix: ~ parent: ~ - kind: @@ -99,7 +99,7 @@ expression: diagnostics column: 0 end_location: row: 17 - column: 9 + column: 12 fix: ~ parent: ~ - kind: @@ -109,7 +109,7 @@ expression: diagnostics column: 4 end_location: row: 18 - column: 18 + column: 21 fix: ~ parent: ~ - kind: @@ -119,7 +119,7 @@ expression: diagnostics column: 5 end_location: row: 19 - column: 23 + column: 26 fix: ~ parent: ~ - kind: @@ -129,7 +129,7 @@ expression: diagnostics column: 6 end_location: row: 20 - column: 19 + column: 22 fix: ~ parent: ~ - kind: @@ -139,7 +139,7 @@ expression: diagnostics column: 7 end_location: row: 21 - column: 21 + column: 24 fix: ~ parent: ~ - kind: @@ -149,7 +149,7 @@ expression: diagnostics column: 8 end_location: row: 22 - column: 22 + column: 25 fix: ~ parent: ~ - kind: @@ -159,7 +159,7 @@ expression: diagnostics column: 0 end_location: row: 23 - column: 11 + column: 14 fix: ~ parent: ~ - kind: @@ -169,7 +169,7 @@ expression: diagnostics column: 0 end_location: row: 24 - column: 7 + column: 10 fix: ~ parent: ~ - kind: @@ -179,7 +179,7 @@ expression: diagnostics column: 0 end_location: row: 25 - column: 13 + column: 16 fix: ~ parent: ~ - kind: @@ -189,7 +189,7 @@ expression: diagnostics column: 0 end_location: row: 26 - column: 12 + column: 15 fix: ~ parent: ~ - kind: @@ -199,7 +199,7 @@ expression: diagnostics column: 0 end_location: row: 27 - column: 16 + column: 19 fix: ~ parent: ~ - kind: @@ -209,7 +209,7 @@ expression: diagnostics column: 0 end_location: row: 28 - column: 15 + column: 18 fix: ~ parent: ~ - kind: @@ -219,7 +219,7 @@ expression: diagnostics column: 0 end_location: row: 29 - column: 16 + column: 19 fix: ~ parent: ~ - kind: @@ -229,7 +229,7 @@ expression: diagnostics column: 0 end_location: row: 30 - column: 16 + column: 19 fix: ~ parent: ~ - kind: @@ -239,7 +239,7 @@ expression: diagnostics column: 5 end_location: row: 31 - column: 9 + column: 12 fix: ~ parent: ~ - kind: @@ -249,7 +249,7 @@ expression: diagnostics column: 0 end_location: row: 33 - column: 4 + column: 7 fix: ~ parent: ~ - kind: @@ -259,7 +259,7 @@ expression: diagnostics column: 0 end_location: row: 34 - column: 10 + column: 13 fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap index fc9ab5ab10abc3..32dfdc8abc43ae 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap @@ -9,8 +9,15 @@ expression: diagnostics column: 4 end_location: row: 7 - column: 17 - fix: ~ + column: 20 + fix: + content: pth.Path(p).resolve() + location: + row: 7 + column: 4 + end_location: + row: 7 + column: 20 parent: ~ - kind: PathlibChmod: ~ @@ -19,8 +26,15 @@ expression: diagnostics column: 5 end_location: row: 8 - column: 14 - fix: ~ + column: 17 + fix: + content: pth.Path(p).chmod() + location: + row: 8 + column: 5 + end_location: + row: 8 + column: 17 parent: ~ - kind: PathlibMkdir: ~ @@ -29,8 +43,15 @@ expression: diagnostics column: 6 end_location: row: 9 - column: 15 - fix: ~ + column: 18 + fix: + content: pth.Path(p).mkdir() + location: + row: 9 + column: 6 + end_location: + row: 9 + column: 18 parent: ~ - kind: PathlibMakedirs: ~ @@ -39,7 +60,7 @@ expression: diagnostics column: 0 end_location: row: 10 - column: 12 + column: 15 fix: ~ parent: ~ - kind: @@ -49,8 +70,15 @@ expression: diagnostics column: 0 end_location: row: 11 - column: 10 - fix: ~ + column: 13 + fix: + content: pth.Path(p).rename() + location: + row: 11 + column: 0 + end_location: + row: 11 + column: 13 parent: ~ - kind: PathlibReplace: ~ @@ -59,8 +87,15 @@ expression: diagnostics column: 0 end_location: row: 12 - column: 11 - fix: ~ + column: 14 + fix: + content: pth.Path(p).replace() + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 14 parent: ~ - kind: PathlibRmdir: ~ @@ -69,8 +104,15 @@ expression: diagnostics column: 0 end_location: row: 13 - column: 9 - fix: ~ + column: 12 + fix: + content: pth.Path(p).rmdir() + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 12 parent: ~ - kind: PathlibRemove: ~ @@ -79,8 +121,15 @@ expression: diagnostics column: 0 end_location: row: 14 - column: 10 - fix: ~ + column: 13 + fix: + content: pth.Path(p).unlink() + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 13 parent: ~ - kind: PathlibUnlink: ~ @@ -89,8 +138,15 @@ expression: diagnostics column: 0 end_location: row: 15 - column: 10 - fix: ~ + column: 13 + fix: + content: pth.Path(p).unlink() + location: + row: 15 + column: 0 + end_location: + row: 15 + column: 13 parent: ~ - kind: PathlibGetcwd: ~ @@ -99,15 +155,15 @@ expression: diagnostics column: 0 end_location: row: 16 - column: 10 + column: 13 fix: - content: pth.Path.cwd + content: pth.Path(p).cwd() location: row: 16 column: 0 end_location: row: 16 - column: 10 + column: 13 parent: ~ - kind: PathlibExists: ~ @@ -116,8 +172,15 @@ expression: diagnostics column: 4 end_location: row: 17 - column: 16 - fix: ~ + column: 19 + fix: + content: pth.Path(p).exists() + location: + row: 17 + column: 4 + end_location: + row: 17 + column: 19 parent: ~ - kind: PathlibExpanduser: ~ @@ -126,8 +189,15 @@ expression: diagnostics column: 5 end_location: row: 18 - column: 21 - fix: ~ + column: 24 + fix: + content: pth.Path(p).expanduser() + location: + row: 18 + column: 5 + end_location: + row: 18 + column: 24 parent: ~ - kind: PathlibIsDir: ~ @@ -136,8 +206,15 @@ expression: diagnostics column: 6 end_location: row: 19 - column: 17 - fix: ~ + column: 20 + fix: + content: pth.Path(p).is_dir() + location: + row: 19 + column: 6 + end_location: + row: 19 + column: 20 parent: ~ - kind: PathlibIsFile: ~ @@ -146,8 +223,15 @@ expression: diagnostics column: 7 end_location: row: 20 - column: 19 - fix: ~ + column: 22 + fix: + content: pth.Path(p).is_file() + location: + row: 20 + column: 7 + end_location: + row: 20 + column: 22 parent: ~ - kind: PathlibIsLink: ~ @@ -156,8 +240,15 @@ expression: diagnostics column: 8 end_location: row: 21 - column: 20 - fix: ~ + column: 23 + fix: + content: pth.Path(p).is_symlink() + location: + row: 21 + column: 8 + end_location: + row: 21 + column: 23 parent: ~ - kind: PathlibReadlink: ~ @@ -166,8 +257,15 @@ expression: diagnostics column: 0 end_location: row: 22 - column: 12 - fix: ~ + column: 15 + fix: + content: pth.Path(p).readlink() + location: + row: 22 + column: 0 + end_location: + row: 22 + column: 15 parent: ~ - kind: PathlibStat: ~ @@ -176,7 +274,7 @@ expression: diagnostics column: 0 end_location: row: 23 - column: 8 + column: 11 fix: ~ parent: ~ - kind: @@ -186,8 +284,15 @@ expression: diagnostics column: 0 end_location: row: 24 - column: 11 - fix: ~ + column: 14 + fix: + content: pth.Path(p).is_absolute() + location: + row: 24 + column: 0 + end_location: + row: 24 + column: 14 parent: ~ - kind: PathlibJoin: ~ @@ -196,7 +301,7 @@ expression: diagnostics column: 0 end_location: row: 25 - column: 10 + column: 13 fix: ~ parent: ~ - kind: @@ -206,7 +311,7 @@ expression: diagnostics column: 0 end_location: row: 26 - column: 14 + column: 17 fix: ~ parent: ~ - kind: @@ -216,7 +321,7 @@ expression: diagnostics column: 0 end_location: row: 27 - column: 13 + column: 16 fix: ~ parent: ~ - kind: @@ -226,8 +331,15 @@ expression: diagnostics column: 0 end_location: row: 28 - column: 14 - fix: ~ + column: 17 + fix: + content: pth.Path(p).samefile() + location: + row: 28 + column: 0 + end_location: + row: 28 + column: 17 parent: ~ - kind: PathlibSplitext: ~ @@ -236,7 +348,7 @@ expression: diagnostics column: 0 end_location: row: 29 - column: 14 + column: 17 fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap index 16d86722760afc..756aeaefdf9147 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap @@ -9,8 +9,15 @@ expression: diagnostics column: 4 end_location: row: 9 - column: 11 - fix: ~ + column: 14 + fix: + content: Path(p).resolve() + location: + row: 9 + column: 4 + end_location: + row: 9 + column: 14 parent: ~ - kind: PathlibChmod: ~ @@ -19,8 +26,15 @@ expression: diagnostics column: 5 end_location: row: 10 - column: 10 - fix: ~ + column: 13 + fix: + content: Path(p).chmod() + location: + row: 10 + column: 5 + end_location: + row: 10 + column: 13 parent: ~ - kind: PathlibMkdir: ~ @@ -29,8 +43,15 @@ expression: diagnostics column: 6 end_location: row: 11 - column: 11 - fix: ~ + column: 14 + fix: + content: Path(p).mkdir() + location: + row: 11 + column: 6 + end_location: + row: 11 + column: 14 parent: ~ - kind: PathlibMakedirs: ~ @@ -39,7 +60,7 @@ expression: diagnostics column: 0 end_location: row: 12 - column: 8 + column: 11 fix: ~ parent: ~ - kind: @@ -49,8 +70,15 @@ expression: diagnostics column: 0 end_location: row: 13 - column: 6 - fix: ~ + column: 9 + fix: + content: Path(p).rename() + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 9 parent: ~ - kind: PathlibReplace: ~ @@ -59,8 +87,15 @@ expression: diagnostics column: 0 end_location: row: 14 - column: 7 - fix: ~ + column: 10 + fix: + content: Path(p).replace() + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 10 parent: ~ - kind: PathlibRmdir: ~ @@ -69,8 +104,15 @@ expression: diagnostics column: 0 end_location: row: 15 - column: 5 - fix: ~ + column: 8 + fix: + content: Path(p).rmdir() + location: + row: 15 + column: 0 + end_location: + row: 15 + column: 8 parent: ~ - kind: PathlibRemove: ~ @@ -79,8 +121,15 @@ expression: diagnostics column: 0 end_location: row: 16 - column: 6 - fix: ~ + column: 9 + fix: + content: Path(p).unlink() + location: + row: 16 + column: 0 + end_location: + row: 16 + column: 9 parent: ~ - kind: PathlibUnlink: ~ @@ -89,8 +138,15 @@ expression: diagnostics column: 0 end_location: row: 17 - column: 6 - fix: ~ + column: 9 + fix: + content: Path(p).unlink() + location: + row: 17 + column: 0 + end_location: + row: 17 + column: 9 parent: ~ - kind: PathlibGetcwd: ~ @@ -99,15 +155,15 @@ expression: diagnostics column: 0 end_location: row: 18 - column: 6 + column: 9 fix: - content: Path.cwd + content: Path(p).cwd() location: row: 18 column: 0 end_location: row: 18 - column: 6 + column: 9 parent: ~ - kind: PathlibExists: ~ @@ -116,8 +172,15 @@ expression: diagnostics column: 4 end_location: row: 19 - column: 10 - fix: ~ + column: 13 + fix: + content: Path(p).exists() + location: + row: 19 + column: 4 + end_location: + row: 19 + column: 13 parent: ~ - kind: PathlibExpanduser: ~ @@ -126,8 +189,15 @@ expression: diagnostics column: 5 end_location: row: 20 - column: 15 - fix: ~ + column: 18 + fix: + content: Path(p).expanduser() + location: + row: 20 + column: 5 + end_location: + row: 20 + column: 18 parent: ~ - kind: PathlibIsDir: ~ @@ -136,8 +206,15 @@ expression: diagnostics column: 6 end_location: row: 21 - column: 11 - fix: ~ + column: 14 + fix: + content: Path(p).is_dir() + location: + row: 21 + column: 6 + end_location: + row: 21 + column: 14 parent: ~ - kind: PathlibIsFile: ~ @@ -146,8 +223,15 @@ expression: diagnostics column: 7 end_location: row: 22 - column: 13 - fix: ~ + column: 16 + fix: + content: Path(p).is_file() + location: + row: 22 + column: 7 + end_location: + row: 22 + column: 16 parent: ~ - kind: PathlibIsLink: ~ @@ -156,8 +240,15 @@ expression: diagnostics column: 8 end_location: row: 23 - column: 14 - fix: ~ + column: 17 + fix: + content: Path(p).is_symlink() + location: + row: 23 + column: 8 + end_location: + row: 23 + column: 17 parent: ~ - kind: PathlibReadlink: ~ @@ -166,8 +257,15 @@ expression: diagnostics column: 0 end_location: row: 24 - column: 8 - fix: ~ + column: 11 + fix: + content: Path(p).readlink() + location: + row: 24 + column: 0 + end_location: + row: 24 + column: 11 parent: ~ - kind: PathlibStat: ~ @@ -176,7 +274,7 @@ expression: diagnostics column: 0 end_location: row: 25 - column: 4 + column: 7 fix: ~ parent: ~ - kind: @@ -186,8 +284,15 @@ expression: diagnostics column: 0 end_location: row: 26 - column: 5 - fix: ~ + column: 8 + fix: + content: Path(p).is_absolute() + location: + row: 26 + column: 0 + end_location: + row: 26 + column: 8 parent: ~ - kind: PathlibJoin: ~ @@ -196,7 +301,7 @@ expression: diagnostics column: 0 end_location: row: 27 - column: 4 + column: 7 fix: ~ parent: ~ - kind: @@ -206,7 +311,7 @@ expression: diagnostics column: 0 end_location: row: 28 - column: 8 + column: 11 fix: ~ parent: ~ - kind: @@ -216,7 +321,7 @@ expression: diagnostics column: 0 end_location: row: 29 - column: 7 + column: 10 fix: ~ parent: ~ - kind: @@ -226,8 +331,15 @@ expression: diagnostics column: 0 end_location: row: 30 - column: 8 - fix: ~ + column: 11 + fix: + content: Path(p).samefile() + location: + row: 30 + column: 0 + end_location: + row: 30 + column: 11 parent: ~ - kind: PathlibSplitext: ~ @@ -236,7 +348,7 @@ expression: diagnostics column: 0 end_location: row: 31 - column: 8 + column: 11 fix: ~ parent: ~ - kind: @@ -246,7 +358,7 @@ expression: diagnostics column: 5 end_location: row: 32 - column: 9 + column: 12 fix: ~ parent: ~ - kind: @@ -256,7 +368,7 @@ expression: diagnostics column: 0 end_location: row: 34 - column: 4 + column: 7 fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap index 0bdd522f746154..ecd6a2136ab43e 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap @@ -9,8 +9,15 @@ expression: diagnostics column: 4 end_location: row: 14 - column: 12 - fix: ~ + column: 15 + fix: + content: pth(p).resolve() + location: + row: 14 + column: 4 + end_location: + row: 14 + column: 15 parent: ~ - kind: PathlibChmod: ~ @@ -19,8 +26,15 @@ expression: diagnostics column: 5 end_location: row: 15 - column: 11 - fix: ~ + column: 14 + fix: + content: pth(p).chmod() + location: + row: 15 + column: 5 + end_location: + row: 15 + column: 14 parent: ~ - kind: PathlibMkdir: ~ @@ -29,8 +43,15 @@ expression: diagnostics column: 6 end_location: row: 16 - column: 12 - fix: ~ + column: 15 + fix: + content: pth(p).mkdir() + location: + row: 16 + column: 6 + end_location: + row: 16 + column: 15 parent: ~ - kind: PathlibMakedirs: ~ @@ -39,7 +60,7 @@ expression: diagnostics column: 0 end_location: row: 17 - column: 9 + column: 12 fix: ~ parent: ~ - kind: @@ -49,8 +70,15 @@ expression: diagnostics column: 0 end_location: row: 18 - column: 7 - fix: ~ + column: 10 + fix: + content: pth(p).rename() + location: + row: 18 + column: 0 + end_location: + row: 18 + column: 10 parent: ~ - kind: PathlibReplace: ~ @@ -59,8 +87,15 @@ expression: diagnostics column: 0 end_location: row: 19 - column: 8 - fix: ~ + column: 11 + fix: + content: pth(p).replace() + location: + row: 19 + column: 0 + end_location: + row: 19 + column: 11 parent: ~ - kind: PathlibRmdir: ~ @@ -69,8 +104,15 @@ expression: diagnostics column: 0 end_location: row: 20 - column: 6 - fix: ~ + column: 9 + fix: + content: pth(p).rmdir() + location: + row: 20 + column: 0 + end_location: + row: 20 + column: 9 parent: ~ - kind: PathlibRemove: ~ @@ -79,8 +121,15 @@ expression: diagnostics column: 0 end_location: row: 21 - column: 7 - fix: ~ + column: 10 + fix: + content: pth(p).unlink() + location: + row: 21 + column: 0 + end_location: + row: 21 + column: 10 parent: ~ - kind: PathlibUnlink: ~ @@ -89,8 +138,15 @@ expression: diagnostics column: 0 end_location: row: 22 - column: 7 - fix: ~ + column: 10 + fix: + content: pth(p).unlink() + location: + row: 22 + column: 0 + end_location: + row: 22 + column: 10 parent: ~ - kind: PathlibGetcwd: ~ @@ -99,15 +155,15 @@ expression: diagnostics column: 0 end_location: row: 23 - column: 7 + column: 10 fix: - content: pth.cwd + content: pth(p).cwd() location: row: 23 column: 0 end_location: row: 23 - column: 7 + column: 10 parent: ~ - kind: PathlibExists: ~ @@ -116,8 +172,15 @@ expression: diagnostics column: 4 end_location: row: 24 - column: 11 - fix: ~ + column: 14 + fix: + content: pth(p).exists() + location: + row: 24 + column: 4 + end_location: + row: 24 + column: 14 parent: ~ - kind: PathlibExpanduser: ~ @@ -126,8 +189,15 @@ expression: diagnostics column: 5 end_location: row: 25 - column: 16 - fix: ~ + column: 19 + fix: + content: pth(p).expanduser() + location: + row: 25 + column: 5 + end_location: + row: 25 + column: 19 parent: ~ - kind: PathlibIsDir: ~ @@ -136,8 +206,15 @@ expression: diagnostics column: 6 end_location: row: 26 - column: 12 - fix: ~ + column: 15 + fix: + content: pth(p).is_dir() + location: + row: 26 + column: 6 + end_location: + row: 26 + column: 15 parent: ~ - kind: PathlibIsFile: ~ @@ -146,8 +223,15 @@ expression: diagnostics column: 7 end_location: row: 27 - column: 14 - fix: ~ + column: 17 + fix: + content: pth(p).is_file() + location: + row: 27 + column: 7 + end_location: + row: 27 + column: 17 parent: ~ - kind: PathlibIsLink: ~ @@ -156,8 +240,15 @@ expression: diagnostics column: 8 end_location: row: 28 - column: 15 - fix: ~ + column: 18 + fix: + content: pth(p).is_symlink() + location: + row: 28 + column: 8 + end_location: + row: 28 + column: 18 parent: ~ - kind: PathlibReadlink: ~ @@ -166,8 +257,15 @@ expression: diagnostics column: 0 end_location: row: 29 - column: 9 - fix: ~ + column: 12 + fix: + content: pth(p).readlink() + location: + row: 29 + column: 0 + end_location: + row: 29 + column: 12 parent: ~ - kind: PathlibStat: ~ @@ -176,7 +274,7 @@ expression: diagnostics column: 0 end_location: row: 30 - column: 5 + column: 8 fix: ~ parent: ~ - kind: @@ -186,8 +284,15 @@ expression: diagnostics column: 0 end_location: row: 31 - column: 6 - fix: ~ + column: 9 + fix: + content: pth(p).is_absolute() + location: + row: 31 + column: 0 + end_location: + row: 31 + column: 9 parent: ~ - kind: PathlibJoin: ~ @@ -196,7 +301,7 @@ expression: diagnostics column: 0 end_location: row: 32 - column: 5 + column: 8 fix: ~ parent: ~ - kind: @@ -206,7 +311,7 @@ expression: diagnostics column: 0 end_location: row: 33 - column: 9 + column: 12 fix: ~ parent: ~ - kind: @@ -216,7 +321,7 @@ expression: diagnostics column: 0 end_location: row: 34 - column: 8 + column: 11 fix: ~ parent: ~ - kind: @@ -226,8 +331,15 @@ expression: diagnostics column: 0 end_location: row: 35 - column: 9 - fix: ~ + column: 12 + fix: + content: pth(p).samefile() + location: + row: 35 + column: 0 + end_location: + row: 35 + column: 12 parent: ~ - kind: PathlibSplitext: ~ @@ -236,7 +348,7 @@ expression: diagnostics column: 0 end_location: row: 36 - column: 9 + column: 12 fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__stat.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__stat.py.snap index 908476ff1bc511..3e57c48a13eb9c 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__stat.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__stat.py.snap @@ -9,7 +9,7 @@ expression: diagnostics column: 0 end_location: row: 4 - column: 16 + column: 28 fix: ~ parent: ~ - kind: @@ -19,7 +19,7 @@ expression: diagnostics column: 0 end_location: row: 5 - column: 16 + column: 29 fix: ~ parent: ~ - kind: @@ -29,7 +29,7 @@ expression: diagnostics column: 0 end_location: row: 6 - column: 16 + column: 34 fix: ~ parent: ~ - kind: @@ -39,7 +39,7 @@ expression: diagnostics column: 0 end_location: row: 8 - column: 16 + column: 28 fix: ~ parent: ~ - kind: @@ -49,7 +49,7 @@ expression: diagnostics column: 0 end_location: row: 9 - column: 16 + column: 29 fix: ~ parent: ~ - kind: @@ -59,7 +59,7 @@ expression: diagnostics column: 0 end_location: row: 10 - column: 16 + column: 34 fix: ~ parent: ~ - kind: @@ -69,7 +69,7 @@ expression: diagnostics column: 0 end_location: row: 12 - column: 16 + column: 28 fix: ~ parent: ~ - kind: @@ -79,7 +79,7 @@ expression: diagnostics column: 0 end_location: row: 13 - column: 16 + column: 29 fix: ~ parent: ~ - kind: @@ -89,7 +89,7 @@ expression: diagnostics column: 0 end_location: row: 14 - column: 16 + column: 34 fix: ~ parent: ~ - kind: @@ -99,7 +99,7 @@ expression: diagnostics column: 0 end_location: row: 16 - column: 15 + column: 27 fix: ~ parent: ~ - kind: @@ -109,7 +109,7 @@ expression: diagnostics column: 0 end_location: row: 17 - column: 15 + column: 28 fix: ~ parent: ~ - kind: @@ -119,7 +119,7 @@ expression: diagnostics column: 0 end_location: row: 18 - column: 15 + column: 33 fix: ~ parent: ~ - kind: @@ -129,7 +129,7 @@ expression: diagnostics column: 0 end_location: row: 19 - column: 15 + column: 25 fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__use_pathlib.py.snap b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__use_pathlib.py.snap index 5c13987c28ea2f..405d421abe7ab6 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__use_pathlib.py.snap +++ b/crates/ruff/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__use_pathlib.py.snap @@ -9,15 +9,8 @@ expression: diagnostics column: 9 end_location: row: 6 - column: 18 - fix: - content: Path.cwd - location: - row: 6 - column: 9 - end_location: - row: 6 - column: 18 + column: 20 + fix: ~ parent: ~ - kind: PathlibGetcwd: ~ @@ -26,15 +19,8 @@ expression: diagnostics column: 4 end_location: row: 10 - column: 14 - fix: - content: Path.cwd - location: - row: 9 - column: 4 - end_location: - row: 10 - column: 14 + column: 16 + fix: ~ parent: ~ - kind: PathlibGetcwd: ~ @@ -43,15 +29,8 @@ expression: diagnostics column: 4 end_location: row: 14 - column: 14 - fix: - content: Path.cwd - location: - row: 14 - column: 4 - end_location: - row: 14 - column: 14 + column: 16 + fix: ~ parent: ~ - kind: PathlibGetcwd: ~ @@ -60,14 +39,24 @@ expression: diagnostics column: 9 end_location: row: 18 - column: 18 + column: 20 + fix: ~ + parent: ~ +- kind: + PathlibAbspath: ~ + location: + row: 21 + column: 0 + end_location: + row: 21 + column: 33 fix: - content: Path.cwd + content: "Path(\"../../hello.py\").resolve()" location: - row: 18 - column: 9 + row: 21 + column: 0 end_location: - row: 18 - column: 18 + row: 21 + column: 33 parent: ~ diff --git a/crates/ruff/src/rules/flake8_use_pathlib/violations.rs b/crates/ruff/src/rules/flake8_use_pathlib/violations.rs index 42a8174a50a58f..891ce85e9e22db 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/violations.rs +++ b/crates/ruff/src/rules/flake8_use_pathlib/violations.rs @@ -7,10 +7,16 @@ define_violation!( pub struct PathlibAbspath; ); impl Violation for PathlibAbspath { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.path.abspath` should be replaced by `.resolve()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibAbspath| format!("Replace `os.path.abspath(x)` with `Path(x).resolve()`")) + } } // PTH101 @@ -18,10 +24,16 @@ define_violation!( pub struct PathlibChmod; ); impl Violation for PathlibChmod { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.chmod` should be replaced by `.chmod()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibChmod| format!("Replace `os.chmod(x, y)` with `Path(x).chmod(y)`")) + } } // PTH102 @@ -40,10 +52,16 @@ define_violation!( pub struct PathlibMkdir; ); impl Violation for PathlibMkdir { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.mkdir` should be replaced by `.mkdir()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibMkdir| format!("Replace `os.mkdir(x)` with `Path(x).mkdir()`")) + } } // PTH104 @@ -51,10 +69,16 @@ define_violation!( pub struct PathlibRename; ); impl Violation for PathlibRename { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.rename` should be replaced by `.rename()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibRename| format!("Replace `os.rename(x, y)` with `Path(x).rename(y)`")) + } } // PTH105 @@ -62,10 +86,16 @@ define_violation!( pub struct PathlibReplace; ); impl Violation for PathlibReplace { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.replace`should be replaced by `.replace()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibReplace| format!("Replace `os.replace(x, y)` with `Path(x).replace(y)`")) + } } // PTH106 @@ -73,10 +103,16 @@ define_violation!( pub struct PathlibRmdir; ); impl Violation for PathlibRmdir { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.rmdir` should be replaced by `.rmdir()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibRmdir| format!("Replace `os.rmdir(x)` with `Path(x).rmdir()`")) + } } // PTH107 @@ -84,10 +120,16 @@ define_violation!( pub struct PathlibRemove; ); impl Violation for PathlibRemove { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.remove` should be replaced by `.unlink()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibRemove| format!("Replace `os.remove(x)` with `Path(x).unlink()`")) + } } // PTH108 @@ -95,10 +137,16 @@ define_violation!( pub struct PathlibUnlink; ); impl Violation for PathlibUnlink { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.unlink` should be replaced by `.unlink()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibUnlink| format!("Replace `os.unlink(x)` with `Path(x).unlink()`")) + } } // PTH109 @@ -120,6 +168,9 @@ define_violation!( /// cwd = Path.cwd() /// ``` /// + /// ## Options + /// * `isort.required-imports` + /// /// ## References /// * [PEP 428](https://peps.python.org/pep-0428/) /// * [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module) @@ -146,10 +197,16 @@ define_violation!( pub struct PathlibExists; ); impl Violation for PathlibExists { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.path.exists` should be replaced by `.exists()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibExists| format!("Replace `os.path.exists(x)` with `Path(x).exists()`")) + } } // PTH111 @@ -157,10 +214,18 @@ define_violation!( pub struct PathlibExpanduser; ); impl Violation for PathlibExpanduser { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.path.expanduser` should be replaced by `.expanduser()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibExpanduser| { + format!("Replace `os.path.expanduser(x)` with `Path(x).expanduser()`") + }) + } } // PTH112 @@ -168,10 +233,16 @@ define_violation!( pub struct PathlibIsDir; ); impl Violation for PathlibIsDir { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.path.isdir` should be replaced by `.is_dir()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibIsDir| format!("Replace `os.path.isdir(x)` with `Path(x).is_dir()`")) + } } // PTH113 @@ -179,10 +250,16 @@ define_violation!( pub struct PathlibIsFile; ); impl Violation for PathlibIsFile { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.path.isfile` should be replaced by `.is_file()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibIsFile| format!("Replace `os.path.isfile(x)` with `Path(x).is_file()`")) + } } // PTH114 @@ -190,21 +267,60 @@ define_violation!( pub struct PathlibIsLink; ); impl Violation for PathlibIsLink { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.path.islink` should be replaced by `.is_symlink()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibIsLink| format!("Replace `os.path.islink(x)` with `Path(x).is_symlink()`")) + } } // PTH115 define_violation!( + /// ## What is does + /// Detects the use of `os.readlink`. + /// Autofix is available when the `pathlib` module is imported. + /// + /// ## Why is this bad? + /// A modern alternative to `os.readlink(x)` is the `Path(x).readlink()` function + /// + /// ## Examples + /// ```python + /// link = os.readlink(x) + /// ``` + /// + /// Use instead: + /// ```python + /// link = Path(x).readlink() + /// ``` + /// + /// ## Options + /// * `target-version` + /// * `isort.required-imports` + /// + /// ## References + /// * [PEP 428](https://peps.python.org/pep-0428/) + /// * [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module) + /// * [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/) + /// * [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/) + pub struct PathlibReadlink; ); impl Violation for PathlibReadlink { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.readlink` should be replaced by `.readlink()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibReadlink| format!("Replace `os.readlink(x)` with `Path(x).readlink()`")) + } } // PTH116 @@ -223,10 +339,16 @@ define_violation!( pub struct PathlibIsAbs; ); impl Violation for PathlibIsAbs { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.path.isabs` should be replaced by `.is_absolute()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibIsAbs| format!("Replace `os.path.isabs(x)` with `Path(x).is_absolute()`")) + } } // PTH118 @@ -267,10 +389,18 @@ define_violation!( pub struct PathlibSamefile; ); impl Violation for PathlibSamefile { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Sometimes)); + #[derive_message_formats] fn message(&self) -> String { format!("`os.path.samefile` should be replaced by `.samefile()`") } + + fn autofix_title_formatter(&self) -> Option String> { + Some(|PathlibSamefile| { + format!("Replace `os.path.samefile(x, y)` with `Path(x).samefile(y)`") + }) + } } // PTH122 @@ -306,7 +436,6 @@ impl Violation for PathlibPyPath { } } -// TODO: add documentation // PTH201 define_violation!( pub struct PathlibGetsize; diff --git a/docs/rules/pathlib-getcwd.md b/docs/rules/pathlib-getcwd.md index 37be84dd926b29..138202744e496c 100644 --- a/docs/rules/pathlib-getcwd.md +++ b/docs/rules/pathlib-getcwd.md @@ -21,6 +21,9 @@ Use instead: cwd = Path.cwd() ``` +## Options +* `isort.required-imports` + ## References * [PEP 428](https://peps.python.org/pep-0428/) * [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module) diff --git a/docs/rules/pathlib-readlink.md b/docs/rules/pathlib-readlink.md new file mode 100644 index 00000000000000..a978ed9145cfeb --- /dev/null +++ b/docs/rules/pathlib-readlink.md @@ -0,0 +1,32 @@ +# pathlib-readlink (PTH115) + +Derived from the **flake8-use-pathlib** linter. + +Autofix is sometimes available. + +## What is does +Detects the use of `os.readlink`. +Autofix is available when the `pathlib` module is imported. + +## Why is this bad? +A modern alternative to `os.readlink(x)` is the `Path(x).readlink()` function + +## Examples +```python +link = os.readlink(x) +``` + +Use instead: +```python +link = Path(x).readlink() +``` + +## Options +* `target-version` +* `isort.required-imports` + +## References +* [PEP 428](https://peps.python.org/pep-0428/) +* [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module) +* [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/) +* [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/) \ No newline at end of file diff --git a/resources/test/popmon b/resources/test/popmon new file mode 160000 index 00000000000000..0949e0fce92952 --- /dev/null +++ b/resources/test/popmon @@ -0,0 +1 @@ +Subproject commit 0949e0fce929528413ceeb29484db94d9a237d69