Skip to content

Commit 029de78

Browse files
danparizherntBre
andauthored
[flake8-use-pathlib] Fix false negative on direct Path() instantiation (PTH210) (#19388)
## Summary Fixes #19329 --------- Co-authored-by: Brent Westbrook <[email protected]>
1 parent ff94fe7 commit 029de78

File tree

3 files changed

+157
-12
lines changed

3 files changed

+157
-12
lines changed

crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH210.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@
5454
windows_path.with_suffix(u'' "json")
5555
windows_path.with_suffix(suffix="js")
5656

57+
Path().with_suffix(".")
58+
Path().with_suffix("py")
59+
PosixPath().with_suffix("py")
60+
PurePath().with_suffix("py")
61+
PurePosixPath().with_suffix("py")
62+
PureWindowsPath().with_suffix("py")
63+
WindowsPath().with_suffix("py")
5764

5865
### No errors
5966
path.with_suffix()

crates/ruff_linter/src/rules/flake8_use_pathlib/rules/invalid_pathlib_with_suffix.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{Edit, Fix, FixAvailability, Violation};
33
use ruff_macros::{ViolationMetadata, derive_message_formats};
44
use ruff_python_ast::{self as ast, PythonVersion, StringFlags};
55
use ruff_python_semantic::SemanticModel;
6-
use ruff_python_semantic::analyze::typing;
6+
use ruff_python_semantic::analyze::typing::{self, PathlibPathChecker, TypeChecker};
77
use ruff_text_size::Ranged;
88

99
/// ## What it does
@@ -141,12 +141,13 @@ fn is_path_with_suffix_call(semantic: &SemanticModel, func: &ast::Expr) -> bool
141141
return false;
142142
}
143143

144-
let ast::Expr::Name(name) = &**value else {
145-
return false;
146-
};
147-
let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else {
148-
return false;
149-
};
150-
151-
typing::is_pathlib_path(binding, semantic)
144+
match &**value {
145+
ast::Expr::Name(name) => {
146+
let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else {
147+
return false;
148+
};
149+
typing::is_pathlib_path(binding, semantic)
150+
}
151+
expr => PathlibPathChecker::match_initializer(expr, semantic),
152+
}
152153
}

crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__PTH210_PTH210.py.snap

Lines changed: 140 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -536,14 +536,16 @@ PTH210.py:54:1: PTH210 [*] Dotless suffix passed to `.with_suffix()`
536536
54 |+windows_path.with_suffix(u'.' "json")
537537
55 55 | windows_path.with_suffix(suffix="js")
538538
56 56 |
539-
57 57 |
539+
57 57 | Path().with_suffix(".")
540540

541541
PTH210.py:55:1: PTH210 [*] Dotless suffix passed to `.with_suffix()`
542542
|
543543
53 | windows_path.with_suffix(r"s")
544544
54 | windows_path.with_suffix(u'' "json")
545545
55 | windows_path.with_suffix(suffix="js")
546546
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH210
547+
56 |
548+
57 | Path().with_suffix(".")
547549
|
548550
= help: Add a leading dot
549551

