From a1e65a92bddde10118ff22cb7bda7f283a462101 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 15 Jan 2024 16:10:40 +0000 Subject: [PATCH] Move `is_tuple_parenthesized` from the formatter to `ruff_python_ast` (#9533) This allows it to be used in the linter as well as the formatter. It will be useful in #9474 --- crates/ruff_python_ast/src/nodes.rs | 32 ++++++++++++++++ .../src/comments/placement.rs | 3 +- .../src/expression/expr_tuple.rs | 38 ++----------------- .../src/expression/mod.rs | 5 +-- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index c281a7b86e..58d9656607 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -8,6 +8,7 @@ use std::slice::{Iter, IterMut}; use itertools::Itertools; +use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::{int, LiteralExpressionRef}; @@ -1801,6 +1802,37 @@ impl From for Expr { } } +impl ExprTuple { + /// Return `true` if a tuple is parenthesized in the source code. + pub fn is_parenthesized(&self, source: &str) -> bool { + let Some(elt) = self.elts.first() else { + return true; + }; + + // Count the number of open parentheses between the start of the tuple and the first element. + let open_parentheses_count = + SimpleTokenizer::new(source, TextRange::new(self.start(), elt.start())) + .skip_trivia() + .filter(|token| token.kind() == SimpleTokenKind::LParen) + .count(); + if open_parentheses_count == 0 { + return false; + } + + // Count the number of parentheses between the end of the first element and its trailing comma. + let close_parentheses_count = + SimpleTokenizer::new(source, TextRange::new(elt.end(), self.end())) + .skip_trivia() + .take_while(|token| token.kind() != SimpleTokenKind::Comma) + .filter(|token| token.kind() == SimpleTokenKind::RParen) + .count(); + + // If the number of open parentheses is greater than the number of close parentheses, the tuple + // is parenthesized. + open_parentheses_count > close_parentheses_count + } +} + /// See also [Slice](https://docs.python.org/3/library/ast.html#ast.Slice) #[derive(Clone, Debug, PartialEq)] pub struct ExprSlice { diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 4b667fd236..2d958ebee9 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -14,7 +14,6 @@ use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::comments::visitor::{CommentPlacement, DecoratedComment}; use crate::expression::expr_generator_exp::is_generator_parenthesized; use crate::expression::expr_slice::{assign_comment_in_slice, ExprSliceCommentSection}; -use crate::expression::expr_tuple::is_tuple_parenthesized; use crate::other::parameters::{ assign_argument_separator_comment_placement, find_parameter_separators, }; @@ -294,7 +293,7 @@ fn handle_enclosed_comment<'a>( | AnyNodeRef::ExprSet(_) | AnyNodeRef::ExprListComp(_) | AnyNodeRef::ExprSetComp(_) => handle_bracketed_end_of_line_comment(comment, locator), - AnyNodeRef::ExprTuple(tuple) if is_tuple_parenthesized(tuple, locator.contents()) => { + AnyNodeRef::ExprTuple(tuple) if tuple.is_parenthesized(locator.contents()) => { handle_bracketed_end_of_line_comment(comment, locator) } AnyNodeRef::ExprGeneratorExp(generator) diff --git a/crates/ruff_python_formatter/src/expression/expr_tuple.rs b/crates/ruff_python_formatter/src/expression/expr_tuple.rs index 39236c7127..9db272c611 100644 --- a/crates/ruff_python_formatter/src/expression/expr_tuple.rs +++ b/crates/ruff_python_formatter/src/expression/expr_tuple.rs @@ -1,8 +1,7 @@ use ruff_formatter::{format_args, write, FormatRuleWithOptions}; use ruff_python_ast::AnyNodeRef; use ruff_python_ast::ExprTuple; -use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; -use ruff_text_size::{Ranged, TextRange}; +use ruff_text_size::Ranged; use crate::builders::parenthesize_if_expands; use crate::comments::SourceComment; @@ -137,9 +136,7 @@ impl FormatNodeRule for FormatExprTuple { return empty_parenthesized("(", dangling, ")").fmt(f); } [single] => match self.parentheses { - TupleParentheses::Preserve - if !is_tuple_parenthesized(item, f.context().source()) => - { + TupleParentheses::Preserve if !item.is_parenthesized(f.context().source()) => { write!(f, [single.format(), token(",")]) } _ => @@ -155,7 +152,7 @@ impl FormatNodeRule for FormatExprTuple { // // Unlike other expression parentheses, tuple parentheses are part of the range of the // tuple itself. - _ if is_tuple_parenthesized(item, f.context().source()) + _ if item.is_parenthesized(f.context().source()) && !(self.parentheses == TupleParentheses::NeverPreserve && dangling.is_empty()) => { @@ -223,32 +220,3 @@ impl NeedsParentheses for ExprTuple { OptionalParentheses::Never } } - -/// Return `true` if a tuple is parenthesized in the source code. -pub(crate) fn is_tuple_parenthesized(tuple: &ExprTuple, source: &str) -> bool { - let Some(elt) = tuple.elts.first() else { - return true; - }; - - // Count the number of open parentheses between the start of the tuple and the first element. - let open_parentheses_count = - SimpleTokenizer::new(source, TextRange::new(tuple.start(), elt.start())) - .skip_trivia() - .filter(|token| token.kind() == SimpleTokenKind::LParen) - .count(); - if open_parentheses_count == 0 { - return false; - } - - // Count the number of parentheses between the end of the first element and its trailing comma. - let close_parentheses_count = - SimpleTokenizer::new(source, TextRange::new(elt.end(), tuple.end())) - .skip_trivia() - .take_while(|token| token.kind() != SimpleTokenKind::Comma) - .filter(|token| token.kind() == SimpleTokenKind::RParen) - .count(); - - // If the number of open parentheses is greater than the number of close parentheses, the tuple - // is parenthesized. - open_parentheses_count > close_parentheses_count -} diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index d22fdecda2..b63ae81ed1 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -15,7 +15,6 @@ use crate::builders::parenthesize_if_expands; use crate::comments::{leading_comments, trailing_comments, LeadingDanglingTrailingComments}; use crate::context::{NodeLevel, WithNodeLevel}; use crate::expression::expr_generator_exp::is_generator_parenthesized; -use crate::expression::expr_tuple::is_tuple_parenthesized; use crate::expression::parentheses::{ is_expression_parenthesized, optional_parentheses, parenthesized, HuggingStyle, NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize, @@ -670,7 +669,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> { return; } - Expr::Tuple(tuple) if is_tuple_parenthesized(tuple, self.context.source()) => { + Expr::Tuple(tuple) if tuple.is_parenthesized(self.context.source()) => { self.any_parenthesized_expressions = true; // The values are always parenthesized, don't visit. return; @@ -1059,7 +1058,7 @@ pub(crate) fn has_own_parentheses( } } - Expr::Tuple(tuple) if is_tuple_parenthesized(tuple, context.source()) => { + Expr::Tuple(tuple) if tuple.is_parenthesized(context.source()) => { if !tuple.elts.is_empty() || context.comments().has_dangling(AnyNodeRef::from(expr)) { Some(OwnParentheses::NonEmpty) } else {