1- use ruff_diagnostics:: { AlwaysFixableViolation , Diagnostic , Edit , Fix } ;
1+ use std:: ops:: Range ;
2+
3+ use ruff_diagnostics:: { AlwaysFixableViolation , Applicability , Diagnostic , Edit , Fix } ;
24use 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 } ;
410
511use crate :: checkers:: ast:: Checker ;
12+ use crate :: fix:: edits:: { remove_argument, Parentheses } ;
613
714/// ## What it does
815/// Checks for `pathlib.Path` objects that are initialized with the current
@@ -43,7 +50,17 @@ impl AlwaysFixableViolation for PathConstructorCurrentDirectory {
4350}
4451
4552/// 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+
4764 if !checker
4865 . semantic ( )
4966 . resolve_qualified_name ( func)
@@ -54,21 +71,75 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &E
5471 return ;
5572 }
5673
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 {
5879 return ;
5980 } ;
6081
61- if !arguments . keywords . is_empty ( ) {
82+ if !matches ! ( arg . value . to_str ( ) , "" | "." ) {
6283 return ;
6384 }
6485
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+ } ) ,
67113 } ;
68114
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 ;
73133 }
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+ ) )
74145}
0 commit comments