@@ -554,5 +556,140 @@ PTH210.py:55:1: PTH210 [*] Dotless suffix passed to `.with_suffix()`
554556
55 |-windows_path.with_suffix(suffix="js")
555557
55 |+windows_path.with_suffix(suffix=".js")
556558
56 56 |
557-
57 57 |
558-
58 58 | ### No errors
559+
57 57 | Path().with_suffix(".")
560+
58 58 | Path().with_suffix("py")
561+
562+
PTH210.py:57:1: PTH210 Invalid suffix passed to `.with_suffix()`
563+
|
564+
55 | windows_path.with_suffix(suffix="js")
565+
56 |
566+
57 | Path().with_suffix(".")
567+
| ^^^^^^^^^^^^^^^^^^^^^^^ PTH210
568+
58 | Path().with_suffix("py")
569+
59 | PosixPath().with_suffix("py")
570+
|
571+
= help: Remove "." or extend to valid suffix
572+
573+
PTH210.py:58:1: PTH210 [*] Dotless suffix passed to `.with_suffix()`
574+
|
575+
57 | Path().with_suffix(".")
576+
58 | Path().with_suffix("py")
577+
| ^^^^^^^^^^^^^^^^^^^^^^^^ PTH210
578+
59 | PosixPath().with_suffix("py")
579+
60 | PurePath().with_suffix("py")
580+
|
581+
= help: Add a leading dot
582+
583+
Unsafe fix
584+
55 55 | windows_path.with_suffix(suffix="js")
585+
56 56 |
586+
57 57 | Path().with_suffix(".")
587+
58 |-Path().with_suffix("py")
588+
58 |+Path().with_suffix(".py")
589+
59 59 | PosixPath().with_suffix("py")
590+
60 60 | PurePath().with_suffix("py")
591+
61 61 | PurePosixPath().with_suffix("py")
592+
593+
PTH210.py:59:1: PTH210 [*] Dotless suffix passed to `.with_suffix()`
594+
|
595+
57 | Path().with_suffix(".")
596+
58 | Path().with_suffix("py")
597+
59 | PosixPath().with_suffix("py")
598+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH210
599+
60 | PurePath().with_suffix("py")
600+
61 | PurePosixPath().with_suffix("py")
601+
|
602+
= help: Add a leading dot
603+
604+
Unsafe fix
605+
56 56 |
606+
57 57 | Path().with_suffix(".")
607+
58 58 | Path().with_suffix("py")
608+
59 |-PosixPath().with_suffix("py")
609+
59 |+PosixPath().with_suffix(".py")
610+
60 60 | PurePath().with_suffix("py")
611+
61 61 | PurePosixPath().with_suffix("py")
612+
62 62 | PureWindowsPath().with_suffix("py")
613+
614+
PTH210.py:60:1: PTH210 [*] Dotless suffix passed to `.with_suffix()`
615+
|
616+
58 | Path().with_suffix("py")
617+
59 | PosixPath().with_suffix("py")
618+
60 | PurePath().with_suffix("py")
619+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH210
620+
61 | PurePosixPath().with_suffix("py")
621+
62 | PureWindowsPath().with_suffix("py")
622+
|
623+
= help: Add a leading dot
624+
625+
Unsafe fix
626+
57 57 | Path().with_suffix(".")
627+
58 58 | Path().with_suffix("py")
628+
59 59 | PosixPath().with_suffix("py")
629+
60 |-PurePath().with_suffix("py")
630+
60 |+PurePath().with_suffix(".py")
631+
61 61 | PurePosixPath().with_suffix("py")
632+
62 62 | PureWindowsPath().with_suffix("py")
633+
63 63 | WindowsPath().with_suffix("py")
634+
635+
PTH210.py:61:1: PTH210 [*] Dotless suffix passed to `.with_suffix()`
636+
|
637+
59 | PosixPath().with_suffix("py")
638+
60 | PurePath().with_suffix("py")
639+
61 | PurePosixPath().with_suffix("py")
640+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH210
641+
62 | PureWindowsPath().with_suffix("py")
642+
63 | WindowsPath().with_suffix("py")
643+
|
644+
= help: Add a leading dot
645+
646+
Unsafe fix
647+
58 58 | Path().with_suffix("py")
648+
59 59 | PosixPath().with_suffix("py")
649+
60 60 | PurePath().with_suffix("py")
650+
61 |-PurePosixPath().with_suffix("py")
651+
61 |+PurePosixPath().with_suffix(".py")
652+
62 62 | PureWindowsPath().with_suffix("py")
653+
63 63 | WindowsPath().with_suffix("py")
654+
64 64 |
655+
656+
PTH210.py:62:1: PTH210 [*] Dotless suffix passed to `.with_suffix()`
657+
|
658+
60 | PurePath().with_suffix("py")
659+
61 | PurePosixPath().with_suffix("py")
660+
62 | PureWindowsPath().with_suffix("py")
661+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH210
662+
63 | WindowsPath().with_suffix("py")
663+
|
664+
= help: Add a leading dot
665+
666+
Unsafe fix
667+
59 59 | PosixPath().with_suffix("py")
668+
60 60 | PurePath().with_suffix("py")
669+
61 61 | PurePosixPath().with_suffix("py")
670+
62 |-PureWindowsPath().with_suffix("py")
671+
62 |+PureWindowsPath().with_suffix(".py")
672+
63 63 | WindowsPath().with_suffix("py")
673+
64 64 |
674+
65 65 | ### No errors
675+
676+
PTH210.py:63:1: PTH210 [*] Dotless suffix passed to `.with_suffix()`
677+
|
678+
61 | PurePosixPath().with_suffix("py")
679+
62 | PureWindowsPath().with_suffix("py")
680+
63 | WindowsPath().with_suffix("py")
681+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH210
682+
64 |
683+
65 | ### No errors
684+
|
685+
= help: Add a leading dot
686+
687+
Unsafe fix
688+
60 60 | PurePath().with_suffix("py")
689+
61 61 | PurePosixPath().with_suffix("py")
690+
62 62 | PureWindowsPath().with_suffix("py")
691+
63 |-WindowsPath().with_suffix("py")
692+
63 |+WindowsPath().with_suffix(".py")
693+
64 64 |
694+
65 65 | ### No errors
695+
66 66 | path.with_suffix()

0 commit comments

Comments
 (0)