|
1 | | -use std::cmp::Ordering; |
2 | | - |
3 | 1 | use ast::helpers::comment_indentation_after; |
4 | 2 | use ruff_python_ast::whitespace::indentation; |
5 | 3 | use ruff_python_ast::{ |
6 | | - self as ast, AnyNodeRef, Comprehension, Expr, ModModule, Parameter, Parameters, |
| 4 | + self as ast, AnyNodeRef, Comprehension, Expr, ModModule, Parameter, Parameters, StringLike, |
7 | 5 | }; |
8 | 6 | use ruff_python_trivia::{ |
9 | 7 | find_only_token_in_range, first_non_trivia_token, indentation_at_offset, BackwardsTokenizer, |
10 | 8 | CommentRanges, SimpleToken, SimpleTokenKind, SimpleTokenizer, |
11 | 9 | }; |
12 | 10 | use ruff_source_file::LineRanges; |
13 | 11 | use ruff_text_size::{Ranged, TextLen, TextRange}; |
| 12 | +use std::cmp::Ordering; |
14 | 13 |
|
15 | 14 | use crate::comments::visitor::{CommentPlacement, DecoratedComment}; |
16 | 15 | use crate::expression::expr_slice::{assign_comment_in_slice, ExprSliceCommentSection}; |
| 16 | +use crate::expression::parentheses::is_expression_parenthesized; |
17 | 17 | use crate::other::parameters::{ |
18 | 18 | assign_argument_separator_comment_placement, find_parameter_separators, |
19 | 19 | }; |
@@ -355,6 +355,41 @@ fn handle_enclosed_comment<'a>( |
355 | 355 | AnyNodeRef::ExprGenerator(generator) if generator.parenthesized => { |
356 | 356 | handle_bracketed_end_of_line_comment(comment, source) |
357 | 357 | } |
| 358 | + AnyNodeRef::StmtReturn(_) => { |
| 359 | + handle_trailing_implicit_concatenated_string_comment(comment, comment_ranges, source) |
| 360 | + } |
| 361 | + AnyNodeRef::StmtAssign(assignment) |
| 362 | + if comment.preceding_node().is_some_and(|preceding| { |
| 363 | + preceding.ptr_eq(AnyNodeRef::from(&*assignment.value)) |
| 364 | + }) => |
| 365 | + { |
| 366 | + handle_trailing_implicit_concatenated_string_comment(comment, comment_ranges, source) |
| 367 | + } |
| 368 | + AnyNodeRef::StmtAnnAssign(assignment) |
| 369 | + if comment.preceding_node().is_some_and(|preceding| { |
| 370 | + assignment |
| 371 | + .value |
| 372 | + .as_deref() |
| 373 | + .is_some_and(|value| preceding.ptr_eq(value.into())) |
| 374 | + }) => |
| 375 | + { |
| 376 | + handle_trailing_implicit_concatenated_string_comment(comment, comment_ranges, source) |
| 377 | + } |
| 378 | + AnyNodeRef::StmtAugAssign(assignment) |
| 379 | + if comment.preceding_node().is_some_and(|preceding| { |
| 380 | + preceding.ptr_eq(AnyNodeRef::from(&*assignment.value)) |
| 381 | + }) => |
| 382 | + { |
| 383 | + handle_trailing_implicit_concatenated_string_comment(comment, comment_ranges, source) |
| 384 | + } |
| 385 | + AnyNodeRef::StmtTypeAlias(assignment) |
| 386 | + if comment.preceding_node().is_some_and(|preceding| { |
| 387 | + preceding.ptr_eq(AnyNodeRef::from(&*assignment.value)) |
| 388 | + }) => |
| 389 | + { |
| 390 | + handle_trailing_implicit_concatenated_string_comment(comment, comment_ranges, source) |
| 391 | + } |
| 392 | + |
358 | 393 | _ => CommentPlacement::Default(comment), |
359 | 394 | } |
360 | 395 | } |
@@ -2086,6 +2121,75 @@ fn handle_comprehension_comment<'a>( |
2086 | 2121 | CommentPlacement::Default(comment) |
2087 | 2122 | } |
2088 | 2123 |
|
| 2124 | +/// Handle end-of-line comments for parenthesized implicitly concatenated strings when used in |
| 2125 | +/// a `FormatStatementLastExpression` context: |
| 2126 | +/// |
| 2127 | +/// ```python |
| 2128 | +/// a = ( |
| 2129 | +/// "a" |
| 2130 | +/// "b" |
| 2131 | +/// "c" # comment |
| 2132 | +/// ) |
| 2133 | +/// ``` |
| 2134 | +/// |
| 2135 | +/// `# comment` is a trailing comment of the last part and not a trailing comment of the entire f-string. |
| 2136 | +/// Associating the comment with the last part is important or the assignment formatting might move |
| 2137 | +/// the comment at the end of the assignment, making it impossible to suppress an error for the last part. |
| 2138 | +/// |
| 2139 | +/// On the other hand, `# comment` is a trailing end-of-line f-string comment for: |
| 2140 | +/// |
| 2141 | +/// ```python |
| 2142 | +/// a = ( |
| 2143 | +/// "a" "b" "c" # comment |
| 2144 | +/// ) |
| 2145 | +/// |
| 2146 | +/// a = ( |
| 2147 | +/// "a" |
| 2148 | +/// "b" |
| 2149 | +/// "c" |
| 2150 | +/// ) # comment |
| 2151 | +/// ``` |
| 2152 | +/// |
| 2153 | +/// Associating the comment with the f-string is desired in those cases because it allows |
| 2154 | +/// joining the string literals into a single string literal if it fits on the line. |
| 2155 | +fn handle_trailing_implicit_concatenated_string_comment<'a>( |
| 2156 | + comment: DecoratedComment<'a>, |
| 2157 | + comment_ranges: &CommentRanges, |
| 2158 | + source: &str, |
| 2159 | +) -> CommentPlacement<'a> { |
| 2160 | + if !comment.line_position().is_end_of_line() { |
| 2161 | + return CommentPlacement::Default(comment); |
| 2162 | + } |
| 2163 | + |
| 2164 | + let Some(string_like) = comment |
| 2165 | + .preceding_node() |
| 2166 | + .and_then(|preceding| StringLike::try_from(preceding).ok()) |
| 2167 | + else { |
| 2168 | + return CommentPlacement::Default(comment); |
| 2169 | + }; |
| 2170 | + |
| 2171 | + let mut parts = string_like.parts(); |
| 2172 | + |
| 2173 | + let (Some(last), Some(second_last)) = (parts.next_back(), parts.next_back()) else { |
| 2174 | + return CommentPlacement::Default(comment); |
| 2175 | + }; |
| 2176 | + |
| 2177 | + if source.contains_line_break(TextRange::new(second_last.end(), last.start())) |
| 2178 | + && is_expression_parenthesized(string_like.as_expression_ref(), comment_ranges, source) |
| 2179 | + { |
| 2180 | + let range = TextRange::new(last.end(), comment.start()); |
| 2181 | + |
| 2182 | + if !SimpleTokenizer::new(source, range) |
| 2183 | + .skip_trivia() |
| 2184 | + .any(|token| token.kind() == SimpleTokenKind::RParen) |
| 2185 | + { |
| 2186 | + return CommentPlacement::trailing(AnyNodeRef::from(last), comment); |
| 2187 | + } |
| 2188 | + } |
| 2189 | + |
| 2190 | + CommentPlacement::Default(comment) |
| 2191 | +} |
| 2192 | + |
2089 | 2193 | /// Returns `true` if the parameters are parenthesized (as in a function definition), or `false` if |
2090 | 2194 | /// not (as in a lambda). |
2091 | 2195 | fn are_parameters_parenthesized(parameters: &Parameters, contents: &str) -> bool { |
|
0 commit comments