mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 02:12:22 +00:00
[flake8-use-pathlib
] Implement path-constructor-default-argument
(PTH201
) (#5833)
Reviving https://github.com/astral-sh/ruff/pull/2348 step by step Pt 2. PTH201: Path Constructor Default Argument - rule originates from `refurb`: https://github.com/charliermarsh/ruff/issues/1348 - Using PTH201 rather than FURBXXX to keep all pathlib logic together
This commit is contained in:
parent
a37d91529b
commit
d35cb6942f
8 changed files with 175 additions and 0 deletions
14
crates/ruff/resources/test/fixtures/flake8_use_pathlib/PTH201.py
vendored
Normal file
14
crates/ruff/resources/test/fixtures/flake8_use_pathlib/PTH201.py
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
from pathlib import Path, PurePath
|
||||
from pathlib import Path as pth
|
||||
|
||||
# match
|
||||
_ = Path(".")
|
||||
_ = pth(".")
|
||||
_ = PurePath(".")
|
||||
|
||||
# no match
|
||||
_ = Path()
|
||||
print(".")
|
||||
Path("file.txt")
|
||||
Path(".", "folder")
|
||||
PurePath(".", "folder")
|
|
@ -3027,6 +3027,9 @@ where
|
|||
]) {
|
||||
flake8_use_pathlib::rules::replaceable_by_pathlib(self, func);
|
||||
}
|
||||
if self.enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(self, expr, func);
|
||||
}
|
||||
if self.enabled(Rule::NumpyLegacyRandom) {
|
||||
numpy::rules::legacy_random(self, func);
|
||||
}
|
||||
|
|
|
@ -748,6 +748,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||
(Flake8UsePathlib, "122") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathSplitext),
|
||||
(Flake8UsePathlib, "123") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::BuiltinOpen),
|
||||
(Flake8UsePathlib, "124") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::PyPath),
|
||||
(Flake8UsePathlib, "201") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::PathConstructorCurrentDirectory),
|
||||
|
||||
// flake8-logging-format
|
||||
(Flake8LoggingFormat, "001") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingStringFormat),
|
||||
|
|
|
@ -56,6 +56,8 @@ mod tests {
|
|||
|
||||
#[test_case(Rule::PyPath, Path::new("py_path_1.py"))]
|
||||
#[test_case(Rule::PyPath, Path::new("py_path_2.py"))]
|
||||
#[test_case(Rule::PathConstructorCurrentDirectory, Path::new("PTH201.py"))]
|
||||
|
||||
fn rules_pypath(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
pub(crate) use path_constructor_current_directory::*;
|
||||
pub(crate) use replaceable_by_pathlib::*;
|
||||
|
||||
mod path_constructor_current_directory;
|
||||
mod replaceable_by_pathlib;
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
use rustpython_parser::ast::{Constant, Expr, ExprCall, ExprConstant};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `pathlib.Path` objects that are initialized with the current
|
||||
/// directory.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The `Path()` constructor defaults to the current directory, so passing it
|
||||
/// in explicitly (as `"."`) is unnecessary.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// _ = Path(".")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// _ = Path()
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path)
|
||||
#[violation]
|
||||
pub struct PathConstructorCurrentDirectory;
|
||||
|
||||
impl AlwaysAutofixableViolation for PathConstructorCurrentDirectory {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Do not pass the current directory explicitly to `Path`")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
"Remove the current directory argument".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH201
|
||||
pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["pathlib", "Path" | "PurePath"])
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let Expr::Call(ExprCall { args, keywords, .. }) = expr else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let [Expr::Constant(ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
kind: _,
|
||||
range
|
||||
})] = args.as_slice()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if value != "." {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(PathConstructorCurrentDirectory, *range);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_deletion(*range)));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
PTH201.py:5:10: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
4 | # match
|
||||
5 | _ = Path(".")
|
||||
| ^^^ PTH201
|
||||
6 | _ = pth(".")
|
||||
7 | _ = PurePath(".")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Fix
|
||||
2 2 | from pathlib import Path as pth
|
||||
3 3 |
|
||||
4 4 | # match
|
||||
5 |-_ = Path(".")
|
||||
5 |+_ = Path()
|
||||
6 6 | _ = pth(".")
|
||||
7 7 | _ = PurePath(".")
|
||||
8 8 |
|
||||
|
||||
PTH201.py:6:9: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
4 | # match
|
||||
5 | _ = Path(".")
|
||||
6 | _ = pth(".")
|
||||
| ^^^ PTH201
|
||||
7 | _ = PurePath(".")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Fix
|
||||
3 3 |
|
||||
4 4 | # match
|
||||
5 5 | _ = Path(".")
|
||||
6 |-_ = pth(".")
|
||||
6 |+_ = pth()
|
||||
7 7 | _ = PurePath(".")
|
||||
8 8 |
|
||||
9 9 | # no match
|
||||
|
||||
PTH201.py:7:14: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
5 | _ = Path(".")
|
||||
6 | _ = pth(".")
|
||||
7 | _ = PurePath(".")
|
||||
| ^^^ PTH201
|
||||
8 |
|
||||
9 | # no match
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Fix
|
||||
4 4 | # match
|
||||
5 5 | _ = Path(".")
|
||||
6 6 | _ = pth(".")
|
||||
7 |-_ = PurePath(".")
|
||||
7 |+_ = PurePath()
|
||||
8 8 |
|
||||
9 9 | # no match
|
||||
10 10 | _ = Path()
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue