From 3a0ad1035a44d31801e381943fb5260e86636cb0 Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Sat, 11 Feb 2023 12:05:18 +0100 Subject: [PATCH] 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 | 6 +- crates/ruff/src/registry.rs | 5 + .../src/rules/flake8_use_pathlib/fixes.rs | 70 +++++ .../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 | 52 ++++ ...ake8_use_pathlib__tests__full_name.py.snap | 106 +++---- ...flake8_use_pathlib__tests__guarded.py.snap | 265 ++++++++++++++++++ ...ake8_use_pathlib__tests__import_as.py.snap | 94 +++---- ...e8_use_pathlib__tests__import_from.py.snap | 102 +++---- ...use_pathlib__tests__import_from_as.py.snap | 94 +++---- ...ests__simplify_pathlib_constructor.py.snap | 6 + ...s__flake8_use_pathlib__tests__stat.py.snap | 135 +++++++++ ...e8_use_pathlib__tests__use_pathlib.py.snap | 43 ++- .../rules/flake8_use_pathlib/violations.rs | 78 +++++- .../pylint/rules/consider_using_sys_exit.rs | 59 +--- docs/rules/pathlib-getcwd.md | 28 ++ ruff.schema.json | 7 + 29 files changed, 1071 insertions(+), 269 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/pathlib-getcwd.md diff --git a/README.md b/README.md index e65529b44c9cc8..3fe7a6c990671b 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 | simplify-path-constructor | 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..4f3c31a957acef 100644 --- a/crates/ruff/src/checkers/ast.rs +++ b/crates/ruff/src/checkers/ast.rs @@ -2756,7 +2756,11 @@ 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), + ); } // flake8-logging-format diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 0dc874533a92c7..80024fb85e96dc 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::SimplifyPathConstructor, + 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..3719d8b107aecf --- /dev/null +++ b/crates/ruff/src/rules/flake8_use_pathlib/fixes.rs @@ -0,0 +1,70 @@ +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, + expr: &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") { + if let ExprKind::Call { func, .. } = &expr.node { + 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, expr.location), + helpers::to_relative(fixme.end_location, expr.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 + } +} 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..c7d18f8f10e98c 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::SimplifyPathConstructor, ]), )?; 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..80b3342e047df6 --- /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, SimplifyPathConstructor}; + +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..8c1719453a43d7 --- /dev/null +++ b/crates/ruff/src/rules/flake8_use_pathlib/rules/simplify_path_constructor.rs @@ -0,0 +1,52 @@ +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!( + pub struct SimplifyPathConstructor; +); +impl Violation for SimplifyPathConstructor { + 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(|SimplifyPathConstructor| format!("Replace `Path(\".\")` with `Path()`")) + } +} + +/// PTH200 +pub fn simplify_path_constructor(checker: &mut Checker, expr: &Expr) { + if checker.resolve_call_path(expr).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(SimplifyPathConstructor, 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..8bdb59baf131a9 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,264 +1,264 @@ --- -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: ~ 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: ~ 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..9c9362cc24dbb5 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,234 @@ --- -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: ~ 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..db124772d8248f 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,254 @@ --- -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: ~ 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..9876da49afbf2e 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,234 @@ --- -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: ~ 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..fe1cfd80943d4b --- /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,6 @@ +--- +source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs +expression: diagnostics +--- +[] + 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..c06f6f651f66b4 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,45 @@ --- -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: ~ + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 9 + column: 4 + end_location: + row: 10 + column: 14 + fix: ~ + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 14 + column: 4 + end_location: + row: 14 + column: 14 + fix: ~ + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 18 + column: 9 + end_location: + row: 18 + column: 18 + fix: ~ + 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/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",