From c85a6df66b2b9317278ed7e8cd966e86b1bee979 Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Sun, 22 Jan 2023 19:43:58 +0100 Subject: [PATCH 1/6] feat: flake8-use-pathlib PTH100-122 See: https://github.com/charliermarsh/ruff/issues/2060 --- README.md | 31 +++ licenses/LICENSE_RoPP_flake8-use-pathlib | 22 ++ .../fixtures/flake8_use_pathlib/full_name.py | 28 ++ .../fixtures/flake8_use_pathlib/import_as.py | 28 ++ .../flake8_use_pathlib/import_from.py | 30 ++ .../flake8_use_pathlib/import_from_as.py | 35 +++ ruff.schema.json | 28 ++ src/checkers/ast.rs | 31 ++- src/registry.rs | 26 ++ src/rules/flake8_use_pathlib/helpers.rs | 238 ++++++++++++++++ src/rules/flake8_use_pathlib/mod.rs | 55 ++++ ...ake8_use_pathlib__tests__full_name.py.snap | 235 ++++++++++++++++ ...ake8_use_pathlib__tests__import_as.py.snap | 235 ++++++++++++++++ ...e8_use_pathlib__tests__import_from.py.snap | 235 ++++++++++++++++ ...use_pathlib__tests__import_from_as.py.snap | 235 ++++++++++++++++ src/rules/flake8_use_pathlib/violations.rs | 257 ++++++++++++++++++ src/rules/mod.rs | 1 + 17 files changed, 1748 insertions(+), 2 deletions(-) create mode 100644 licenses/LICENSE_RoPP_flake8-use-pathlib create mode 100644 resources/test/fixtures/flake8_use_pathlib/full_name.py create mode 100644 resources/test/fixtures/flake8_use_pathlib/import_as.py create mode 100644 resources/test/fixtures/flake8_use_pathlib/import_from.py create mode 100644 resources/test/fixtures/flake8_use_pathlib/import_from_as.py create mode 100644 src/rules/flake8_use_pathlib/helpers.rs create mode 100644 src/rules/flake8_use_pathlib/mod.rs create mode 100644 src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap create mode 100644 src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap create mode 100644 src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap create mode 100644 src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap create mode 100644 src/rules/flake8_use_pathlib/violations.rs diff --git a/README.md b/README.md index 7d2093536aaa6..d771b5cd8c4c1 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ developer of [Zulip](https://github.com/zulip/zulip): 1. [flake8-executable (EXE)](#flake8-executable-exe) 1. [flake8-type-checking (TYP)](#flake8-type-checking-typ) 1. [tryceratops (TRY)](#tryceratops-try) + 1. [flake8-use-pathlib (PTH)](#flake8-use-pathlib-pth) 1. [Ruff-specific rules (RUF)](#ruff-specific-rules-ruf) 1. [Editor Integrations](#editor-integrations) 1. [FAQ](#faq) @@ -1197,6 +1198,36 @@ For more, see [tryceratops](https://pypi.org/project/tryceratops/1.1.0/) on PyPI | TRY004 | prefer-type-error | Prefer `TypeError` exception for invalid type | 🛠 | | TRY300 | try-consider-else | Consider `else` block | | +### flake8-use-pathlib (PTH) + +For more, see [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) on PyPI. + +| 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()` | | +| 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()` | | +| PTH109 | pathlib-getcwd | `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()` | | +| 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" | | +| 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` | | + ### Ruff-specific rules (RUF) | Code | Name | Message | Fix | diff --git a/licenses/LICENSE_RoPP_flake8-use-pathlib b/licenses/LICENSE_RoPP_flake8-use-pathlib new file mode 100644 index 0000000000000..d0acd6e7cb6f7 --- /dev/null +++ b/licenses/LICENSE_RoPP_flake8-use-pathlib @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Rodolphe Pelloux-Prayer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/resources/test/fixtures/flake8_use_pathlib/full_name.py b/resources/test/fixtures/flake8_use_pathlib/full_name.py new file mode 100644 index 0000000000000..6af31a02d22aa --- /dev/null +++ b/resources/test/fixtures/flake8_use_pathlib/full_name.py @@ -0,0 +1,28 @@ +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) diff --git a/resources/test/fixtures/flake8_use_pathlib/import_as.py b/resources/test/fixtures/flake8_use_pathlib/import_as.py new file mode 100644 index 0000000000000..446412f18de67 --- /dev/null +++ b/resources/test/fixtures/flake8_use_pathlib/import_as.py @@ -0,0 +1,28 @@ +import os as foo +import os.path as foo_p + +p = "/foo" + +a = foo_p.abspath(p) +aa = foo.chmod(p) +aaa = foo.mkdir(p) +foo.makedirs(p) +foo.rename(p) +foo.replace(p) +foo.rmdir(p) +foo.remove(p) +foo.unlink(p) +foo.getcwd(p) +b = foo_p.exists(p) +bb = foo_p.expanduser(p) +bbb = foo_p.isdir(p) +bbbb = foo_p.isfile(p) +bbbbb = foo_p.islink(p) +foo.readlink(p) +foo.stat(p) +foo_p.isabs(p) +foo_p.join(p) +foo_p.basename(p) +foo_p.dirname(p) +foo_p.samefile(p) +foo_p.splitext(p) diff --git a/resources/test/fixtures/flake8_use_pathlib/import_from.py b/resources/test/fixtures/flake8_use_pathlib/import_from.py new file mode 100644 index 0000000000000..3a38a6e93812d --- /dev/null +++ b/resources/test/fixtures/flake8_use_pathlib/import_from.py @@ -0,0 +1,30 @@ +from os import chmod, mkdir, makedirs, rename, replace, rmdir +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 + +p = "/foo" + +a = abspath(p) +aa = chmod(p) +aaa = mkdir(p) +makedirs(p) +rename(p) +replace(p) +rmdir(p) +remove(p) +unlink(p) +getcwd(p) +b = exists(p) +bb = expanduser(p) +bbb = isdir(p) +bbbb = isfile(p) +bbbbb = islink(p) +readlink(p) +stat(p) +isabs(p) +join(p) +basename(p) +dirname(p) +samefile(p) +splitext(p) diff --git a/resources/test/fixtures/flake8_use_pathlib/import_from_as.py b/resources/test/fixtures/flake8_use_pathlib/import_from_as.py new file mode 100644 index 0000000000000..2beff6d7e7bb5 --- /dev/null +++ b/resources/test/fixtures/flake8_use_pathlib/import_from_as.py @@ -0,0 +1,35 @@ +from os import chmod as xchmod, mkdir as xmkdir +from os import makedirs as xmakedirs, rename as xrename, replace as xreplace +from os import rmdir as xrmdir, remove as xremove, unlink as xunlink +from os import getcwd as xgetcwd, readlink as xreadlink, stat as xstat +from os.path import abspath as xabspath, exists as xexists +from os.path import expanduser as xexpanduser, isdir as xisdir +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 + +p = "/foo" + +a = xabspath(p) +aa = xchmod(p) +aaa = xmkdir(p) +xmakedirs(p) +xrename(p) +xreplace(p) +xrmdir(p) +xremove(p) +xunlink(p) +xgetcwd(p) +b = xexists(p) +bb = xexpanduser(p) +bbb = xisdir(p) +bbbb = xisfile(p) +bbbbb = xislink(p) +xreadlink(p) +xstat(p) +xisabs(p) +xjoin(p) +xbasename(p) +xdirname(p) +xsamefile(p) +xsplitext(p) diff --git a/ruff.schema.json b/ruff.schema.json index 1883aa5b75e20..5032ab4368f21 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1633,6 +1633,34 @@ "PT024", "PT025", "PT026", + "PTH", + "PTH1", + "PTH10", + "PTH100", + "PTH101", + "PTH102", + "PTH103", + "PTH104", + "PTH105", + "PTH106", + "PTH107", + "PTH108", + "PTH109", + "PTH11", + "PTH110", + "PTH111", + "PTH112", + "PTH113", + "PTH114", + "PTH115", + "PTH116", + "PTH117", + "PTH118", + "PTH119", + "PTH12", + "PTH120", + "PTH121", + "PTH122", "Q", "Q0", "Q00", diff --git a/src/checkers/ast.rs b/src/checkers/ast.rs index dcd40a2901a8a..3a515010b6a8a 100644 --- a/src/checkers/ast.rs +++ b/src/checkers/ast.rs @@ -36,8 +36,8 @@ use crate::rules::{ flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_pie, flake8_print, flake8_pytest_style, flake8_return, flake8_simplify, flake8_tidy_imports, flake8_type_checking, - flake8_unused_arguments, mccabe, pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes, - pygrep_hooks, pylint, pyupgrade, ruff, tryceratops, + flake8_unused_arguments, flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle, + pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops, }; use crate::settings::types::PythonVersion; use crate::settings::{flags, Settings}; @@ -2545,6 +2545,33 @@ where { flake8_simplify::rules::open_file_with_context_handler(self, func); } + + // flake8-use-pathlib + if self.settings.rules.enabled(&Rule::PathlibAbspath) + || self.settings.rules.enabled(&Rule::PathlibChmod) + || self.settings.rules.enabled(&Rule::PathlibMkdir) + || self.settings.rules.enabled(&Rule::PathlibMakedirs) + || self.settings.rules.enabled(&Rule::PathlibRename) + || self.settings.rules.enabled(&Rule::PathlibReplace) + || self.settings.rules.enabled(&Rule::PathlibRmdir) + || self.settings.rules.enabled(&Rule::PathlibRemove) + || self.settings.rules.enabled(&Rule::PathlibUnlink) + || self.settings.rules.enabled(&Rule::PathlibGetcwd) + || self.settings.rules.enabled(&Rule::PathlibExists) + || self.settings.rules.enabled(&Rule::PathlibExpanduser) + || self.settings.rules.enabled(&Rule::PathlibIsDir) + || self.settings.rules.enabled(&Rule::PathlibIsFile) + || self.settings.rules.enabled(&Rule::PathlibIsLink) + || self.settings.rules.enabled(&Rule::PathlibReadlink) + || self.settings.rules.enabled(&Rule::PathlibStat) + || self.settings.rules.enabled(&Rule::PathlibIsAbs) + || self.settings.rules.enabled(&Rule::PathlibJoin) + || self.settings.rules.enabled(&Rule::PathlibBasename) + || self.settings.rules.enabled(&Rule::PathlibSamefile) + || self.settings.rules.enabled(&Rule::PathlibSplitext) + { + flake8_use_pathlib::helpers::replaceable_by_pathlib(self, func); + } } ExprKind::Dict { keys, values } => { if self diff --git a/src/registry.rs b/src/registry.rs index cc76d4c502f70..0bc562e0b8235 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -429,6 +429,30 @@ ruff_macros::define_rule_mapping!( // tryceratops TRY004 => rules::tryceratops::rules::PreferTypeError, TRY300 => rules::tryceratops::rules::TryConsiderElse, + // flake8-use-pathlib + PTH100 => rules::flake8_use_pathlib::violations::PathlibAbspath, + PTH101 => rules::flake8_use_pathlib::violations::PathlibChmod, + PTH102 => rules::flake8_use_pathlib::violations::PathlibMkdir, + PTH103 => rules::flake8_use_pathlib::violations::PathlibMakedirs, + PTH104 => rules::flake8_use_pathlib::violations::PathlibRename, + PTH105 => rules::flake8_use_pathlib::violations::PathlibReplace, + PTH106 => rules::flake8_use_pathlib::violations::PathlibRmdir, + PTH107 => rules::flake8_use_pathlib::violations::PathlibRemove, + PTH108 => rules::flake8_use_pathlib::violations::PathlibUnlink, + PTH109 => rules::flake8_use_pathlib::violations::PathlibGetcwd, + PTH110 => rules::flake8_use_pathlib::violations::PathlibExists, + PTH111 => rules::flake8_use_pathlib::violations::PathlibExpanduser, + PTH112 => rules::flake8_use_pathlib::violations::PathlibIsDir, + PTH113 => rules::flake8_use_pathlib::violations::PathlibIsFile, + PTH114 => rules::flake8_use_pathlib::violations::PathlibIsLink, + PTH115 => rules::flake8_use_pathlib::violations::PathlibReadlink, + PTH116 => rules::flake8_use_pathlib::violations::PathlibStat, + PTH117 => rules::flake8_use_pathlib::violations::PathlibIsAbs, + PTH118 => rules::flake8_use_pathlib::violations::PathlibJoin, + PTH119 => rules::flake8_use_pathlib::violations::PathlibBasename, + PTH120 => rules::flake8_use_pathlib::violations::PathlibDirname, + PTH121 => rules::flake8_use_pathlib::violations::PathlibSamefile, + PTH122 => rules::flake8_use_pathlib::violations::PathlibSplitext, // ruff RUF001 => violations::AmbiguousUnicodeCharacterString, RUF002 => violations::AmbiguousUnicodeCharacterDocstring, @@ -515,6 +539,8 @@ pub enum Linter { Flake8TypeChecking, #[prefix = "TRY"] Tryceratops, + #[prefix = "PTH"] + Flake8UsePathlib, #[prefix = "RUF"] Ruff, } diff --git a/src/rules/flake8_use_pathlib/helpers.rs b/src/rules/flake8_use_pathlib/helpers.rs new file mode 100644 index 0000000000000..784ed806b0b60 --- /dev/null +++ b/src/rules/flake8_use_pathlib/helpers.rs @@ -0,0 +1,238 @@ +use rustpython_ast::Expr; + +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::registry::{Diagnostic, DiagnosticKind, Rule}; +use crate::rules::flake8_use_pathlib::violations::{ + PathlibAbspath, PathlibBasename, PathlibChmod, PathlibDirname, PathlibExists, + PathlibExpanduser, PathlibGetcwd, PathlibIsAbs, PathlibIsDir, PathlibIsFile, PathlibIsLink, + PathlibJoin, PathlibMakedirs, PathlibMkdir, PathlibReadlink, PathlibRemove, PathlibRename, + PathlibReplace, PathlibRmdir, PathlibSamefile, PathlibSplitext, PathlibStat, PathlibUnlink, +}; + +enum OsCall { + Abspath, + Chmod, + Mkdir, + Makedirs, + Rename, + Replace, + Rmdir, + Remove, + Unlink, + Getcwd, + Exists, + Expanduser, + IsDir, + IsFile, + IsLink, + Readlink, + Stat, + IsAbs, + Join, + Basename, + Dirname, + Samefile, + Splitext, +} + +pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { + if let Some(os_call) = + checker + .resolve_call_path(expr) + .and_then(|call_path| match call_path.as_slice() { + ["os", "path", "abspath"] => { + if checker.settings.rules.enabled(&Rule::PathlibAbspath) { + Some(OsCall::Abspath) + } else { + None + } + } + ["os", "chmod"] => { + if checker.settings.rules.enabled(&Rule::PathlibChmod) { + Some(OsCall::Chmod) + } else { + None + } + } + ["os", "mkdir"] => { + if checker.settings.rules.enabled(&Rule::PathlibMkdir) { + Some(OsCall::Mkdir) + } else { + None + } + } + ["os", "makedirs"] => { + if checker.settings.rules.enabled(&Rule::PathlibMakedirs) { + Some(OsCall::Makedirs) + } else { + None + } + } + ["os", "rename"] => { + if checker.settings.rules.enabled(&Rule::PathlibRename) { + Some(OsCall::Rename) + } else { + None + } + } + ["os", "replace"] => { + if checker.settings.rules.enabled(&Rule::PathlibReplace) { + Some(OsCall::Replace) + } else { + None + } + } + ["os", "rmdir"] => { + if checker.settings.rules.enabled(&Rule::PathlibRmdir) { + Some(OsCall::Rmdir) + } else { + None + } + } + ["os", "remove"] => { + if checker.settings.rules.enabled(&Rule::PathlibRemove) { + Some(OsCall::Remove) + } else { + None + } + } + ["os", "unlink"] => { + if checker.settings.rules.enabled(&Rule::PathlibUnlink) { + Some(OsCall::Unlink) + } else { + None + } + } + ["os", "getcwd"] => { + if checker.settings.rules.enabled(&Rule::PathlibGetcwd) { + Some(OsCall::Getcwd) + } else { + None + } + } + ["os", "path", "exists"] => { + if checker.settings.rules.enabled(&Rule::PathlibExists) { + Some(OsCall::Exists) + } else { + None + } + } + ["os", "path", "expanduser"] => { + if checker.settings.rules.enabled(&Rule::PathlibExpanduser) { + Some(OsCall::Expanduser) + } else { + None + } + } + ["os", "path", "isdir"] => { + if checker.settings.rules.enabled(&Rule::PathlibIsDir) { + Some(OsCall::IsDir) + } else { + None + } + } + ["os", "path", "isfile"] => { + if checker.settings.rules.enabled(&Rule::PathlibIsFile) { + Some(OsCall::IsFile) + } else { + None + } + } + ["os", "path", "islink"] => { + if checker.settings.rules.enabled(&Rule::PathlibIsLink) { + Some(OsCall::IsLink) + } else { + None + } + } + ["os", "readlink"] => { + if checker.settings.rules.enabled(&Rule::PathlibReadlink) { + Some(OsCall::Readlink) + } else { + None + } + } + ["os", "stat"] => { + if checker.settings.rules.enabled(&Rule::PathlibStat) { + Some(OsCall::Stat) + } else { + None + } + } + ["os", "path", "isabs"] => { + if checker.settings.rules.enabled(&Rule::PathlibIsAbs) { + Some(OsCall::IsAbs) + } else { + None + } + } + ["os", "path", "join"] => { + if checker.settings.rules.enabled(&Rule::PathlibJoin) { + Some(OsCall::Join) + } else { + None + } + } + ["os", "path", "basename"] => { + if checker.settings.rules.enabled(&Rule::PathlibBasename) { + Some(OsCall::Basename) + } else { + None + } + } + ["os", "path", "dirname"] => { + if checker.settings.rules.enabled(&Rule::PathlibDirname) { + Some(OsCall::Dirname) + } else { + None + } + } + ["os", "path", "samefile"] => { + if checker.settings.rules.enabled(&Rule::PathlibSamefile) { + Some(OsCall::Samefile) + } else { + None + } + } + ["os", "path", "splitext"] => { + if checker.settings.rules.enabled(&Rule::PathlibSplitext) { + Some(OsCall::Splitext) + } else { + None + } + } + _ => None, + }) + { + let diagnostic = Diagnostic::new::( + match os_call { + OsCall::Abspath => PathlibAbspath.into(), + OsCall::Chmod => PathlibChmod.into(), + OsCall::Mkdir => PathlibMkdir.into(), + OsCall::Makedirs => PathlibMakedirs.into(), + OsCall::Rename => PathlibRename.into(), + OsCall::Replace => PathlibReplace.into(), + OsCall::Rmdir => PathlibRmdir.into(), + OsCall::Remove => PathlibRemove.into(), + OsCall::Unlink => PathlibUnlink.into(), + OsCall::Getcwd => PathlibGetcwd.into(), + OsCall::Exists => PathlibExists.into(), + OsCall::Expanduser => PathlibExpanduser.into(), + OsCall::IsDir => PathlibIsDir.into(), + OsCall::IsFile => PathlibIsFile.into(), + OsCall::IsLink => PathlibIsLink.into(), + OsCall::Readlink => PathlibReadlink.into(), + OsCall::Stat => PathlibStat.into(), + OsCall::IsAbs => PathlibIsAbs.into(), + OsCall::Join => PathlibJoin.into(), + OsCall::Basename => PathlibBasename.into(), + OsCall::Dirname => PathlibDirname.into(), + OsCall::Samefile => PathlibSamefile.into(), + OsCall::Splitext => PathlibSplitext.into(), + }, + Range::from_located(expr), + ); + checker.diagnostics.push(diagnostic); + } +} diff --git a/src/rules/flake8_use_pathlib/mod.rs b/src/rules/flake8_use_pathlib/mod.rs new file mode 100644 index 0000000000000..b065fd2b6d833 --- /dev/null +++ b/src/rules/flake8_use_pathlib/mod.rs @@ -0,0 +1,55 @@ +//! Rules from [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/). +pub(crate) mod helpers; +pub(crate) mod violations; + +#[cfg(test)] +mod tests { + use std::path::Path; + + use anyhow::Result; + use test_case::test_case; + + use crate::linter::test_path; + use crate::registry::Rule; + use crate::settings; + + #[test_case(Path::new("full_name.py"); "PTH1_1")] + #[test_case(Path::new("import_as.py"); "PTH1_2")] + #[test_case(Path::new("import_from_as.py"); "PTH1_3")] + #[test_case(Path::new("import_from.py"); "PTH1_4")] + fn rules(path: &Path) -> Result<()> { + let snapshot = format!("{}", path.to_string_lossy()); + let diagnostics = test_path( + Path::new("./resources/test/fixtures/flake8_use_pathlib") + .join(path) + .as_path(), + &settings::Settings::for_rules(vec![ + Rule::PathlibAbspath, + Rule::PathlibChmod, + Rule::PathlibMkdir, + Rule::PathlibMakedirs, + Rule::PathlibRename, + Rule::PathlibReplace, + Rule::PathlibRmdir, + Rule::PathlibRemove, + Rule::PathlibUnlink, + Rule::PathlibGetcwd, + Rule::PathlibExists, + Rule::PathlibExpanduser, + Rule::PathlibIsDir, + Rule::PathlibIsFile, + Rule::PathlibIsLink, + Rule::PathlibReadlink, + Rule::PathlibStat, + Rule::PathlibIsAbs, + Rule::PathlibJoin, + Rule::PathlibBasename, + Rule::PathlibDirname, + Rule::PathlibSamefile, + Rule::PathlibSplitext, + ]), + )?; + insta::assert_yaml_snapshot!(snapshot, diagnostics); + Ok(()) + } +} diff --git a/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap new file mode 100644 index 0000000000000..1691e4d752c1a --- /dev/null +++ b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap @@ -0,0 +1,235 @@ +--- +source: src/rules/flake8_use_pathlib/mod.rs +expression: diagnostics +--- +- kind: + PathlibAbspath: ~ + location: + row: 6 + column: 4 + end_location: + row: 6 + column: 19 + fix: ~ + parent: ~ +- kind: + PathlibChmod: ~ + location: + row: 7 + column: 5 + end_location: + row: 7 + column: 13 + fix: ~ + parent: ~ +- kind: + PathlibMkdir: ~ + location: + row: 8 + column: 6 + end_location: + row: 8 + column: 14 + fix: ~ + parent: ~ +- kind: + PathlibMakedirs: ~ + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 11 + fix: ~ + parent: ~ +- kind: + PathlibRename: ~ + location: + row: 10 + column: 0 + end_location: + row: 10 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibReplace: ~ + location: + row: 11 + column: 0 + end_location: + row: 11 + column: 10 + fix: ~ + parent: ~ +- kind: + PathlibRmdir: ~ + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 8 + fix: ~ + parent: ~ +- kind: + PathlibRemove: ~ + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibUnlink: ~ + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 15 + column: 0 + end_location: + row: 15 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibExists: ~ + location: + row: 16 + column: 4 + end_location: + row: 16 + column: 18 + fix: ~ + parent: ~ +- kind: + PathlibExpanduser: ~ + location: + row: 17 + column: 5 + end_location: + row: 17 + column: 23 + fix: ~ + parent: ~ +- kind: + PathlibIsDir: ~ + location: + row: 18 + column: 6 + end_location: + row: 18 + column: 19 + fix: ~ + parent: ~ +- kind: + PathlibIsFile: ~ + location: + row: 19 + column: 7 + end_location: + row: 19 + column: 21 + fix: ~ + parent: ~ +- kind: + PathlibIsLink: ~ + location: + row: 20 + column: 8 + end_location: + row: 20 + column: 22 + fix: ~ + parent: ~ +- kind: + PathlibReadlink: ~ + location: + row: 21 + column: 0 + end_location: + row: 21 + column: 11 + fix: ~ + parent: ~ +- kind: + PathlibStat: ~ + location: + row: 22 + column: 0 + end_location: + row: 22 + column: 7 + fix: ~ + parent: ~ +- kind: + PathlibIsAbs: ~ + location: + row: 23 + column: 0 + end_location: + row: 23 + column: 13 + fix: ~ + parent: ~ +- kind: + PathlibJoin: ~ + location: + row: 24 + column: 0 + end_location: + row: 24 + column: 12 + fix: ~ + parent: ~ +- kind: + PathlibBasename: ~ + location: + row: 25 + column: 0 + end_location: + row: 25 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibDirname: ~ + location: + row: 26 + column: 0 + end_location: + row: 26 + column: 15 + fix: ~ + parent: ~ +- kind: + PathlibSamefile: ~ + location: + row: 27 + column: 0 + end_location: + row: 27 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibSplitext: ~ + location: + row: 28 + column: 0 + end_location: + row: 28 + column: 16 + fix: ~ + parent: ~ + diff --git a/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap new file mode 100644 index 0000000000000..956e8e62b6b9f --- /dev/null +++ b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_as.py.snap @@ -0,0 +1,235 @@ +--- +source: src/rules/flake8_use_pathlib/mod.rs +expression: diagnostics +--- +- kind: + PathlibAbspath: ~ + location: + row: 6 + column: 4 + end_location: + row: 6 + column: 17 + fix: ~ + parent: ~ +- kind: + PathlibChmod: ~ + location: + row: 7 + column: 5 + end_location: + row: 7 + column: 14 + fix: ~ + parent: ~ +- kind: + PathlibMkdir: ~ + location: + row: 8 + column: 6 + end_location: + row: 8 + column: 15 + fix: ~ + parent: ~ +- kind: + PathlibMakedirs: ~ + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 12 + fix: ~ + parent: ~ +- kind: + PathlibRename: ~ + location: + row: 10 + column: 0 + end_location: + row: 10 + column: 10 + fix: ~ + parent: ~ +- kind: + PathlibReplace: ~ + location: + row: 11 + column: 0 + end_location: + row: 11 + column: 11 + fix: ~ + parent: ~ +- kind: + PathlibRmdir: ~ + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibRemove: ~ + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 10 + fix: ~ + parent: ~ +- kind: + PathlibUnlink: ~ + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 10 + fix: ~ + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 15 + column: 0 + end_location: + row: 15 + column: 10 + fix: ~ + parent: ~ +- kind: + PathlibExists: ~ + location: + row: 16 + column: 4 + end_location: + row: 16 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibExpanduser: ~ + location: + row: 17 + column: 5 + end_location: + row: 17 + column: 21 + fix: ~ + parent: ~ +- kind: + PathlibIsDir: ~ + location: + row: 18 + column: 6 + end_location: + row: 18 + column: 17 + fix: ~ + parent: ~ +- kind: + PathlibIsFile: ~ + location: + row: 19 + column: 7 + end_location: + row: 19 + column: 19 + fix: ~ + parent: ~ +- kind: + PathlibIsLink: ~ + location: + row: 20 + column: 8 + end_location: + row: 20 + column: 20 + fix: ~ + parent: ~ +- kind: + PathlibReadlink: ~ + location: + row: 21 + column: 0 + end_location: + row: 21 + column: 12 + fix: ~ + parent: ~ +- kind: + PathlibStat: ~ + location: + row: 22 + column: 0 + end_location: + row: 22 + column: 8 + fix: ~ + parent: ~ +- kind: + PathlibIsAbs: ~ + location: + row: 23 + column: 0 + end_location: + row: 23 + column: 11 + fix: ~ + parent: ~ +- kind: + PathlibJoin: ~ + location: + row: 24 + column: 0 + end_location: + row: 24 + column: 10 + fix: ~ + parent: ~ +- kind: + PathlibBasename: ~ + location: + row: 25 + column: 0 + end_location: + row: 25 + column: 14 + fix: ~ + parent: ~ +- kind: + PathlibDirname: ~ + location: + row: 26 + column: 0 + end_location: + row: 26 + column: 13 + fix: ~ + parent: ~ +- kind: + PathlibSamefile: ~ + location: + row: 27 + column: 0 + end_location: + row: 27 + column: 14 + fix: ~ + parent: ~ +- kind: + PathlibSplitext: ~ + location: + row: 28 + column: 0 + end_location: + row: 28 + column: 14 + fix: ~ + parent: ~ + diff --git a/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap new file mode 100644 index 0000000000000..3eb1d2c4ccf1b --- /dev/null +++ b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap @@ -0,0 +1,235 @@ +--- +source: src/rules/flake8_use_pathlib/mod.rs +expression: diagnostics +--- +- kind: + PathlibAbspath: ~ + location: + row: 8 + column: 4 + end_location: + row: 8 + column: 11 + fix: ~ + parent: ~ +- kind: + PathlibChmod: ~ + location: + row: 9 + column: 5 + end_location: + row: 9 + column: 10 + fix: ~ + parent: ~ +- kind: + PathlibMkdir: ~ + location: + row: 10 + column: 6 + end_location: + row: 10 + column: 11 + fix: ~ + parent: ~ +- kind: + PathlibMakedirs: ~ + location: + row: 11 + column: 0 + end_location: + row: 11 + column: 8 + fix: ~ + parent: ~ +- kind: + PathlibRename: ~ + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 6 + fix: ~ + parent: ~ +- kind: + PathlibReplace: ~ + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 7 + fix: ~ + parent: ~ +- kind: + PathlibRmdir: ~ + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 5 + fix: ~ + parent: ~ +- kind: + PathlibRemove: ~ + location: + row: 15 + column: 0 + end_location: + row: 15 + column: 6 + fix: ~ + parent: ~ +- kind: + PathlibUnlink: ~ + location: + row: 16 + column: 0 + end_location: + row: 16 + column: 6 + fix: ~ + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 17 + column: 0 + end_location: + row: 17 + column: 6 + fix: ~ + parent: ~ +- kind: + PathlibExists: ~ + location: + row: 18 + column: 4 + end_location: + row: 18 + column: 10 + fix: ~ + parent: ~ +- kind: + PathlibExpanduser: ~ + location: + row: 19 + column: 5 + end_location: + row: 19 + column: 15 + fix: ~ + parent: ~ +- kind: + PathlibIsDir: ~ + location: + row: 20 + column: 6 + end_location: + row: 20 + column: 11 + fix: ~ + parent: ~ +- kind: + PathlibIsFile: ~ + location: + row: 21 + column: 7 + end_location: + row: 21 + column: 13 + fix: ~ + parent: ~ +- kind: + PathlibIsLink: ~ + location: + row: 22 + column: 8 + end_location: + row: 22 + column: 14 + fix: ~ + parent: ~ +- kind: + PathlibReadlink: ~ + location: + row: 23 + column: 0 + end_location: + row: 23 + column: 8 + fix: ~ + parent: ~ +- kind: + PathlibStat: ~ + location: + row: 24 + column: 0 + end_location: + row: 24 + column: 4 + fix: ~ + parent: ~ +- kind: + PathlibIsAbs: ~ + location: + row: 25 + column: 0 + end_location: + row: 25 + column: 5 + fix: ~ + parent: ~ +- kind: + PathlibJoin: ~ + location: + row: 26 + column: 0 + end_location: + row: 26 + column: 4 + fix: ~ + parent: ~ +- kind: + PathlibBasename: ~ + location: + row: 27 + column: 0 + end_location: + row: 27 + column: 8 + fix: ~ + parent: ~ +- kind: + PathlibDirname: ~ + location: + row: 28 + column: 0 + end_location: + row: 28 + column: 7 + fix: ~ + parent: ~ +- kind: + PathlibSamefile: ~ + location: + row: 29 + column: 0 + end_location: + row: 29 + column: 8 + fix: ~ + parent: ~ +- kind: + PathlibSplitext: ~ + location: + row: 30 + column: 0 + end_location: + row: 30 + column: 8 + fix: ~ + parent: ~ + diff --git a/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap new file mode 100644 index 0000000000000..f0ae20f26d990 --- /dev/null +++ b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from_as.py.snap @@ -0,0 +1,235 @@ +--- +source: src/rules/flake8_use_pathlib/mod.rs +expression: diagnostics +--- +- kind: + PathlibAbspath: ~ + location: + row: 13 + column: 4 + end_location: + row: 13 + column: 12 + fix: ~ + parent: ~ +- kind: + PathlibChmod: ~ + location: + row: 14 + column: 5 + end_location: + row: 14 + column: 11 + fix: ~ + parent: ~ +- kind: + PathlibMkdir: ~ + location: + row: 15 + column: 6 + end_location: + row: 15 + column: 12 + fix: ~ + parent: ~ +- kind: + PathlibMakedirs: ~ + location: + row: 16 + column: 0 + end_location: + row: 16 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibRename: ~ + location: + row: 17 + column: 0 + end_location: + row: 17 + column: 7 + fix: ~ + parent: ~ +- kind: + PathlibReplace: ~ + location: + row: 18 + column: 0 + end_location: + row: 18 + column: 8 + fix: ~ + parent: ~ +- kind: + PathlibRmdir: ~ + location: + row: 19 + column: 0 + end_location: + row: 19 + column: 6 + fix: ~ + parent: ~ +- kind: + PathlibRemove: ~ + location: + row: 20 + column: 0 + end_location: + row: 20 + column: 7 + fix: ~ + parent: ~ +- kind: + PathlibUnlink: ~ + location: + row: 21 + column: 0 + end_location: + row: 21 + column: 7 + fix: ~ + parent: ~ +- kind: + PathlibGetcwd: ~ + location: + row: 22 + column: 0 + end_location: + row: 22 + column: 7 + fix: ~ + parent: ~ +- kind: + PathlibExists: ~ + location: + row: 23 + column: 4 + end_location: + row: 23 + column: 11 + fix: ~ + parent: ~ +- kind: + PathlibExpanduser: ~ + location: + row: 24 + column: 5 + end_location: + row: 24 + column: 16 + fix: ~ + parent: ~ +- kind: + PathlibIsDir: ~ + location: + row: 25 + column: 6 + end_location: + row: 25 + column: 12 + fix: ~ + parent: ~ +- kind: + PathlibIsFile: ~ + location: + row: 26 + column: 7 + end_location: + row: 26 + column: 14 + fix: ~ + parent: ~ +- kind: + PathlibIsLink: ~ + location: + row: 27 + column: 8 + end_location: + row: 27 + column: 15 + fix: ~ + parent: ~ +- kind: + PathlibReadlink: ~ + location: + row: 28 + column: 0 + end_location: + row: 28 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibStat: ~ + location: + row: 29 + column: 0 + end_location: + row: 29 + column: 5 + fix: ~ + parent: ~ +- kind: + PathlibIsAbs: ~ + location: + row: 30 + column: 0 + end_location: + row: 30 + column: 6 + fix: ~ + parent: ~ +- kind: + PathlibJoin: ~ + location: + row: 31 + column: 0 + end_location: + row: 31 + column: 5 + fix: ~ + parent: ~ +- kind: + PathlibBasename: ~ + location: + row: 32 + column: 0 + end_location: + row: 32 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibDirname: ~ + location: + row: 33 + column: 0 + end_location: + row: 33 + column: 8 + fix: ~ + parent: ~ +- kind: + PathlibSamefile: ~ + location: + row: 34 + column: 0 + end_location: + row: 34 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibSplitext: ~ + location: + row: 35 + column: 0 + end_location: + row: 35 + column: 9 + fix: ~ + parent: ~ + diff --git a/src/rules/flake8_use_pathlib/violations.rs b/src/rules/flake8_use_pathlib/violations.rs new file mode 100644 index 0000000000000..0f55f086c7b59 --- /dev/null +++ b/src/rules/flake8_use_pathlib/violations.rs @@ -0,0 +1,257 @@ +use ruff_macros::derive_message_formats; + +use crate::define_violation; +use crate::violation::Violation; + +// PTH100 +define_violation!( + pub struct PathlibAbspath; +); +impl Violation for PathlibAbspath { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.abspath` should be replaced by `.resolve()`") + } +} + +// PTH101 +define_violation!( + pub struct PathlibChmod; +); +impl Violation for PathlibChmod { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.chmod` should be replaced by `.chmod`") + } +} + +// PTH102 +define_violation!( + pub struct PathlibMakedirs; +); +impl Violation for PathlibMakedirs { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.makedirs` should be replaced by `.mkdir(parents=True)`") + } +} + +// PTH103 +define_violation!( + pub struct PathlibMkdir; +); +impl Violation for PathlibMkdir { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.mkdir` should be replaced by `.mkdir()`") + } +} + +// PTH104 +define_violation!( + pub struct PathlibRename; +); +impl Violation for PathlibRename { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.rename` should be replaced by `.rename()`") + } +} + +// PTH105 +define_violation!( + pub struct PathlibReplace; +); +impl Violation for PathlibReplace { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.replace`should be replaced by `.replace()`") + } +} + +// PTH106 +define_violation!( + pub struct PathlibRmdir; +); +impl Violation for PathlibRmdir { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.rmdir` should be replaced by `.rmdir()`") + } +} + +// PTH107 +define_violation!( + pub struct PathlibRemove; +); +impl Violation for PathlibRemove { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.remove` should be replaced by `.unlink()`") + } +} + +// PTH108 +define_violation!( + pub struct PathlibUnlink; +); +impl Violation for PathlibUnlink { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.unlink` should be replaced by `.unlink()`") + } +} + +// PTH109 +define_violation!( + pub struct PathlibGetcwd; +); +impl Violation for PathlibGetcwd { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.getcwd()` should be replaced by `Path.cwd()`") + } +} + +// PTH110 +define_violation!( + pub struct PathlibExists; +); +impl Violation for PathlibExists { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.exists` should be replaced by `.exists()`") + } +} + +// PTH111 +define_violation!( + pub struct PathlibExpanduser; +); +impl Violation for PathlibExpanduser { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.expanduser` should be replaced by `.expanduser()`") + } +} + +// PTH112 +define_violation!( + pub struct PathlibIsDir; +); +impl Violation for PathlibIsDir { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.isdir` should be replaced by `.is_dir()`") + } +} + +// PTH113 +define_violation!( + pub struct PathlibIsFile; +); +impl Violation for PathlibIsFile { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.isfile` should be replaced by `.is_file()`") + } +} + +// PTH114 +define_violation!( + pub struct PathlibIsLink; +); +impl Violation for PathlibIsLink { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.islink` should be replaced by `.is_symlink()`") + } +} + +// PTH115 +define_violation!( + pub struct PathlibReadlink; +); +impl Violation for PathlibReadlink { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.readlink(` should be replaced by `.readlink()`") + } +} + +// PTH116 +define_violation!( + pub struct PathlibStat; +); +impl Violation for PathlibStat { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.stat` should be replaced by `.stat()` or `.owner()` or `.group`") + } +} + +// PTH117 +define_violation!( + pub struct PathlibIsAbs; +); +impl Violation for PathlibIsAbs { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.isabs` should be replaced by `.is_absolute()`") + } +} + +// PTH118 +define_violation!( + pub struct PathlibJoin; +); +impl Violation for PathlibJoin { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.join` should be replaced by foo_path / \"bar\"") + } +} + +// PTH119 +define_violation!( + pub struct PathlibBasename; +); +impl Violation for PathlibBasename { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.basename` should be replaced by `.name`") + } +} + +// PTH120 +define_violation!( + pub struct PathlibDirname; +); +impl Violation for PathlibDirname { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.dirname` should be replaced by `.parent`") + } +} + +// PTH121 +define_violation!( + pub struct PathlibSamefile; +); +impl Violation for PathlibSamefile { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.samefile` should be replaced by `.samefile()`") + } +} + +// PTH122 +define_violation!( + pub struct PathlibSplitext; +); +impl Violation for PathlibSplitext { + #[derive_message_formats] + fn message(&self) -> String { + format!("`os.path.splitext` should be replaced by `.suffix`") + } +} diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 2c4eddc9e4f25..8e58049101778 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -25,6 +25,7 @@ pub mod flake8_simplify; pub mod flake8_tidy_imports; pub mod flake8_type_checking; pub mod flake8_unused_arguments; +pub mod flake8_use_pathlib; pub mod isort; pub mod mccabe; pub mod pandas_vet; From ed1d24247efa291b679c21dfa881cc3772928e6c Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Sun, 22 Jan 2023 19:47:50 +0100 Subject: [PATCH 2/6] fix(script): registry when linter contains dashes --- scripts/add_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/add_rule.py b/scripts/add_rule.py index d80a8b58bc1a4..2e08bbc4df70e 100755 --- a/scripts/add_rule.py +++ b/scripts/add_rule.py @@ -116,7 +116,7 @@ def main(*, name: str, code: str, linter: str) -> None: if line.strip() == f"// {linter}": indent = get_indent(line) - fp.write(f"{indent}{code} => rules::{linter}::rules::{name},") + fp.write(f"{indent}{code} => rules::{dir_name(linter)}::rules::{name},") fp.write("\n") has_written = True From 497d7344603eac5ba8cccd0c02f4d184ad12bca2 Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Sun, 22 Jan 2023 20:03:18 +0100 Subject: [PATCH 3/6] refactor: post-hoc check for specific PTH --- src/rules/flake8_use_pathlib/helpers.rs | 191 ++++-------------------- 1 file changed, 28 insertions(+), 163 deletions(-) diff --git a/src/rules/flake8_use_pathlib/helpers.rs b/src/rules/flake8_use_pathlib/helpers.rs index 784ed806b0b60..01d6baf520b9f 100644 --- a/src/rules/flake8_use_pathlib/helpers.rs +++ b/src/rules/flake8_use_pathlib/helpers.rs @@ -2,7 +2,7 @@ use rustpython_ast::Expr; use crate::ast::types::Range; use crate::checkers::ast::Checker; -use crate::registry::{Diagnostic, DiagnosticKind, Rule}; +use crate::registry::{Diagnostic, DiagnosticKind}; use crate::rules::flake8_use_pathlib::violations::{ PathlibAbspath, PathlibBasename, PathlibChmod, PathlibDirname, PathlibExists, PathlibExpanduser, PathlibGetcwd, PathlibIsAbs, PathlibIsDir, PathlibIsFile, PathlibIsLink, @@ -41,167 +41,29 @@ pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { checker .resolve_call_path(expr) .and_then(|call_path| match call_path.as_slice() { - ["os", "path", "abspath"] => { - if checker.settings.rules.enabled(&Rule::PathlibAbspath) { - Some(OsCall::Abspath) - } else { - None - } - } - ["os", "chmod"] => { - if checker.settings.rules.enabled(&Rule::PathlibChmod) { - Some(OsCall::Chmod) - } else { - None - } - } - ["os", "mkdir"] => { - if checker.settings.rules.enabled(&Rule::PathlibMkdir) { - Some(OsCall::Mkdir) - } else { - None - } - } - ["os", "makedirs"] => { - if checker.settings.rules.enabled(&Rule::PathlibMakedirs) { - Some(OsCall::Makedirs) - } else { - None - } - } - ["os", "rename"] => { - if checker.settings.rules.enabled(&Rule::PathlibRename) { - Some(OsCall::Rename) - } else { - None - } - } - ["os", "replace"] => { - if checker.settings.rules.enabled(&Rule::PathlibReplace) { - Some(OsCall::Replace) - } else { - None - } - } - ["os", "rmdir"] => { - if checker.settings.rules.enabled(&Rule::PathlibRmdir) { - Some(OsCall::Rmdir) - } else { - None - } - } - ["os", "remove"] => { - if checker.settings.rules.enabled(&Rule::PathlibRemove) { - Some(OsCall::Remove) - } else { - None - } - } - ["os", "unlink"] => { - if checker.settings.rules.enabled(&Rule::PathlibUnlink) { - Some(OsCall::Unlink) - } else { - None - } - } - ["os", "getcwd"] => { - if checker.settings.rules.enabled(&Rule::PathlibGetcwd) { - Some(OsCall::Getcwd) - } else { - None - } - } - ["os", "path", "exists"] => { - if checker.settings.rules.enabled(&Rule::PathlibExists) { - Some(OsCall::Exists) - } else { - None - } - } - ["os", "path", "expanduser"] => { - if checker.settings.rules.enabled(&Rule::PathlibExpanduser) { - Some(OsCall::Expanduser) - } else { - None - } - } - ["os", "path", "isdir"] => { - if checker.settings.rules.enabled(&Rule::PathlibIsDir) { - Some(OsCall::IsDir) - } else { - None - } - } - ["os", "path", "isfile"] => { - if checker.settings.rules.enabled(&Rule::PathlibIsFile) { - Some(OsCall::IsFile) - } else { - None - } - } - ["os", "path", "islink"] => { - if checker.settings.rules.enabled(&Rule::PathlibIsLink) { - Some(OsCall::IsLink) - } else { - None - } - } - ["os", "readlink"] => { - if checker.settings.rules.enabled(&Rule::PathlibReadlink) { - Some(OsCall::Readlink) - } else { - None - } - } - ["os", "stat"] => { - if checker.settings.rules.enabled(&Rule::PathlibStat) { - Some(OsCall::Stat) - } else { - None - } - } - ["os", "path", "isabs"] => { - if checker.settings.rules.enabled(&Rule::PathlibIsAbs) { - Some(OsCall::IsAbs) - } else { - None - } - } - ["os", "path", "join"] => { - if checker.settings.rules.enabled(&Rule::PathlibJoin) { - Some(OsCall::Join) - } else { - None - } - } - ["os", "path", "basename"] => { - if checker.settings.rules.enabled(&Rule::PathlibBasename) { - Some(OsCall::Basename) - } else { - None - } - } - ["os", "path", "dirname"] => { - if checker.settings.rules.enabled(&Rule::PathlibDirname) { - Some(OsCall::Dirname) - } else { - None - } - } - ["os", "path", "samefile"] => { - if checker.settings.rules.enabled(&Rule::PathlibSamefile) { - Some(OsCall::Samefile) - } else { - None - } - } - ["os", "path", "splitext"] => { - if checker.settings.rules.enabled(&Rule::PathlibSplitext) { - Some(OsCall::Splitext) - } else { - None - } - } + ["os", "path", "abspath"] => Some(OsCall::Abspath), + ["os", "chmod"] => Some(OsCall::Chmod), + ["os", "mkdir"] => Some(OsCall::Mkdir), + ["os", "makedirs"] => Some(OsCall::Makedirs), + ["os", "rename"] => Some(OsCall::Rename), + ["os", "replace"] => Some(OsCall::Replace), + ["os", "rmdir"] => Some(OsCall::Rmdir), + ["os", "remove"] => Some(OsCall::Remove), + ["os", "unlink"] => Some(OsCall::Unlink), + ["os", "getcwd"] => Some(OsCall::Getcwd), + ["os", "path", "exists"] => Some(OsCall::Exists), + ["os", "path", "expanduser"] => Some(OsCall::Expanduser), + ["os", "path", "isdir"] => Some(OsCall::IsDir), + ["os", "path", "isfile"] => Some(OsCall::IsFile), + ["os", "path", "islink"] => Some(OsCall::IsLink), + ["os", "readlink"] => Some(OsCall::Readlink), + ["os", "stat"] => Some(OsCall::Stat), + ["os", "path", "isabs"] => Some(OsCall::IsAbs), + ["os", "path", "join"] => Some(OsCall::Join), + ["os", "path", "basename"] => Some(OsCall::Basename), + ["os", "path", "dirname"] => Some(OsCall::Dirname), + ["os", "path", "samefile"] => Some(OsCall::Samefile), + ["os", "path", "splitext"] => Some(OsCall::Splitext), _ => None, }) { @@ -233,6 +95,9 @@ pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { }, Range::from_located(expr), ); - checker.diagnostics.push(diagnostic); + + if checker.settings.rules.enabled(diagnostic.kind.rule()) { + checker.diagnostics.push(diagnostic); + } } } From 666892955324605ea3764e1fd51c62b2505b7805 Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Sun, 22 Jan 2023 20:27:54 +0100 Subject: [PATCH 4/6] feat: open (PTH023) + parentheses --- README.md | 5 +++-- .../fixtures/flake8_use_pathlib/full_name.py | 3 +++ .../flake8_use_pathlib/import_from.py | 3 +++ ruff.schema.json | 1 + src/checkers/ast.rs | 1 + src/registry.rs | 1 + src/rules/flake8_use_pathlib/helpers.rs | 8 ++++++-- src/rules/flake8_use_pathlib/mod.rs | 1 + ...ake8_use_pathlib__tests__full_name.py.snap | 20 +++++++++++++++++++ ...e8_use_pathlib__tests__import_from.py.snap | 20 +++++++++++++++++++ src/rules/flake8_use_pathlib/violations.rs | 15 ++++++++++++-- 11 files changed, 72 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d771b5cd8c4c1..c585170c6dff2 100644 --- a/README.md +++ b/README.md @@ -1205,7 +1205,7 @@ 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` | | +| 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()` | | @@ -1220,13 +1220,14 @@ For more, see [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) | 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()` | | -| PTH116 | pathlib-stat | `os.stat` should be replaced by `.stat()` or `.owner()` or `.group` | | +| 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" | | | 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()` | | ### Ruff-specific rules (RUF) diff --git a/resources/test/fixtures/flake8_use_pathlib/full_name.py b/resources/test/fixtures/flake8_use_pathlib/full_name.py index 6af31a02d22aa..6d6ec8ed1d0ac 100644 --- a/resources/test/fixtures/flake8_use_pathlib/full_name.py +++ b/resources/test/fixtures/flake8_use_pathlib/full_name.py @@ -26,3 +26,6 @@ os.path.dirname(p) os.path.samefile(p) os.path.splitext(p) +with open(p) as fp: + fp.read() +open(p).close() diff --git a/resources/test/fixtures/flake8_use_pathlib/import_from.py b/resources/test/fixtures/flake8_use_pathlib/import_from.py index 3a38a6e93812d..9e4dc71a2d647 100644 --- a/resources/test/fixtures/flake8_use_pathlib/import_from.py +++ b/resources/test/fixtures/flake8_use_pathlib/import_from.py @@ -28,3 +28,6 @@ dirname(p) samefile(p) splitext(p) +with open(p) as fp: + fp.read() +open(p).close() diff --git a/ruff.schema.json b/ruff.schema.json index 5032ab4368f21..68f5d083ddd91 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1661,6 +1661,7 @@ "PTH120", "PTH121", "PTH122", + "PTH123", "Q", "Q0", "Q00", diff --git a/src/checkers/ast.rs b/src/checkers/ast.rs index 3a515010b6a8a..4df5bde4c0030 100644 --- a/src/checkers/ast.rs +++ b/src/checkers/ast.rs @@ -2569,6 +2569,7 @@ where || self.settings.rules.enabled(&Rule::PathlibBasename) || self.settings.rules.enabled(&Rule::PathlibSamefile) || self.settings.rules.enabled(&Rule::PathlibSplitext) + || self.settings.rules.enabled(&Rule::PathlibOpen) { flake8_use_pathlib::helpers::replaceable_by_pathlib(self, func); } diff --git a/src/registry.rs b/src/registry.rs index 0bc562e0b8235..0e508693762f5 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -453,6 +453,7 @@ ruff_macros::define_rule_mapping!( PTH120 => rules::flake8_use_pathlib::violations::PathlibDirname, PTH121 => rules::flake8_use_pathlib::violations::PathlibSamefile, PTH122 => rules::flake8_use_pathlib::violations::PathlibSplitext, + PTH123 => rules::flake8_use_pathlib::violations::PathlibOpen, // ruff RUF001 => violations::AmbiguousUnicodeCharacterString, RUF002 => violations::AmbiguousUnicodeCharacterDocstring, diff --git a/src/rules/flake8_use_pathlib/helpers.rs b/src/rules/flake8_use_pathlib/helpers.rs index 01d6baf520b9f..74cea9be2a6fe 100644 --- a/src/rules/flake8_use_pathlib/helpers.rs +++ b/src/rules/flake8_use_pathlib/helpers.rs @@ -6,8 +6,9 @@ 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, PathlibReadlink, PathlibRemove, PathlibRename, - PathlibReplace, PathlibRmdir, PathlibSamefile, PathlibSplitext, PathlibStat, PathlibUnlink, + PathlibJoin, PathlibMakedirs, PathlibMkdir, PathlibOpen, PathlibReadlink, PathlibRemove, + PathlibRename, PathlibReplace, PathlibRmdir, PathlibSamefile, PathlibSplitext, PathlibStat, + PathlibUnlink, }; enum OsCall { @@ -34,6 +35,7 @@ enum OsCall { Dirname, Samefile, Splitext, + Open, } pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { @@ -64,6 +66,7 @@ pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { ["os", "path", "dirname"] => Some(OsCall::Dirname), ["os", "path", "samefile"] => Some(OsCall::Samefile), ["os", "path", "splitext"] => Some(OsCall::Splitext), + ["", "open"] => Some(OsCall::Open), _ => None, }) { @@ -92,6 +95,7 @@ pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { OsCall::Dirname => PathlibDirname.into(), OsCall::Samefile => PathlibSamefile.into(), OsCall::Splitext => PathlibSplitext.into(), + OsCall::Open => PathlibOpen.into(), }, Range::from_located(expr), ); diff --git a/src/rules/flake8_use_pathlib/mod.rs b/src/rules/flake8_use_pathlib/mod.rs index b065fd2b6d833..a632d262a7839 100644 --- a/src/rules/flake8_use_pathlib/mod.rs +++ b/src/rules/flake8_use_pathlib/mod.rs @@ -47,6 +47,7 @@ mod tests { Rule::PathlibDirname, Rule::PathlibSamefile, Rule::PathlibSplitext, + Rule::PathlibOpen, ]), )?; insta::assert_yaml_snapshot!(snapshot, diagnostics); diff --git a/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap index 1691e4d752c1a..d14875f4966b5 100644 --- a/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap +++ b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__full_name.py.snap @@ -232,4 +232,24 @@ expression: diagnostics column: 16 fix: ~ parent: ~ +- kind: + PathlibOpen: ~ + location: + row: 29 + column: 5 + end_location: + row: 29 + column: 9 + fix: ~ + parent: ~ +- kind: + PathlibOpen: ~ + location: + row: 31 + column: 0 + end_location: + row: 31 + column: 4 + fix: ~ + parent: ~ diff --git a/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap index 3eb1d2c4ccf1b..a3f6ff105b440 100644 --- a/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap +++ b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__import_from.py.snap @@ -232,4 +232,24 @@ expression: diagnostics column: 8 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: ~ diff --git a/src/rules/flake8_use_pathlib/violations.rs b/src/rules/flake8_use_pathlib/violations.rs index 0f55f086c7b59..fa59d098fc446 100644 --- a/src/rules/flake8_use_pathlib/violations.rs +++ b/src/rules/flake8_use_pathlib/violations.rs @@ -21,7 +21,7 @@ define_violation!( impl Violation for PathlibChmod { #[derive_message_formats] fn message(&self) -> String { - format!("`os.chmod` should be replaced by `.chmod`") + format!("`os.chmod` should be replaced by `.chmod()`") } } @@ -186,7 +186,7 @@ define_violation!( impl Violation for PathlibStat { #[derive_message_formats] fn message(&self) -> String { - format!("`os.stat` should be replaced by `.stat()` or `.owner()` or `.group`") + format!("`os.stat` should be replaced by `.stat()` or `.owner()` or `.group()`") } } @@ -255,3 +255,14 @@ impl Violation for PathlibSplitext { format!("`os.path.splitext` should be replaced by `.suffix`") } } + +// PTH123 +define_violation!( + pub struct PathlibOpen; +); +impl Violation for PathlibOpen { + #[derive_message_formats] + fn message(&self) -> String { + format!("`open(\"foo\")` should be replaced by`Path(\"foo\").open()`") + } +} From e38d12dc68ee905d12d10665face1e0a867f3320 Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Sun, 22 Jan 2023 20:39:00 +0100 Subject: [PATCH 5/6] feat: final rule for this plugin (PTH024) --- README.md | 1 + .../test/fixtures/flake8_use_pathlib/py_path_1.py | 3 +++ .../test/fixtures/flake8_use_pathlib/py_path_2.py | 3 +++ ruff.schema.json | 1 + src/checkers/ast.rs | 1 + src/registry.rs | 1 + src/rules/flake8_use_pathlib/helpers.rs | 9 ++++++--- src/rules/flake8_use_pathlib/mod.rs | 14 ++++++++++++++ ...8_use_pathlib__tests__PTH124_py_path_1.py.snap | 15 +++++++++++++++ ...8_use_pathlib__tests__PTH124_py_path_2.py.snap | 15 +++++++++++++++ src/rules/flake8_use_pathlib/violations.rs | 11 +++++++++++ 11 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 resources/test/fixtures/flake8_use_pathlib/py_path_1.py create mode 100644 resources/test/fixtures/flake8_use_pathlib/py_path_2.py create mode 100644 src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_1.py.snap create mode 100644 src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_2.py.snap diff --git a/README.md b/README.md index c585170c6dff2..90a6580fc0170 100644 --- a/README.md +++ b/README.md @@ -1228,6 +1228,7 @@ For more, see [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) | 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 | | ### Ruff-specific rules (RUF) diff --git a/resources/test/fixtures/flake8_use_pathlib/py_path_1.py b/resources/test/fixtures/flake8_use_pathlib/py_path_1.py new file mode 100644 index 0000000000000..27c6205c81350 --- /dev/null +++ b/resources/test/fixtures/flake8_use_pathlib/py_path_1.py @@ -0,0 +1,3 @@ +import py + +p = py.path.local("../foo") diff --git a/resources/test/fixtures/flake8_use_pathlib/py_path_2.py b/resources/test/fixtures/flake8_use_pathlib/py_path_2.py new file mode 100644 index 0000000000000..fc25529fb2e90 --- /dev/null +++ b/resources/test/fixtures/flake8_use_pathlib/py_path_2.py @@ -0,0 +1,3 @@ +from py.path import local as path + +p = path("/foo") diff --git a/ruff.schema.json b/ruff.schema.json index 68f5d083ddd91..792a4096ad8ad 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1662,6 +1662,7 @@ "PTH121", "PTH122", "PTH123", + "PTH124", "Q", "Q0", "Q00", diff --git a/src/checkers/ast.rs b/src/checkers/ast.rs index 4df5bde4c0030..94c5045e948ae 100644 --- a/src/checkers/ast.rs +++ b/src/checkers/ast.rs @@ -2570,6 +2570,7 @@ where || self.settings.rules.enabled(&Rule::PathlibSamefile) || self.settings.rules.enabled(&Rule::PathlibSplitext) || self.settings.rules.enabled(&Rule::PathlibOpen) + || self.settings.rules.enabled(&Rule::PathlibPyPath) { flake8_use_pathlib::helpers::replaceable_by_pathlib(self, func); } diff --git a/src/registry.rs b/src/registry.rs index 0e508693762f5..4d66462b28341 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -454,6 +454,7 @@ ruff_macros::define_rule_mapping!( PTH121 => rules::flake8_use_pathlib::violations::PathlibSamefile, PTH122 => rules::flake8_use_pathlib::violations::PathlibSplitext, PTH123 => rules::flake8_use_pathlib::violations::PathlibOpen, + PTH124 => rules::flake8_use_pathlib::violations::PathlibPyPath, // ruff RUF001 => violations::AmbiguousUnicodeCharacterString, RUF002 => violations::AmbiguousUnicodeCharacterDocstring, diff --git a/src/rules/flake8_use_pathlib/helpers.rs b/src/rules/flake8_use_pathlib/helpers.rs index 74cea9be2a6fe..053d51fe13d4a 100644 --- a/src/rules/flake8_use_pathlib/helpers.rs +++ b/src/rules/flake8_use_pathlib/helpers.rs @@ -6,9 +6,9 @@ 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, PathlibReadlink, PathlibRemove, - PathlibRename, PathlibReplace, PathlibRmdir, PathlibSamefile, PathlibSplitext, PathlibStat, - PathlibUnlink, + PathlibJoin, PathlibMakedirs, PathlibMkdir, PathlibOpen, PathlibPyPath, PathlibReadlink, + PathlibRemove, PathlibRename, PathlibReplace, PathlibRmdir, PathlibSamefile, PathlibSplitext, + PathlibStat, PathlibUnlink, }; enum OsCall { @@ -36,6 +36,7 @@ enum OsCall { Samefile, Splitext, Open, + PyPath, } pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { @@ -67,6 +68,7 @@ pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { ["os", "path", "samefile"] => Some(OsCall::Samefile), ["os", "path", "splitext"] => Some(OsCall::Splitext), ["", "open"] => Some(OsCall::Open), + ["py", "path", "local"] => Some(OsCall::PyPath), _ => None, }) { @@ -96,6 +98,7 @@ pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { OsCall::Samefile => PathlibSamefile.into(), OsCall::Splitext => PathlibSplitext.into(), OsCall::Open => PathlibOpen.into(), + OsCall::PyPath => PathlibPyPath.into(), }, Range::from_located(expr), ); diff --git a/src/rules/flake8_use_pathlib/mod.rs b/src/rules/flake8_use_pathlib/mod.rs index a632d262a7839..27944b701469d 100644 --- a/src/rules/flake8_use_pathlib/mod.rs +++ b/src/rules/flake8_use_pathlib/mod.rs @@ -53,4 +53,18 @@ mod tests { insta::assert_yaml_snapshot!(snapshot, diagnostics); Ok(()) } + + #[test_case(Rule::PathlibPyPath, Path::new("py_path_1.py"); "PTH024_1")] + #[test_case(Rule::PathlibPyPath, Path::new("py_path_2.py"); "PTH024_2")] + fn rules_pypath(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy()); + let diagnostics = test_path( + Path::new("./resources/test/fixtures/flake8_use_pathlib") + .join(path) + .as_path(), + &settings::Settings::for_rule(rule_code), + )?; + insta::assert_yaml_snapshot!(snapshot, diagnostics); + Ok(()) + } } diff --git a/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_1.py.snap b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_1.py.snap new file mode 100644 index 0000000000000..b27363952472b --- /dev/null +++ b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_1.py.snap @@ -0,0 +1,15 @@ +--- +source: src/rules/flake8_use_pathlib/mod.rs +expression: diagnostics +--- +- kind: + PathlibPyPath: ~ + location: + row: 3 + column: 4 + end_location: + row: 3 + column: 17 + fix: ~ + parent: ~ + diff --git a/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_2.py.snap b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_2.py.snap new file mode 100644 index 0000000000000..c5f6ad5501c67 --- /dev/null +++ b/src/rules/flake8_use_pathlib/snapshots/ruff__rules__flake8_use_pathlib__tests__PTH124_py_path_2.py.snap @@ -0,0 +1,15 @@ +--- +source: src/rules/flake8_use_pathlib/mod.rs +expression: diagnostics +--- +- kind: + PathlibPyPath: ~ + location: + row: 3 + column: 4 + end_location: + row: 3 + column: 8 + fix: ~ + parent: ~ + diff --git a/src/rules/flake8_use_pathlib/violations.rs b/src/rules/flake8_use_pathlib/violations.rs index fa59d098fc446..c1d6f38a570fd 100644 --- a/src/rules/flake8_use_pathlib/violations.rs +++ b/src/rules/flake8_use_pathlib/violations.rs @@ -266,3 +266,14 @@ impl Violation for PathlibOpen { format!("`open(\"foo\")` should be replaced by`Path(\"foo\").open()`") } } + +// PTH124 +define_violation!( + pub struct PathlibPyPath; +); +impl Violation for PathlibPyPath { + #[derive_message_formats] + fn message(&self) -> String { + format!("`py.path` is in maintenance mode, use pathlib instead") + } +} From bd2f86389d11f77a8d01bf5e5f7b927a2155fc29 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 22 Jan 2023 15:17:09 -0500 Subject: [PATCH 6/6] Wrap pathlib in backticks --- README.md | 2 +- src/rules/flake8_use_pathlib/violations.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 90a6580fc0170..2c14399a83584 100644 --- a/README.md +++ b/README.md @@ -1228,7 +1228,7 @@ For more, see [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) | 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 | | +| PTH124 | pathlib-py-path | `py.path` is in maintenance mode, use `pathlib` instead | | ### Ruff-specific rules (RUF) diff --git a/src/rules/flake8_use_pathlib/violations.rs b/src/rules/flake8_use_pathlib/violations.rs index c1d6f38a570fd..602898c90f367 100644 --- a/src/rules/flake8_use_pathlib/violations.rs +++ b/src/rules/flake8_use_pathlib/violations.rs @@ -274,6 +274,6 @@ define_violation!( impl Violation for PathlibPyPath { #[derive_message_formats] fn message(&self) -> String { - format!("`py.path` is in maintenance mode, use pathlib instead") + format!("`py.path` is in maintenance mode, use `pathlib` instead") } }