1
- use ruff_diagnostics:: { AlwaysFixableViolation , Diagnostic , Edit , Fix } ;
1
+ use std:: ops:: Range ;
2
+
3
+ use ruff_diagnostics:: { AlwaysFixableViolation , Applicability , Diagnostic , Edit , Fix } ;
2
4
use ruff_macros:: { derive_message_formats, ViolationMetadata } ;
3
- use ruff_python_ast:: { self as ast, Expr , ExprCall } ;
5
+ use ruff_python_ast:: parenthesize:: parenthesized_range;
6
+ use ruff_python_ast:: { AstNode , Expr , ExprBinOp , ExprCall , Operator } ;
7
+ use ruff_python_semantic:: SemanticModel ;
8
+ use ruff_python_trivia:: CommentRanges ;
9
+ use ruff_text_size:: { Ranged , TextRange } ;
4
10
5
11
use crate :: checkers:: ast:: Checker ;
12
+ use crate :: fix:: edits:: { remove_argument, Parentheses } ;
6
13
7
14
/// ## What it does
8
15
/// Checks for `pathlib.Path` objects that are initialized with the current
@@ -43,7 +50,17 @@ impl AlwaysFixableViolation for PathConstructorCurrentDirectory {
43
50
}
44
51
45
52
/// PTH201
46
- pub ( crate ) fn path_constructor_current_directory ( checker : & mut Checker , expr : & Expr , func : & Expr ) {
53
+ pub ( crate ) fn path_constructor_current_directory ( checker : & mut Checker , call : & ExprCall ) {
54
+ let applicability = |range| {
55
+ if checker. comment_ranges ( ) . intersects ( range) {
56
+ Applicability :: Unsafe
57
+ } else {
58
+ Applicability :: Safe
59
+ }
60
+ } ;
61
+
62
+ let ( func, arguments) = ( & call. func , & call. arguments ) ;
63
+
47
64
if !checker
48
65
. semantic ( )
49
66
. resolve_qualified_name ( func)
@@ -54,21 +71,75 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &E
54
71
return ;
55
72
}
56
73
57
- let Expr :: Call ( ExprCall { arguments, .. } ) = expr else {
74
+ if !arguments. keywords . is_empty ( ) {
75
+ return ;
76
+ }
77
+
78
+ let [ Expr :: StringLiteral ( arg) ] = & * arguments. args else {
58
79
return ;
59
80
} ;
60
81
61
- if !arguments . keywords . is_empty ( ) {
82
+ if !matches ! ( arg . value . to_str ( ) , "" | "." ) {
62
83
return ;
63
84
}
64
85
65
- let [ Expr :: StringLiteral ( ast:: ExprStringLiteral { value, range } ) ] = & * arguments. args else {
66
- return ;
86
+ let mut diagnostic = Diagnostic :: new ( PathConstructorCurrentDirectory , arg. range ( ) ) ;
87
+
88
+ match parent_and_next_path_fragment_range (
89
+ checker. semantic ( ) ,
90
+ checker. comment_ranges ( ) ,
91
+ checker. source ( ) ,
92
+ ) {
93
+ Some ( ( parent_range, next_fragment_range) ) => {
94
+ let next_fragment_expr = checker. locator ( ) . slice ( next_fragment_range) ;
95
+ let call_expr = checker. locator ( ) . slice ( call. range ( ) ) ;
96
+
97
+ let relative_argument_range: Range < usize > = {
98
+ let range = arg. range ( ) - call. start ( ) ;
99
+ range. start ( ) . into ( ) ..range. end ( ) . into ( )
100
+ } ;
101
+
102
+ let mut new_call_expr = call_expr. to_string ( ) ;
103
+ new_call_expr. replace_range ( relative_argument_range, next_fragment_expr) ;
104
+
105
+ let edit = Edit :: range_replacement ( new_call_expr, parent_range) ;
106
+
107
+ diagnostic. set_fix ( Fix :: applicable_edit ( edit, applicability ( parent_range) ) ) ;
108
+ }
109
+ None => diagnostic. try_set_fix ( || {
110
+ let edit = remove_argument ( arg, arguments, Parentheses :: Preserve , checker. source ( ) ) ?;
111
+ Ok ( Fix :: applicable_edit ( edit, applicability ( call. range ( ) ) ) )
112
+ } ) ,
67
113
} ;
68
114
69
- if matches ! ( value. to_str( ) , "" | "." ) {
70
- let mut diagnostic = Diagnostic :: new ( PathConstructorCurrentDirectory , * range) ;
71
- diagnostic. set_fix ( Fix :: safe_edit ( Edit :: range_deletion ( * range) ) ) ;
72
- checker. diagnostics . push ( diagnostic) ;
115
+ checker. diagnostics . push ( diagnostic) ;
116
+ }
117
+
118
+ fn parent_and_next_path_fragment_range (
119
+ semantic : & SemanticModel ,
120
+ comment_ranges : & CommentRanges ,
121
+ source : & str ,
122
+ ) -> Option < ( TextRange , TextRange ) > {
123
+ let parent = semantic. current_expression_parent ( ) ?;
124
+
125
+ let Expr :: BinOp ( parent @ ExprBinOp { op, right, .. } ) = parent else {
126
+ return None ;
127
+ } ;
128
+
129
+ let range = right. range ( ) ;
130
+
131
+ if !matches ! ( op, Operator :: Div ) {
132
+ return None ;
73
133
}
134
+
135
+ Some ( (
136
+ parent. range ( ) ,
137
+ parenthesized_range (
138
+ right. into ( ) ,
139
+ parent. as_any_node_ref ( ) ,
140
+ comment_ranges,
141
+ source,
142
+ )
143
+ . unwrap_or ( range) ,
144
+ ) )
74
145
}
0 commit comments