@@ -26,41 +26,109 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
2626 // PTH100
2727 [ "os" , "path" , "abspath" ] => OsPathAbspath . into ( ) ,
2828 // PTH101
29- [ "os" , "chmod" ] => OsChmod . into ( ) ,
29+ [ "os" , "chmod" ] => {
30+ // `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
31+ // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.chmod)
32+ // ```text
33+ // 0 1 2 3
34+ // os.chmod(path, mode, *, dir_fd=None, follow_symlinks=True)
35+ // ```
36+ if call
37+ . arguments
38+ . find_argument_value ( "path" , 0 )
39+ . is_some_and ( |expr| is_file_descriptor ( expr, checker. semantic ( ) ) )
40+ || is_argument_non_default ( & call. arguments , "dir_fd" , 2 )
41+ {
42+ return ;
43+ }
44+ OsChmod . into ( )
45+ }
3046 // PTH102
3147 [ "os" , "makedirs" ] => OsMakedirs . into ( ) ,
3248 // PTH103
33- [ "os" , "mkdir" ] => OsMkdir . into ( ) ,
49+ [ "os" , "mkdir" ] => {
50+ // `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
51+ // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.mkdir)
52+ // ```text
53+ // 0 1 2
54+ // os.mkdir(path, mode=0o777, *, dir_fd=None)
55+ // ```
56+ if is_argument_non_default ( & call. arguments , "dir_fd" , 2 ) {
57+ return ;
58+ }
59+ OsMkdir . into ( )
60+ }
3461 // PTH104
3562 [ "os" , "rename" ] => {
3663 // `src_dir_fd` and `dst_dir_fd` are not supported by pathlib, so check if they are
37- // are set to non-default values.
64+ // set to non-default values.
3865 // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.rename)
3966 // ```text
4067 // 0 1 2 3
4168 // os.rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None)
4269 // ```
43- if call
44- . arguments
45- . find_argument_value ( "src_dir_fd" , 2 )
46- . is_some_and ( |expr| !expr. is_none_literal_expr ( ) )
47- || call
48- . arguments
49- . find_argument_value ( "dst_dir_fd" , 3 )
50- . is_some_and ( |expr| !expr. is_none_literal_expr ( ) )
70+ if is_argument_non_default ( & call. arguments , "src_dir_fd" , 2 )
71+ || is_argument_non_default ( & call. arguments , "dst_dir_fd" , 3 )
5172 {
5273 return ;
5374 }
5475 OsRename . into ( )
5576 }
5677 // PTH105
57- [ "os" , "replace" ] => OsReplace . into ( ) ,
78+ [ "os" , "replace" ] => {
79+ // `src_dir_fd` and `dst_dir_fd` are not supported by pathlib, so check if they are
80+ // set to non-default values.
81+ // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.replace)
82+ // ```text
83+ // 0 1 2 3
84+ // os.replace(src, dst, *, src_dir_fd=None, dst_dir_fd=None)
85+ // ```
86+ if is_argument_non_default ( & call. arguments , "src_dir_fd" , 2 )
87+ || is_argument_non_default ( & call. arguments , "dst_dir_fd" , 3 )
88+ {
89+ return ;
90+ }
91+ OsReplace . into ( )
92+ }
5893 // PTH106
59- [ "os" , "rmdir" ] => OsRmdir . into ( ) ,
94+ [ "os" , "rmdir" ] => {
95+ // `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
96+ // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.rmdir)
97+ // ```text
98+ // 0 1
99+ // os.rmdir(path, *, dir_fd=None)
100+ // ```
101+ if is_argument_non_default ( & call. arguments , "dir_fd" , 1 ) {
102+ return ;
103+ }
104+ OsRmdir . into ( )
105+ }
60106 // PTH107
61- [ "os" , "remove" ] => OsRemove . into ( ) ,
107+ [ "os" , "remove" ] => {
108+ // `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
109+ // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.remove)
110+ // ```text
111+ // 0 1
112+ // os.remove(path, *, dir_fd=None)
113+ // ```
114+ if is_argument_non_default ( & call. arguments , "dir_fd" , 1 ) {
115+ return ;
116+ }
117+ OsRemove . into ( )
118+ }
62119 // PTH108
63- [ "os" , "unlink" ] => OsUnlink . into ( ) ,
120+ [ "os" , "unlink" ] => {
121+ // `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
122+ // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.unlink)
123+ // ```text
124+ // 0 1
125+ // os.unlink(path, *, dir_fd=None)
126+ // ```
127+ if is_argument_non_default ( & call. arguments , "dir_fd" , 1 ) {
128+ return ;
129+ }
130+ OsUnlink . into ( )
131+ }
64132 // PTH109
65133 [ "os" , "getcwd" ] => OsGetcwd . into ( ) ,
66134 [ "os" , "getcwdb" ] => OsGetcwd . into ( ) ,
@@ -76,10 +144,17 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
76144 [ "os" , "path" , "islink" ] => OsPathIslink . into ( ) ,
77145 // PTH116
78146 [ "os" , "stat" ] => {
147+ // `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
148+ // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.stat)
149+ // ```text
150+ // 0 1 2
151+ // os.stat(path, *, dir_fd=None, follow_symlinks=True)
152+ // ```
79153 if call
80154 . arguments
81- . find_positional ( 0 )
155+ . find_argument_value ( "path" , 0 )
82156 . is_some_and ( |expr| is_file_descriptor ( expr, checker. semantic ( ) ) )
157+ || is_argument_non_default ( & call. arguments , "dir_fd" , 1 )
83158 {
84159 return ;
85160 }
@@ -148,13 +223,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
148223 Expr :: BooleanLiteral ( ExprBooleanLiteral { value: true , .. } )
149224 )
150225 } )
226+ || is_argument_non_default ( & call. arguments , "opener" , 7 )
151227 || call
152228 . arguments
153- . find_argument_value ( "opener" , 7 )
154- . is_some_and ( |expr| !expr. is_none_literal_expr ( ) )
155- || call
156- . arguments
157- . find_positional ( 0 )
229+ . find_argument_value ( "file" , 0 )
158230 . is_some_and ( |expr| is_file_descriptor ( expr, checker. semantic ( ) ) )
159231 {
160232 return ;
@@ -164,17 +236,53 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
164236 // PTH124
165237 [ "py" , "path" , "local" ] => PyPath . into ( ) ,
166238 // PTH207
167- [ "glob" , "glob" ] => Glob {
168- function : "glob" . to_string ( ) ,
239+ [ "glob" , "glob" ] => {
240+ // `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
241+ // Signature as of Python 3.13 (https://docs.python.org/3/library/glob.html#glob.glob)
242+ // ```text
243+ // 0 1 2 3 4
244+ // glob.glob(pathname, *, root_dir=None, dir_fd=None, recursive=False, include_hidden=False)
245+ // ```
246+ if is_argument_non_default ( & call. arguments , "dir_fd" , 2 ) {
247+ return ;
248+ }
249+
250+ Glob {
251+ function : "glob" . to_string ( ) ,
252+ }
253+ . into ( )
169254 }
170- . into ( ) ,
171- [ "glob" , "iglob" ] => Glob {
172- function : "iglob" . to_string ( ) ,
255+
256+ [ "glob" , "iglob" ] => {
257+ // `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
258+ // Signature as of Python 3.13 (https://docs.python.org/3/library/glob.html#glob.iglob)
259+ // ```text
260+ // 0 1 2 3 4
261+ // glob.iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False, include_hidden=False)
262+ // ```
263+ if is_argument_non_default ( & call. arguments , "dir_fd" , 2 ) {
264+ return ;
265+ }
266+
267+ Glob {
268+ function : "iglob" . to_string ( ) ,
269+ }
270+ . into ( )
173271 }
174- . into ( ) ,
175272 // PTH115
176273 // Python 3.9+
177- [ "os" , "readlink" ] if checker. target_version ( ) >= PythonVersion :: PY39 => OsReadlink . into ( ) ,
274+ [ "os" , "readlink" ] if checker. target_version ( ) >= PythonVersion :: PY39 => {
275+ // `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
276+ // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.readlink)
277+ // ```text
278+ // 0 1
279+ // os.readlink(path, *, dir_fd=None)
280+ // ```
281+ if is_argument_non_default ( & call. arguments , "dir_fd" , 1 ) {
282+ return ;
283+ }
284+ OsReadlink . into ( )
285+ }
178286 // PTH208
179287 [ "os" , "listdir" ] => {
180288 if call
@@ -224,3 +332,10 @@ fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> {
224332 _ => None ,
225333 }
226334}
335+
336+ /// Returns `true` if argument `name` is set to a non-default `None` value.
337+ fn is_argument_non_default ( arguments : & ast:: Arguments , name : & str , position : usize ) -> bool {
338+ arguments
339+ . find_argument_value ( name, position)
340+ . is_some_and ( |expr| !expr. is_none_literal_expr ( ) )
341+ }
0 commit comments