Skip to content

Commit

Permalink
[flake8-use-pathlib] Implement glob (PTH207) (#5939)
Browse files Browse the repository at this point in the history
Discovered that the usage of `glob.glob` is
[widespread](https://grep.app/search?current=7&q=glob.glob%28&filter%5Blang%5D%5B0%5D=Python)
when working on the previous lints for `flake8-use-pathlib`.
  • Loading branch information
sbrugman committed Jul 26, 2023
1 parent 132f07c commit ffdd653
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 1 deletion.
11 changes: 11 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_use_pathlib/PTH207.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import os
import glob
from glob import glob as search


extensions_dir = "./extensions"

# PTH207
glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))
list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")))
search("*.png")
1 change: 1 addition & 0 deletions crates/ruff/src/checkers/ast/analyze/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
Rule::OsPathGetatime,
Rule::OsPathGetmtime,
Rule::OsPathGetctime,
Rule::Glob,
]) {
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, func);
}
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8UsePathlib, "204") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsPathGetmtime),
(Flake8UsePathlib, "205") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsPathGetctime),
(Flake8UsePathlib, "206") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsSepSplit),
(Flake8UsePathlib, "207") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::Glob),

// flake8-logging-format
(Flake8LoggingFormat, "001") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingStringFormat),
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/rules/flake8_use_pathlib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ mod tests {
#[test_case(Rule::OsPathGetmtime, Path::new("PTH204.py"))]
#[test_case(Rule::OsPathGetctime, Path::new("PTH205.py"))]
#[test_case(Rule::OsSepSplit, Path::new("PTH206.py"))]
#[test_case(Rule::Glob, Path::new("PTH207.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(
Expand Down
55 changes: 55 additions & 0 deletions crates/ruff/src/rules/flake8_use_pathlib/rules/glob_rule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, violation};

/// ## What it does
/// Checks for the use of `glob` and `iglob`.
///
/// ## Why is this bad?
/// `pathlib` offers a high-level API for path manipulation, as compared to
/// the lower-level API offered by `os` and `glob`.
///
/// When possible, using `Path` object methods such as `Path.glob()` can
/// improve readability over their low-level counterparts (e.g.,
/// `glob.glob()`).
///
/// Note that `glob.glob` and `Path.glob` are not exact equivalents:
///
/// | | `glob` | `Path.glob` |
/// |-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
/// | Hidden files | Excludes hidden files by default. From Python 3.11 onwards, the `include_hidden` keyword can used to include hidden directories. | Includes hidden files by default. |
/// | Iterator | `iglob` returns an iterator. Under the hood, `glob` simply converts the iterator to a list. | `Path.glob` returns an iterator. |
/// | Working directory | `glob` takes a `root_dir` keyword to set the current working directory. | `Path.rglob` can be used to return the relative path. |
/// | Globstar (`**`) | `glob` requires the `recursive` flag to be set to `True` for the `**` pattern to match any files and zero or more directories, subdirectories, and symbolic links. | The `**` pattern in `Path.glob` means "this directory and all subdirectories, recursively". In other words, it enables recursive globbing. |
///
/// ## Example
/// ```python
/// import glob
/// import os
///
/// glob.glob(os.path.join(path, "requirements*.txt"))
/// ```
///
/// Use instead:
/// ```python
/// from pathlib import Path
///
/// Path(path).glob("requirements*.txt")
/// ```
///
/// ## References
/// - [Python documentation: `Path.glob`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob)
/// - [Python documentation: `Path.rglob`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rglob)
/// - [Python documentation: `glob.glob`](https://docs.python.org/3/library/glob.html#glob.glob)
/// - [Python documentation: `glob.iglob`](https://docs.python.org/3/library/glob.html#glob.iglob)
#[violation]
pub struct Glob {
pub function: String,
}

impl Violation for Glob {
#[derive_message_formats]
fn message(&self) -> String {
let Glob { function } = self;
format!("Replace `{function}` with `Path.glob` or `Path.rglob`")
}
}
2 changes: 2 additions & 0 deletions crates/ruff/src/rules/flake8_use_pathlib/rules/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub(crate) use glob_rule::*;
pub(crate) use os_path_getatime::*;
pub(crate) use os_path_getctime::*;
pub(crate) use os_path_getmtime::*;
Expand All @@ -6,6 +7,7 @@ pub(crate) use os_sep_split::*;
pub(crate) use path_constructor_current_directory::*;
pub(crate) use replaceable_by_pathlib::*;

mod glob_rule;
mod os_path_getatime;
mod os_path_getctime;
mod os_path_getmtime;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use ruff_diagnostics::{Diagnostic, DiagnosticKind};
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_use_pathlib::rules::{
OsPathGetatime, OsPathGetctime, OsPathGetmtime, OsPathGetsize,
Glob, OsPathGetatime, OsPathGetctime, OsPathGetmtime, OsPathGetsize,
};
use crate::rules::flake8_use_pathlib::violations::{
BuiltinOpen, OsChmod, OsGetcwd, OsMakedirs, OsMkdir, OsPathAbspath, OsPathBasename,
Expand Down Expand Up @@ -89,6 +89,19 @@ pub(crate) fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) {
["" | "builtin", "open"] => Some(BuiltinOpen.into()),
// PTH124
["py", "path", "local"] => Some(PyPath.into()),
// PTH207
["glob", "glob"] => Some(
Glob {
function: "glob".to_string(),
}
.into(),
),
["glob", "iglob"] => Some(
Glob {
function: "iglob".to_string(),
}
.into(),
),
// PTH115
// Python 3.9+
["os", "readlink"] if checker.settings.target_version >= PythonVersion::Py39 => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs
---
PTH207.py:9:1: PTH207 Replace `glob` with `Path.glob` or `Path.rglob`
|
8 | # PTH207
9 | glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))
| ^^^^^^^^^ PTH207
10 | list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")))
11 | search("*.png")
|

PTH207.py:10:6: PTH207 Replace `iglob` with `Path.glob` or `Path.rglob`
|
8 | # PTH207
9 | glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))
10 | list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")))
| ^^^^^^^^^^ PTH207
11 | search("*.png")
|

PTH207.py:11:1: PTH207 Replace `glob` with `Path.glob` or `Path.rglob`
|
9 | glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))
10 | list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")))
11 | search("*.png")
| ^^^^^^ PTH207
|


1 change: 1 addition & 0 deletions ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ffdd653

Please sign in to comment.