diff --git a/Cargo.lock b/Cargo.lock index 9487cdddf1a44c..9b96ccbe2c341a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3606,6 +3606,7 @@ dependencies = [ "itertools 0.15.0", "ruff_source_file", "ruff_text_size", + "rustc-hash", "unicode-ident", ] diff --git a/crates/ruff_benchmark/benches/formatter.rs b/crates/ruff_benchmark/benches/formatter.rs index 468ade96958867..06a883e11e7bad 100644 --- a/crates/ruff_benchmark/benches/formatter.rs +++ b/crates/ruff_benchmark/benches/formatter.rs @@ -9,7 +9,7 @@ use ruff_benchmark::{ }; use ruff_python_formatter::{PreviewMode, PyFormatOptions, format_module_ast}; use ruff_python_parser::{Mode, ParseOptions, parse}; -use ruff_python_trivia::CommentRanges; +use ruff_python_trivia::TriviaRanges; #[cfg(target_os = "windows")] #[global_allocator] @@ -53,11 +53,11 @@ fn benchmark_formatter(criterion: &mut Criterion) { .expect("Input should be a valid Python code"); b.iter(|| { - let comment_ranges = CommentRanges::from(parsed.tokens()); + let trivia_ranges = TriviaRanges::from(parsed.tokens()); let options = PyFormatOptions::from_extension(Path::new(case.name())) .with_preview(PreviewMode::Enabled); let formatted = - format_module_ast(&parsed, &comment_ranges, case.code(), options) + format_module_ast(&parsed, &trivia_ranges, case.code(), options) .expect("Formatting to succeed"); formatted.print().expect("Printing to succeed") diff --git a/crates/ruff_python_ast/src/token/tokens.rs b/crates/ruff_python_ast/src/token/tokens.rs index c71793d624858e..69eb99de5a5a38 100644 --- a/crates/ruff_python_ast/src/token/tokens.rs +++ b/crates/ruff_python_ast/src/token/tokens.rs @@ -1,8 +1,9 @@ use std::{iter::FusedIterator, ops::Deref}; use super::{Token, TokenKind}; -use ruff_python_trivia::CommentRanges; +use ruff_python_trivia::{CommentRanges, ParenthesizedExpressions, TriviaRanges}; use ruff_text_size::{Ranged as _, TextRange, TextSize}; +use rustc_hash::FxHashSet; /// Tokens represents a vector of lexed [`Token`]. #[derive(Debug, Clone, PartialEq, Eq)] @@ -279,15 +280,62 @@ impl FusedIterator for TokenAt {} impl From<&Tokens> for CommentRanges { fn from(tokens: &Tokens) -> Self { let mut ranges = vec![]; + for token in tokens { if token.kind() == TokenKind::Comment { ranges.push(token.range()); } } + CommentRanges::new(ranges) } } +impl From<&Tokens> for TriviaRanges { + fn from(tokens: &Tokens) -> Self { + let mut comments = vec![]; + let mut parenthesized = FxHashSet::default(); + let mut stack = Vec::>::new(); + let mut previous_end = None; + + for token in tokens { + if token.kind() == TokenKind::Comment { + comments.push(token.range()); + } + + if token.kind().is_trivia() { + continue; + } + + match token.kind() { + TokenKind::Lpar => { + if let Some(start) = stack.last_mut() { + start.get_or_insert(token.start()); + } + stack.push(None); + } + TokenKind::Rpar => { + if let (Some(Some(start)), Some(end)) = (stack.pop(), previous_end) { + parenthesized.insert(TextRange::new(start, end)); + } + } + _ => { + if let Some(start) = stack.last_mut() { + start.get_or_insert(token.start()); + } + } + } + + previous_end = Some(token.end()); + } + + TriviaRanges::new( + CommentRanges::new(comments), + ParenthesizedExpressions::new(parenthesized), + ) + } +} + /// An iterator over the [`Token`]s with context. /// /// This struct is created by the [`iter_with_context`] method on [`Tokens`]. Refer to its diff --git a/crates/ruff_python_formatter/src/cli.rs b/crates/ruff_python_formatter/src/cli.rs index df289f08d836dd..b8a86ef7727753 100644 --- a/crates/ruff_python_formatter/src/cli.rs +++ b/crates/ruff_python_formatter/src/cli.rs @@ -8,7 +8,7 @@ use clap::{Parser, ValueEnum}; use ruff_formatter::SourceCode; use ruff_python_ast::{PySourceType, PythonVersion}; use ruff_python_parser::{ParseOptions, parse}; -use ruff_python_trivia::CommentRanges; +use ruff_python_trivia::TriviaRanges; use ruff_text_size::Ranged; use crate::comments::collect_comments; @@ -70,15 +70,15 @@ pub fn format_and_debug_print(source: &str, cli: &Cli, source_path: &Path) -> Re .with_target_version(cli.target_version); let source_code = SourceCode::new(source); - let comment_ranges = CommentRanges::from(parsed.tokens()); - let formatted = format_module_ast(&parsed, &comment_ranges, source, options) - .context("Failed to format node")?; + let trivia = TriviaRanges::from(parsed.tokens()); + let formatted = + format_module_ast(&parsed, &trivia, source, options).context("Failed to format node")?; if cli.print_ir { println!("{}", formatted.document().display(source_code)); } if cli.print_comments { // Print preceding, following and enclosing nodes - let decorated_comments = collect_comments(parsed.syntax(), source_code, &comment_ranges); + let decorated_comments = collect_comments(parsed.syntax(), source_code, trivia.comments()); if !decorated_comments.is_empty() { println!("# Comment decoration: Range, Preceding, Following, Enclosing, Comment"); } diff --git a/crates/ruff_python_formatter/src/comments/debug.rs b/crates/ruff_python_formatter/src/comments/debug.rs index 0adbba24f36139..6d974ed4b6c41d 100644 --- a/crates/ruff_python_formatter/src/comments/debug.rs +++ b/crates/ruff_python_formatter/src/comments/debug.rs @@ -186,7 +186,7 @@ mod tests { use ruff_formatter::SourceCode; use ruff_python_ast::{AnyNodeRef, AtomicNodeIndex}; use ruff_python_ast::{StmtBreak, StmtContinue}; - use ruff_python_trivia::{CommentLinePosition, CommentRanges}; + use ruff_python_trivia::CommentLinePosition; use ruff_text_size::{TextRange, TextSize}; use crate::comments::map::MultiMap; @@ -238,8 +238,7 @@ break; ), ); - let comment_ranges = CommentRanges::default(); - let comments = Comments::new(comments_map, &comment_ranges); + let comments = Comments::new(comments_map); assert_debug_snapshot!(comments.debug(source_code)); } diff --git a/crates/ruff_python_formatter/src/comments/mod.rs b/crates/ruff_python_formatter/src/comments/mod.rs index 07fc789f34dac7..4b7ce3d05fe4c1 100644 --- a/crates/ruff_python_formatter/src/comments/mod.rs +++ b/crates/ruff_python_formatter/src/comments/mod.rs @@ -97,7 +97,7 @@ pub(crate) use format::{ }; use ruff_formatter::{SourceCode, SourceCodeSlice}; use ruff_python_ast::AnyNodeRef; -use ruff_python_trivia::{CommentLinePosition, CommentRanges, SuppressionKind}; +use ruff_python_trivia::{CommentLinePosition, SuppressionKind, TriviaRanges}; use ruff_text_size::{Ranged, TextRange}; pub(crate) use visitor::collect_comments; @@ -219,53 +219,41 @@ pub(crate) struct Comments<'a> { } impl<'a> Comments<'a> { - fn new(comments: CommentsMap<'a>, comment_ranges: &'a CommentRanges) -> Self { + fn new(comments: CommentsMap<'a>) -> Self { Self { - data: Rc::new(CommentsData { - comments, - comment_ranges, - }), + data: Rc::new(CommentsData { comments }), } } - /// Effectively a [`Default`] implementation that works around the lifetimes for tests + /// Creates an empty comment map for tests. #[cfg(test)] - pub(crate) fn from_ranges(comment_ranges: &'a CommentRanges) -> Self { - Self { - data: Rc::new(CommentsData { - comments: CommentsMap::default(), - comment_ranges, - }), - } - } - - pub(crate) fn ranges(&self) -> &'a CommentRanges { - self.data.comment_ranges + pub(crate) fn empty() -> Self { + Self::new(CommentsMap::default()) } /// Extracts the comments from the AST. pub(crate) fn from_ast( root: impl Into>, source_code: SourceCode<'a>, - comment_ranges: &'a CommentRanges, + trivia: &'a TriviaRanges, ) -> Self { fn collect_comments<'a>( root: AnyNodeRef<'a>, source_code: SourceCode<'a>, - comment_ranges: &'a CommentRanges, + trivia: &'a TriviaRanges, ) -> Comments<'a> { - let map = if comment_ranges.is_empty() { + let map = if trivia.comments().is_empty() { CommentsMap::new() } else { - let mut builder = CommentsMapBuilder::new(source_code.as_str(), comment_ranges); - CommentsVisitor::new(source_code, comment_ranges, &mut builder).visit(root); + let mut builder = CommentsMapBuilder::new(source_code.as_str(), trivia); + CommentsVisitor::new(source_code, trivia.comments(), &mut builder).visit(root); builder.finish() }; - Comments::new(map, comment_ranges) + Comments::new(map) } - collect_comments(root.into(), source_code, comment_ranges) + collect_comments(root.into(), source_code, trivia) } /// Returns `true` if the given `node` has any comments. @@ -496,9 +484,6 @@ impl LeadingDanglingTrailingComments<'_> { #[derive(Debug)] struct CommentsData<'a> { comments: CommentsMap<'a>, - - /// We need those for backwards lexing - comment_ranges: &'a CommentRanges, } pub(crate) fn has_skip_comment(trailing_comments: &[SourceComment], source: &str) -> bool { @@ -518,13 +503,13 @@ mod tests { use ruff_formatter::SourceCode; use ruff_python_ast::{Mod, PySourceType}; use ruff_python_parser::{ParseOptions, Parsed, parse}; - use ruff_python_trivia::CommentRanges; + use ruff_python_trivia::TriviaRanges; use crate::comments::Comments; struct CommentsTestCase<'a> { parsed: Parsed, - comment_ranges: CommentRanges, + trivia: TriviaRanges, source_code: SourceCode<'a>, } @@ -534,17 +519,17 @@ mod tests { let source_type = PySourceType::Python; let parsed = parse(source, ParseOptions::from(source_type)) .expect("Expect source to be valid Python"); - let comment_ranges = CommentRanges::from(parsed.tokens()); + let trivia = TriviaRanges::from(parsed.tokens()); CommentsTestCase { parsed, - comment_ranges, + trivia, source_code, } } fn to_comments(&self) -> Comments<'_> { - Comments::from_ast(self.parsed.syntax(), self.source_code, &self.comment_ranges) + Comments::from_ast(self.parsed.syntax(), self.source_code, &self.trivia) } } diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index f86f079283a767..b7d00c790f87d0 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -4,7 +4,7 @@ use ruff_python_ast::{ self as ast, AnyNodeRef, Comprehension, Expr, ModModule, Parameter, Parameters, StringLike, }; use ruff_python_trivia::{ - BackwardsTokenizer, CommentRanges, SimpleToken, SimpleTokenKind, SimpleTokenizer, + BackwardsTokenizer, SimpleToken, SimpleTokenKind, SimpleTokenizer, TriviaRanges, find_only_token_in_range, first_non_trivia_token, indentation_at_offset, }; use ruff_source_file::LineRanges; @@ -13,7 +13,6 @@ use std::cmp::Ordering; use crate::comments::visitor::{CommentPlacement, DecoratedComment}; use crate::expression::expr_slice::{ExprSliceCommentSection, assign_comment_in_slice}; -use crate::expression::parentheses::is_expression_parenthesized; use crate::other::parameters::{ assign_argument_separator_comment_placement, find_parameter_separators, }; @@ -22,13 +21,13 @@ use crate::pattern::pattern_match_sequence::SequenceType; /// Manually attach comments to nodes that the default placement gets wrong. pub(super) fn place_comment<'a>( comment: DecoratedComment<'a>, - comment_ranges: &CommentRanges, + trivia: &TriviaRanges, source: &str, ) -> CommentPlacement<'a> { handle_parenthesized_comment(comment, source) .or_else(|comment| handle_end_of_line_comment_around_body(comment, source)) .or_else(|comment| handle_own_line_comment_around_body(comment, source)) - .or_else(|comment| handle_enclosed_comment(comment, comment_ranges, source)) + .or_else(|comment| handle_enclosed_comment(comment, trivia, source)) } /// Handle parenthesized comments. A parenthesized comment is a comment that appears within a @@ -191,7 +190,7 @@ fn handle_parenthesized_comment<'a>( /// Handle a comment that is enclosed by a node. fn handle_enclosed_comment<'a>( comment: DecoratedComment<'a>, - comment_ranges: &CommentRanges, + trivia: &TriviaRanges, source: &str, ) -> CommentPlacement<'a> { match comment.enclosing_node() { @@ -240,14 +239,14 @@ fn handle_enclosed_comment<'a>( .or_else(|comment| handle_bracketed_end_of_line_comment(comment, source)), AnyNodeRef::ExprIf(expr_if) => handle_expr_if_comment(comment, expr_if, source), AnyNodeRef::ExprSlice(expr_slice) => { - handle_slice_comments(comment, expr_slice, comment_ranges, source) + handle_slice_comments(comment, expr_slice, trivia, source) } AnyNodeRef::ExprStarred(starred) => { handle_trailing_expression_starred_star_end_of_line_comment(comment, starred, source) } AnyNodeRef::ExprSubscript(expr_subscript) => { if let Expr::Slice(expr_slice) = expr_subscript.slice.as_ref() { - return handle_slice_comments(comment, expr_slice, comment_ranges, source); + return handle_slice_comments(comment, expr_slice, trivia, source); } // Handle non-slice subscript end-of-line comments coming after the `[` @@ -357,14 +356,14 @@ fn handle_enclosed_comment<'a>( handle_bracketed_end_of_line_comment(comment, source) } AnyNodeRef::StmtReturn(_) => { - handle_trailing_implicit_concatenated_string_comment(comment, comment_ranges, source) + handle_trailing_implicit_concatenated_string_comment(comment, trivia, source) } AnyNodeRef::StmtAssign(assignment) if comment.preceding_node().is_some_and(|preceding| { preceding.ptr_eq(AnyNodeRef::from(&*assignment.value)) }) => { - handle_trailing_implicit_concatenated_string_comment(comment, comment_ranges, source) + handle_trailing_implicit_concatenated_string_comment(comment, trivia, source) } AnyNodeRef::StmtAnnAssign(assignment) if comment.preceding_node().is_some_and(|preceding| { @@ -374,21 +373,21 @@ fn handle_enclosed_comment<'a>( .is_some_and(|value| preceding.ptr_eq(value.into())) }) => { - handle_trailing_implicit_concatenated_string_comment(comment, comment_ranges, source) + handle_trailing_implicit_concatenated_string_comment(comment, trivia, source) } AnyNodeRef::StmtAugAssign(assignment) if comment.preceding_node().is_some_and(|preceding| { preceding.ptr_eq(AnyNodeRef::from(&*assignment.value)) }) => { - handle_trailing_implicit_concatenated_string_comment(comment, comment_ranges, source) + handle_trailing_implicit_concatenated_string_comment(comment, trivia, source) } AnyNodeRef::StmtTypeAlias(assignment) if comment.preceding_node().is_some_and(|preceding| { preceding.ptr_eq(AnyNodeRef::from(&*assignment.value)) }) => { - handle_trailing_implicit_concatenated_string_comment(comment, comment_ranges, source) + handle_trailing_implicit_concatenated_string_comment(comment, trivia, source) } _ => CommentPlacement::Default(comment), @@ -1134,7 +1133,7 @@ fn handle_module_level_own_line_comment_before_class_or_function_comment<'a>( fn handle_slice_comments<'a>( comment: DecoratedComment<'a>, expr_slice: &'a ast::ExprSlice, - comment_ranges: &CommentRanges, + trivia: &TriviaRanges, source: &str, ) -> CommentPlacement<'a> { let ast::ExprSlice { @@ -1147,7 +1146,7 @@ fn handle_slice_comments<'a>( // Check for `foo[ # comment`, but only if they are on the same line let after_lbracket = matches!( - BackwardsTokenizer::up_to(comment.start(), source, comment_ranges) + BackwardsTokenizer::up_to(comment.start(), source, trivia.comments()) .skip_trivia() .next(), Some(SimpleToken { @@ -2338,7 +2337,7 @@ fn handle_comprehension_comment<'a>( /// joining the string literals into a single string literal if it fits on the line. fn handle_trailing_implicit_concatenated_string_comment<'a>( comment: DecoratedComment<'a>, - comment_ranges: &CommentRanges, + trivia: &TriviaRanges, source: &str, ) -> CommentPlacement<'a> { if !comment.line_position().is_end_of_line() { @@ -2359,7 +2358,9 @@ fn handle_trailing_implicit_concatenated_string_comment<'a>( }; if source.contains_line_break(TextRange::new(second_last.end(), last.start())) - && is_expression_parenthesized(string_like.as_expression_ref(), comment_ranges, source) + && trivia + .parenthesized() + .contains(string_like.as_expression_ref().range()) { let range = TextRange::new(last.end(), comment.start()); diff --git a/crates/ruff_python_formatter/src/comments/visitor.rs b/crates/ruff_python_formatter/src/comments/visitor.rs index 1aad7e273d4814..f18c2f0d7c2237 100644 --- a/crates/ruff_python_formatter/src/comments/visitor.rs +++ b/crates/ruff_python_formatter/src/comments/visitor.rs @@ -8,7 +8,7 @@ use ruff_python_ast::{Mod, Stmt}; // pre-order. #[allow(clippy::wildcard_imports)] use ruff_python_ast::visitor::source_order::*; -use ruff_python_trivia::{CommentLinePosition, CommentRanges}; +use ruff_python_trivia::{CommentLinePosition, CommentRanges, TriviaRanges}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::comments::node_key::NodeRefEqualityKey; @@ -534,13 +534,13 @@ impl<'a> PushComment<'a> for CommentsVecBuilder<'a> { pub(super) struct CommentsMapBuilder<'a> { comments: CommentsMap<'a>, /// We need those for backwards lexing - comment_ranges: &'a CommentRanges, + trivia: &'a TriviaRanges, source: &'a str, } impl<'a> PushComment<'a> for CommentsMapBuilder<'a> { fn push_comment(&mut self, placement: DecoratedComment<'a>) { - let placement = place_comment(placement, self.comment_ranges, self.source); + let placement = place_comment(placement, self.trivia, self.source); match placement { CommentPlacement::Leading { node, comment } => { self.push_leading_comment(node, comment); @@ -602,10 +602,10 @@ impl<'a> PushComment<'a> for CommentsMapBuilder<'a> { } impl<'a> CommentsMapBuilder<'a> { - pub(crate) fn new(source: &'a str, comment_ranges: &'a CommentRanges) -> Self { + pub(crate) fn new(source: &'a str, trivia: &'a TriviaRanges) -> Self { Self { comments: CommentsMap::default(), - comment_ranges, + trivia, source, } } diff --git a/crates/ruff_python_formatter/src/context.rs b/crates/ruff_python_formatter/src/context.rs index 8eaf52ee35993b..57e12ee27c7a49 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -2,8 +2,11 @@ use std::fmt::{Debug, Formatter}; use std::ops::{Deref, DerefMut}; use ruff_formatter::{Buffer, FormatContext, GroupId, IndentWidth, SourceCode}; +use ruff_python_ast::ExprRef; use ruff_python_ast::str::Quote; use ruff_python_ast::token::Tokens; +use ruff_python_trivia::TriviaRanges; +use ruff_text_size::Ranged; use crate::PyFormatOptions; use crate::comments::Comments; @@ -13,6 +16,7 @@ pub struct PyFormatContext<'a> { options: PyFormatOptions, contents: &'a str, comments: Comments<'a>, + trivia: &'a TriviaRanges, tokens: &'a Tokens, node_level: NodeLevel, indent_level: IndentLevel, @@ -34,12 +38,14 @@ impl<'a> PyFormatContext<'a> { options: PyFormatOptions, contents: &'a str, comments: Comments<'a>, + trivia: &'a TriviaRanges, tokens: &'a Tokens, ) -> Self { Self { options, contents, comments, + trivia, tokens, node_level: NodeLevel::TopLevel(TopLevelStatementPosition::Other), indent_level: IndentLevel::new(0), @@ -72,10 +78,18 @@ impl<'a> PyFormatContext<'a> { &self.comments } + pub(crate) fn trivia(&self) -> &'a TriviaRanges { + self.trivia + } + pub(crate) fn tokens(&self) -> &'a Tokens { self.tokens } + pub(crate) fn is_expression_parenthesized(&self, expression: ExprRef) -> bool { + self.trivia.parenthesized().contains(expression.range()) + } + /// Returns a non-None value only if the formatter is running on a code /// snippet within a docstring. /// diff --git a/crates/ruff_python_formatter/src/expression/binary_like.rs b/crates/ruff_python_formatter/src/expression/binary_like.rs index f1da88299df3f9..cdd61c9d5f2eb2 100644 --- a/crates/ruff_python_formatter/src/expression/binary_like.rs +++ b/crates/ruff_python_formatter/src/expression/binary_like.rs @@ -7,8 +7,7 @@ use ruff_formatter::write; use ruff_python_ast::{ Expr, ExprAttribute, ExprBinOp, ExprBoolOp, ExprCompare, ExprUnaryOp, StringLike, UnaryOp, }; -use ruff_python_trivia::CommentRanges; -use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer}; +use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer, TriviaRanges}; use ruff_text_size::{Ranged, TextRange}; use crate::comments::{Comments, SourceComment, leading_comments, trailing_comments}; @@ -16,8 +15,7 @@ use crate::expression::OperatorPrecedence; use crate::expression::parentheses::{ Parentheses, in_parentheses_only_group, in_parentheses_only_if_group_breaks, in_parentheses_only_soft_line_break, in_parentheses_only_soft_line_break_or_space, - is_expression_parenthesized, write_in_parentheses_only_group_end_tag, - write_in_parentheses_only_group_start_tag, + write_in_parentheses_only_group_end_tag, write_in_parentheses_only_group_start_tag, }; use crate::prelude::*; use crate::string::implicit::FormatImplicitConcatenatedString; @@ -33,13 +31,17 @@ impl<'a> BinaryLike<'a> { /// Flattens the hierarchical binary expression into a flat operand, operator, operand... sequence. /// /// See [`FlatBinaryExpressionSlice`] for an in depth explanation. - fn flatten(self, comments: &'a Comments<'a>, source: &str) -> FlatBinaryExpression<'a> { + fn flatten( + self, + comments: &'a Comments<'a>, + trivia: &TriviaRanges, + ) -> FlatBinaryExpression<'a> { fn recurse_compare<'a>( compare: &'a ExprCompare, leading_comments: &'a [SourceComment], trailing_comments: &'a [SourceComment], comments: &'a Comments, - source: &str, + trivia: &TriviaRanges, parts: &mut SmallVec<[OperandOrOperator<'a>; 8]>, ) { parts.reserve(compare.comparators.len() * 2 + 1); @@ -50,7 +52,7 @@ impl<'a> BinaryLike<'a> { leading_comments, }, comments, - source, + trivia, parts, ); @@ -69,7 +71,7 @@ impl<'a> BinaryLike<'a> { trailing_comments: &[], })); - rec(Operand::Middle { expression }, comments, source, parts); + rec(Operand::Middle { expression }, comments, trivia, parts); } parts.push(OperandOrOperator::Operator(Operator { @@ -83,7 +85,7 @@ impl<'a> BinaryLike<'a> { trailing_comments, }, comments, - source, + trivia, parts, ); } @@ -94,7 +96,7 @@ impl<'a> BinaryLike<'a> { leading_comments: &'a [SourceComment], trailing_comments: &'a [SourceComment], comments: &'a Comments, - source: &str, + trivia: &TriviaRanges, parts: &mut SmallVec<[OperandOrOperator<'a>; 8]>, ) { parts.reserve(bool_expression.values.len() * 2 - 1); @@ -106,7 +108,7 @@ impl<'a> BinaryLike<'a> { leading_comments, }, comments, - source, + trivia, parts, ); @@ -117,7 +119,7 @@ impl<'a> BinaryLike<'a> { if let Some((right, middle)) = rest.split_last() { for expression in middle { - rec(Operand::Middle { expression }, comments, source, parts); + rec(Operand::Middle { expression }, comments, trivia, parts); parts.push(OperandOrOperator::Operator(Operator { symbol: OperatorSymbol::Bool(bool_expression.op), trailing_comments: &[], @@ -130,7 +132,7 @@ impl<'a> BinaryLike<'a> { trailing_comments, }, comments, - source, + trivia, parts, ); } @@ -142,7 +144,7 @@ impl<'a> BinaryLike<'a> { leading_comments: &'a [SourceComment], trailing_comments: &'a [SourceComment], comments: &'a Comments, - source: &str, + trivia: &TriviaRanges, parts: &mut SmallVec<[OperandOrOperator<'a>; 8]>, ) { rec( @@ -151,7 +153,7 @@ impl<'a> BinaryLike<'a> { expression: &binary.left, }, comments, - source, + trivia, parts, ); @@ -166,7 +168,7 @@ impl<'a> BinaryLike<'a> { trailing_comments, }, comments, - source, + trivia, parts, ); } @@ -174,18 +176,12 @@ impl<'a> BinaryLike<'a> { fn rec<'a>( operand: Operand<'a>, comments: &'a Comments, - source: &str, + trivia: &TriviaRanges, parts: &mut SmallVec<[OperandOrOperator<'a>; 8]>, ) { let expression = operand.expression(); match expression { - Expr::BinOp(binary) - if !is_expression_parenthesized( - expression.into(), - comments.ranges(), - source, - ) => - { + Expr::BinOp(binary) if !trivia.parenthesized().contains(expression.range()) => { let leading_comments = operand .leading_binary_comments() .unwrap_or_else(|| comments.leading(binary)); @@ -199,17 +195,11 @@ impl<'a> BinaryLike<'a> { leading_comments, trailing_comments, comments, - source, + trivia, parts, ); } - Expr::Compare(compare) - if !is_expression_parenthesized( - expression.into(), - comments.ranges(), - source, - ) => - { + Expr::Compare(compare) if !trivia.parenthesized().contains(expression.range()) => { let leading_comments = operand .leading_binary_comments() .unwrap_or_else(|| comments.leading(compare)); @@ -223,17 +213,11 @@ impl<'a> BinaryLike<'a> { leading_comments, trailing_comments, comments, - source, + trivia, parts, ); } - Expr::BoolOp(bool_op) - if !is_expression_parenthesized( - expression.into(), - comments.ranges(), - source, - ) => - { + Expr::BoolOp(bool_op) if !trivia.parenthesized().contains(expression.range()) => { let leading_comments = operand .leading_binary_comments() .unwrap_or_else(|| comments.leading(bool_op)); @@ -247,7 +231,7 @@ impl<'a> BinaryLike<'a> { leading_comments, trailing_comments, comments, - source, + trivia, parts, ); } @@ -261,14 +245,14 @@ impl<'a> BinaryLike<'a> { match self { BinaryLike::Binary(binary) => { // Leading and trailing comments are handled by the binary's ``FormatNodeRule` implementation. - recurse_binary(binary, &[], &[], comments, source, &mut parts); + recurse_binary(binary, &[], &[], comments, trivia, &mut parts); } BinaryLike::Compare(compare) => { // Leading and trailing comments are handled by the compare's ``FormatNodeRule` implementation. - recurse_compare(compare, &[], &[], comments, source, &mut parts); + recurse_compare(compare, &[], &[], comments, trivia, &mut parts); } BinaryLike::Bool(bool) => { - recurse_bool(bool, &[], &[], comments, source, &mut parts); + recurse_bool(bool, &[], &[], comments, trivia, &mut parts); } } @@ -283,13 +267,13 @@ impl<'a> BinaryLike<'a> { impl Format> for BinaryLike<'_> { fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { let comments = f.context().comments().clone(); - let flat_binary = self.flatten(&comments, f.context().source()); + let trivia = f.context().trivia(); + let flat_binary = self.flatten(&comments, trivia); if self.is_bool_op() { return in_parentheses_only_group(&&*flat_binary).fmt(f); } - let source = f.context().source(); let mut string_operands = flat_binary .operands() .filter_map(|(index, operand)| { @@ -297,11 +281,7 @@ impl Format> for BinaryLike<'_> { .ok() .filter(|string| { string.is_implicit_concatenated() - && !is_expression_parenthesized( - string.into(), - comments.ranges(), - source, - ) + && !trivia.parenthesized().contains(string.range()) }) .map(|string| (index, string, operand)) }) @@ -379,10 +359,8 @@ impl Format> for BinaryLike<'_> { // previous iteration) write_in_parentheses_only_group_end_tag(f); - if operand.has_unparenthesized_leading_comments( - f.context().comments(), - f.context().source(), - ) || left_operator.has_trailing_comments() + if operand.has_unparenthesized_leading_comments(f.context()) + || left_operator.has_trailing_comments() { hard_line_break().fmt(f)?; } else { @@ -433,11 +411,8 @@ impl Format> for BinaryLike<'_> { if let Some(right_operator) = flat_binary.get_operator(index.right_operator()) { write_in_parentheses_only_group_start_tag(f); let right_operand = &flat_binary[right_operator_index.right_operand()]; - let right_operand_has_leading_comments = right_operand - .has_unparenthesized_leading_comments( - f.context().comments(), - f.context().source(), - ); + let right_operand_has_leading_comments = + right_operand.has_unparenthesized_leading_comments(f.context()); // Keep the operator on the same line if the right side has leading comments (and thus, breaks) if right_operand_has_leading_comments { @@ -449,11 +424,9 @@ impl Format> for BinaryLike<'_> { right_operator.fmt(f)?; if (right_operand_has_leading_comments - && !is_expression_parenthesized( - right_operand.expression().into(), - f.context().comments().ranges(), - f.context().source(), - )) + && !f + .context() + .is_expression_parenthesized(right_operand.expression().into())) || right_operator.has_trailing_comments() { hard_line_break().fmt(f)?; @@ -488,16 +461,11 @@ impl Format> for BinaryLike<'_> { } } -fn is_simple_power_expression( - left: &Expr, - right: &Expr, - comment_range: &CommentRanges, - source: &str, -) -> bool { +fn is_simple_power_expression(left: &Expr, right: &Expr, context: &PyFormatContext) -> bool { is_simple_power_operand(left) && is_simple_power_operand(right) - && !is_expression_parenthesized(left.into(), comment_range, source) - && !is_expression_parenthesized(right.into(), comment_range, source) + && !context.is_expression_parenthesized(left.into()) + && !context.is_expression_parenthesized(right.into()) } /// Return `true` if an [`Expr`] adheres to [Black's definition](https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#line-breaks-binary-operators) @@ -547,7 +515,7 @@ impl<'a> Deref for FlatBinaryExpression<'a> { /// ``` /// /// The slice representation of the above is closer to what you have in source. It's a simple sequence of operands and operators, -/// entirely ignoring operator precedence (doesn't flatten parenthesized expressions): +/// entirely ignoring operator precedence (doesn't flatten trivia expressions): /// /// ```text /// ----------------------------- @@ -689,8 +657,7 @@ impl Format> for FlatBinaryExpressionSlice<'_> { && is_simple_power_expression( left.last_operand().expression(), right.first_operand().expression(), - f.context().comments().ranges(), - f.context().source(), + f.context(), ); if let Some(leading) = left.first_operand().leading_binary_comments() { @@ -716,10 +683,9 @@ impl Format> for FlatBinaryExpressionSlice<'_> { // Format the operator on its own line if the right side has any leading comments. if operator_part.has_trailing_comments() - || right.first_operand().has_unparenthesized_leading_comments( - f.context().comments(), - f.context().source(), - ) + || right + .first_operand() + .has_unparenthesized_leading_comments(f.context()) { hard_line_break().fmt(f)?; } else if is_pow { @@ -826,15 +792,17 @@ impl<'a> Operand<'a> { } } - /// Returns `true` if the operand has any leading comments that are not parenthesized. - fn has_unparenthesized_leading_comments(&self, comments: &Comments, source: &str) -> bool { + /// Returns `true` if the operand has any leading comments that are not trivia. + fn has_unparenthesized_leading_comments(&self, context: &PyFormatContext) -> bool { + let comments = context.comments(); + let source = context.source(); match self { Operand::Left { leading_comments, .. } => !leading_comments.is_empty(), Operand::Middle { expression } | Operand::Right { expression, .. } => { let leading = comments.leading(*expression); - if is_expression_parenthesized((*expression).into(), comments.ranges(), source) { + if context.is_expression_parenthesized((*expression).into()) { leading.iter().any(|comment| { !comment.is_formatted() && matches!( @@ -881,11 +849,7 @@ impl Format> for Operand<'_> { fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { let expression = self.expression(); - if is_expression_parenthesized( - expression.into(), - f.context().comments().ranges(), - f.context().source(), - ) { + if f.context().is_expression_parenthesized(expression.into()) { let comments = f.context().comments().clone(); let expression_comments = comments.leading_dangling_trailing(expression); diff --git a/crates/ruff_python_formatter/src/expression/expr_attribute.rs b/crates/ruff_python_formatter/src/expression/expr_attribute.rs index ca88313b19620a..e2784f885ab97a 100644 --- a/crates/ruff_python_formatter/src/expression/expr_attribute.rs +++ b/crates/ruff_python_formatter/src/expression/expr_attribute.rs @@ -6,9 +6,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::comments::dangling_comments; use crate::expression::CallChainLayout; -use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized, -}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses}; use crate::prelude::*; use crate::preview::is_fluent_layout_split_first_call_enabled; @@ -41,11 +39,7 @@ impl FormatNodeRule for FormatExprAttribute { let format_inner = format_with(|f: &mut PyFormatter| { let parenthesize_value = is_base_ten_number_literal(value.as_ref(), f.context().source()) || { - is_expression_parenthesized( - value.into(), - f.context().comments().ranges(), - f.context().source(), - ) + f.context().is_expression_parenthesized(value.into()) }; if call_chain_layout.is_fluent() { @@ -194,21 +188,11 @@ impl NeedsParentheses for ExprAttribute { context: &PyFormatContext, ) -> OptionalParentheses { // Checks if there are any own line comments in an attribute chain (a.b.c). - if CallChainLayout::from_expression( - self.into(), - context.comments().ranges(), - context.source(), - ) - .is_fluent() - { + if CallChainLayout::from_expression(self.into(), context).is_fluent() { OptionalParentheses::Multiline } else if context.comments().has_dangling(self) { OptionalParentheses::Always - } else if is_expression_parenthesized( - self.value.as_ref().into(), - context.comments().ranges(), - context.source(), - ) { + } else if context.is_expression_parenthesized(self.value.as_ref().into()) { // We have to avoid creating syntax errors like // ```python // variable = (something) # trailing diff --git a/crates/ruff_python_formatter/src/expression/expr_await.rs b/crates/ruff_python_formatter/src/expression/expr_await.rs index 222f5c0a80c049..e79e9d411c32d6 100644 --- a/crates/ruff_python_formatter/src/expression/expr_await.rs +++ b/crates/ruff_python_formatter/src/expression/expr_await.rs @@ -5,8 +5,7 @@ use ruff_text_size::Ranged; use crate::expression::maybe_parenthesize_expression; use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parenthesize, is_expression_parenthesized, - is_type_annotation_of, + NeedsParentheses, OptionalParentheses, Parenthesize, is_type_annotation_of, }; use crate::prelude::*; @@ -40,11 +39,7 @@ impl NeedsParentheses for ExprAwait { ) -> OptionalParentheses { if parent.is_expr_await() || is_type_annotation_of(self.range(), parent) { OptionalParentheses::Always - } else if is_expression_parenthesized( - self.value.as_ref().into(), - context.comments().ranges(), - context.source(), - ) { + } else if context.is_expression_parenthesized(self.value.as_ref().into()) { // Prefer splitting the value if it is parenthesized. OptionalParentheses::Never } else { diff --git a/crates/ruff_python_formatter/src/expression/expr_call.rs b/crates/ruff_python_formatter/src/expression/expr_call.rs index 135ade22662bcc..086287b6749737 100644 --- a/crates/ruff_python_formatter/src/expression/expr_call.rs +++ b/crates/ruff_python_formatter/src/expression/expr_call.rs @@ -4,9 +4,7 @@ use ruff_python_ast::{Expr, ExprCall}; use crate::comments::dangling_comments; use crate::expression::CallChainLayout; -use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized, -}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses}; use crate::prelude::*; #[derive(Default)] @@ -39,11 +37,7 @@ impl FormatNodeRule for FormatExprCall { let fmt_func = format_with(|f: &mut PyFormatter| { // Format the function expression. - if is_expression_parenthesized( - func.into(), - f.context().comments().ranges(), - f.context().source(), - ) { + if f.context().is_expression_parenthesized(func.into()) { func.format().with_options(Parentheses::Always).fmt(f) } else { match func.as_ref() { @@ -84,21 +78,11 @@ impl NeedsParentheses for ExprCall { _parent: AnyNodeRef, context: &PyFormatContext, ) -> OptionalParentheses { - if CallChainLayout::from_expression( - self.into(), - context.comments().ranges(), - context.source(), - ) - .is_fluent() - { + if CallChainLayout::from_expression(self.into(), context).is_fluent() { OptionalParentheses::Multiline } else if context.comments().has_dangling(self) { OptionalParentheses::Always - } else if is_expression_parenthesized( - self.func.as_ref().into(), - context.comments().ranges(), - context.source(), - ) { + } else if context.is_expression_parenthesized(self.func.as_ref().into()) { OptionalParentheses::Never } else { self.func.needs_parentheses(self.into(), context) diff --git a/crates/ruff_python_formatter/src/expression/expr_if.rs b/crates/ruff_python_formatter/src/expression/expr_if.rs index 8dfa92d885115f..dbbabd7ed1b895 100644 --- a/crates/ruff_python_formatter/src/expression/expr_if.rs +++ b/crates/ruff_python_formatter/src/expression/expr_if.rs @@ -5,7 +5,7 @@ use ruff_python_ast::{Expr, ExprIf}; use crate::comments::leading_comments; use crate::expression::parentheses::{ NeedsParentheses, OptionalParentheses, in_parentheses_only_group, - in_parentheses_only_soft_line_break_or_space, is_expression_parenthesized, + in_parentheses_only_soft_line_break_or_space, }; use crate::prelude::*; @@ -105,13 +105,7 @@ struct FormatOrElse<'a> { impl Format> for FormatOrElse<'_> { fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { match self.orelse { - Expr::If(expr) - if !is_expression_parenthesized( - expr.into(), - f.context().comments().ranges(), - f.context().source(), - ) => - { + Expr::If(expr) if !f.context().is_expression_parenthesized(expr.into()) => { write!(f, [expr.format().with_options(ExprIfLayout::Nested)]) } _ => write!(f, [in_parentheses_only_group(&self.orelse.format())]), diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index a377c17627f643..36a3d293959721 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -5,9 +5,7 @@ use ruff_text_size::Ranged; use crate::builders::parenthesize_if_expands; use crate::comments::{SourceComment, dangling_comments, leading_comments, trailing_comments}; use crate::expression::has_own_parentheses; -use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized, -}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses}; use crate::other::parameters::ParametersParentheses; use crate::prelude::*; @@ -322,9 +320,7 @@ impl Format> for FormatBody<'_> { // ) // ``` let comments = f.context().comments(); - if is_expression_parenthesized(body.into(), comments.ranges(), f.context().source()) - && comments.has_leading(body) - { + if f.context().is_expression_parenthesized(body.into()) && comments.has_leading(body) { trailing_comments(dangling_header_comments).fmt(f)?; // Note that `leading_body_comments` have already been formatted as part of diff --git a/crates/ruff_python_formatter/src/expression/expr_subscript.rs b/crates/ruff_python_formatter/src/expression/expr_subscript.rs index c9595c4a26639f..91c0c4163d6bfd 100644 --- a/crates/ruff_python_formatter/src/expression/expr_subscript.rs +++ b/crates/ruff_python_formatter/src/expression/expr_subscript.rs @@ -5,7 +5,7 @@ use ruff_python_ast::{Expr, ExprSubscript}; use crate::expression::CallChainLayout; use crate::expression::expr_tuple::TupleParentheses; use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized, parenthesized, + NeedsParentheses, OptionalParentheses, Parentheses, parenthesized, }; use crate::prelude::*; @@ -43,11 +43,7 @@ impl FormatNodeRule for FormatExprSubscript { ); let format_inner = format_with(|f: &mut PyFormatter| { - if is_expression_parenthesized( - value.into(), - f.context().comments().ranges(), - f.context().source(), - ) { + if f.context().is_expression_parenthesized(value.into()) { value.format().with_options(Parentheses::Always).fmt(f) } else { match value.as_ref() { @@ -91,19 +87,9 @@ impl NeedsParentheses for ExprSubscript { context: &PyFormatContext, ) -> OptionalParentheses { { - if CallChainLayout::from_expression( - self.into(), - context.comments().ranges(), - context.source(), - ) - .is_fluent() - { + if CallChainLayout::from_expression(self.into(), context).is_fluent() { OptionalParentheses::Multiline - } else if is_expression_parenthesized( - self.value.as_ref().into(), - context.comments().ranges(), - context.source(), - ) { + } else if context.is_expression_parenthesized(self.value.as_ref().into()) { OptionalParentheses::Never } else { match self.value.needs_parentheses(self.into(), context) { diff --git a/crates/ruff_python_formatter/src/expression/expr_unary_op.rs b/crates/ruff_python_formatter/src/expression/expr_unary_op.rs index 5cc741018fc9c4..110b07b5431de8 100644 --- a/crates/ruff_python_formatter/src/expression/expr_unary_op.rs +++ b/crates/ruff_python_formatter/src/expression/expr_unary_op.rs @@ -1,13 +1,11 @@ use ruff_python_ast::AnyNodeRef; use ruff_python_ast::ExprUnaryOp; use ruff_python_ast::UnaryOp; -use ruff_python_ast::parenthesize::parenthesized_range; +use ruff_python_ast::token::parenthesized_range; use ruff_text_size::Ranged; use crate::comments::trailing_comments; -use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized, -}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses}; use crate::prelude::*; #[derive(Default)] @@ -90,11 +88,7 @@ impl NeedsParentheses for ExprUnaryOp { return OptionalParentheses::Always; } - if is_expression_parenthesized( - self.operand.as_ref().into(), - context.comments().ranges(), - context.source(), - ) { + if context.is_expression_parenthesized(self.operand.as_ref().into()) { return OptionalParentheses::Never; } @@ -110,12 +104,8 @@ impl NeedsParentheses for ExprUnaryOp { /// operand and thus requires parentheses. fn needs_line_break(item: &ExprUnaryOp, context: &PyFormatContext) -> bool { let comments = context.comments(); - let parenthesized_operand_range = parenthesized_range( - item.operand.as_ref().into(), - item.into(), - comments.ranges(), - context.source(), - ); + let parenthesized_operand_range = + parenthesized_range(item.operand.as_ref().into(), item.into(), context.tokens()); let leading_operand_comments = comments.leading(item.operand.as_ref()); let has_leading_comments_before_parens = parenthesized_operand_range.is_some_and(|range| { leading_operand_comments @@ -124,10 +114,6 @@ fn needs_line_break(item: &ExprUnaryOp, context: &PyFormatContext) -> bool { }); !leading_operand_comments.is_empty() - && !is_expression_parenthesized( - item.operand.as_ref().into(), - context.comments().ranges(), - context.source(), - ) + && !context.is_expression_parenthesized(item.operand.as_ref().into()) || has_leading_comments_before_parens } diff --git a/crates/ruff_python_formatter/src/expression/expr_yield.rs b/crates/ruff_python_formatter/src/expression/expr_yield.rs index 8bce330f70f038..a20573fb5a8f0d 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield.rs @@ -5,8 +5,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::expression::maybe_parenthesize_expression; use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parenthesize, is_expression_parenthesized, - is_type_annotation_of, + NeedsParentheses, OptionalParentheses, Parenthesize, is_type_annotation_of, }; use crate::prelude::*; @@ -55,11 +54,7 @@ impl NeedsParentheses for AnyExpressionYield<'_> { // FormatStmtExpr, does not add parenthesis if parent.is_stmt_assign() || parent.is_stmt_ann_assign() || parent.is_stmt_aug_assign() { if let Some(value) = self.value() { - if is_expression_parenthesized( - value.into(), - context.comments().ranges(), - context.source(), - ) { + if context.is_expression_parenthesized(value.into()) { // Ex) `x = yield (1)` OptionalParentheses::Never } else { diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 5e5bc40e95aa1c..5cb3299a901d6d 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -4,19 +4,19 @@ use std::slice; use ruff_formatter::{ FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions, write, }; -use ruff_python_ast::parenthesize::parentheses_iterator; +use ruff_python_ast::token::parentheses_iterator; use ruff_python_ast::visitor::source_order::{SourceOrderVisitor, walk_expr}; use ruff_python_ast::{self as ast}; use ruff_python_ast::{AnyNodeRef, Expr, ExprRef, Operator}; -use ruff_python_trivia::CommentRanges; +use ruff_python_trivia::TriviaRanges; use ruff_text_size::Ranged; use crate::builders::parenthesize_if_expands; use crate::comments::{LeadingDanglingTrailingComments, leading_comments, trailing_comments}; use crate::context::{NodeLevel, WithNodeLevel}; use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize, is_expression_parenthesized, - optional_parentheses, parenthesized, + NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize, optional_parentheses, + parenthesized, }; use crate::prelude::*; use crate::preview::is_hug_parens_with_braces_and_square_brackets_enabled; @@ -112,11 +112,7 @@ impl FormatRule> for FormatExpr { Expr::IpyEscapeCommand(expr) => expr.format().fmt(f), }); let parenthesize = match parentheses { - Parentheses::Preserve => is_expression_parenthesized( - expression.into(), - f.context().comments().ranges(), - f.context().source(), - ), + Parentheses::Preserve => f.context().is_expression_parenthesized(expression.into()), Parentheses::Always => true, // Fluent style means we already have parentheses Parentheses::Never => false, @@ -216,13 +212,8 @@ fn format_with_parentheses_comments( // ) // ) // ``` - let range_with_parens = parentheses_iterator( - expression.into(), - None, - f.context().comments().ranges(), - f.context().source(), - ) - .last(); + let range_with_parens = + parentheses_iterator(expression.into(), None, f.context().tokens()).last(); let (leading_split, trailing_split) = if let Some(range_with_parens) = range_with_parens { let leading_split = node_comments @@ -360,11 +351,8 @@ impl Format> for MaybeParenthesizeExpression<'_> { } = self; let preserve_parentheses = parenthesize.is_optional() - && is_expression_parenthesized( - (*expression).into(), - f.context().comments().ranges(), - f.context().source(), - ); + && f.context() + .is_expression_parenthesized((*expression).into()); // If we want to preserve parentheses, short-circuit. if preserve_parentheses { @@ -808,11 +796,7 @@ impl<'input> SourceOrderVisitor<'input> for CanOmitOptionalParenthesesVisitor<'i self.last = Some(expr); // Rule only applies for non-parenthesized expressions. - if is_expression_parenthesized( - expr.into(), - self.context.comments().ranges(), - self.context.source(), - ) { + if self.context.is_expression_parenthesized(expr.into()) { self.any_parenthesized_expressions = true; } else { self.visit_subexpression(expr); @@ -992,11 +976,7 @@ impl CallChainLayout { /// 3. If the root is parenthesized, add 1 to that value. /// 4. If the total is at least 2, return `Fluent`. Otherwise /// return `NonFluent` - pub(crate) fn from_expression( - mut expr: ExprRef, - comment_ranges: &CommentRanges, - source: &str, - ) -> Self { + pub(crate) fn from_expression(mut expr: ExprRef, context: &PyFormatContext) -> Self { // TODO(dylan): Once the fluent layout preview style is // stabilized, see if it is possible to simplify some of // the logic around parenthesized roots. (While supporting @@ -1051,7 +1031,7 @@ impl CallChainLayout { // data[:100].T // ^^^^^^^^^^ value // ``` - if is_expression_parenthesized(value.into(), comment_ranges, source) { + if context.is_expression_parenthesized(value.into()) { // `(a).b`. We preserve these parentheses so don't recurse root_value_parenthesized = true; break; @@ -1074,7 +1054,7 @@ impl CallChainLayout { // We preserve these parentheses so don't recurse // e.g. (a)[0].x().y().z() // ^stop here - if is_expression_parenthesized(inner.into(), comment_ranges, source) { + if context.is_expression_parenthesized(inner.into()) { break; } @@ -1146,11 +1126,7 @@ impl CallChainLayout { match self { CallChainLayout::Default => { if f.context().node_level().is_parenthesized() { - CallChainLayout::from_expression( - item.into(), - f.context().comments().ranges(), - f.context().source(), - ) + CallChainLayout::from_expression(item.into(), f.context()) } else { CallChainLayout::NonFluent } @@ -1195,7 +1171,7 @@ pub(crate) fn has_parentheses(expr: &Expr, context: &PyFormatContext) -> Option< // Otherwise, if the node lacks parentheses (e.g., `(1)`) or only contains empty parentheses // (e.g., `([])`), we need to check for surrounding parentheses. - if is_expression_parenthesized(expr.into(), context.comments().ranges(), context.source()) { + if context.is_expression_parenthesized(expr.into()) { return Some(OwnParentheses::NonEmpty); } @@ -1409,14 +1385,7 @@ pub(crate) fn is_splittable_expression(expr: &Expr, context: &PyFormatContext) - Expr::Call(ast::ExprCall { arguments, func, .. - }) => { - !arguments.is_empty() - || is_expression_parenthesized( - func.as_ref().into(), - context.comments().ranges(), - context.source(), - ) - } + }) => !arguments.is_empty() || context.is_expression_parenthesized(func.as_ref().into()), // String like literals can expand if they are implicit concatenated. Expr::FString(fstring) => fstring.value.is_implicit_concatenated(), @@ -1434,11 +1403,8 @@ pub(crate) fn is_splittable_expression(expr: &Expr, context: &PyFormatContext) - | Expr::Attribute(ast::ExprAttribute { value: expression, .. }) => { - is_expression_parenthesized( - expression.into(), - context.comments().ranges(), - context.source(), - ) || is_splittable_expression(expression.as_ref(), context) + context.is_expression_parenthesized(expression.into()) + || is_splittable_expression(expression.as_ref(), context) } } } @@ -1460,11 +1426,7 @@ pub(crate) const fn is_invalid_type_expression(expr: &Expr) -> bool { /// /// Parenthesized expressions are treated as belonging to the enclosing expression. Therefore, the left /// most expression for `(a + b) * c` is `a + b` and not `a`. -pub(crate) fn left_most<'expr>( - expression: &'expr Expr, - comment_ranges: &CommentRanges, - source: &str, -) -> &'expr Expr { +pub(crate) fn left_most<'expr>(expression: &'expr Expr, trivia: &TriviaRanges) -> &'expr Expr { let mut current = expression; loop { let left = match current { @@ -1513,7 +1475,7 @@ pub(crate) fn left_most<'expr>( break current; }; - if is_expression_parenthesized(left.into(), comment_ranges, source) { + if trivia.parenthesized().contains(left.range()) { break current; } diff --git a/crates/ruff_python_formatter/src/expression/parentheses.rs b/crates/ruff_python_formatter/src/expression/parentheses.rs index b61e5717887daa..6f50cc24cc4035 100644 --- a/crates/ruff_python_formatter/src/expression/parentheses.rs +++ b/crates/ruff_python_formatter/src/expression/parentheses.rs @@ -1,11 +1,6 @@ use ruff_formatter::prelude::tag::Condition; use ruff_formatter::{Argument, Arguments, format_args, write}; use ruff_python_ast::AnyNodeRef; -use ruff_python_ast::ExprRef; -use ruff_python_trivia::CommentRanges; -use ruff_python_trivia::{ - BackwardsTokenizer, SimpleToken, SimpleTokenKind, first_non_trivia_token, -}; use ruff_text_size::{Ranged, TextRange}; use crate::comments::{ @@ -108,34 +103,6 @@ pub enum Parentheses { Never, } -/// Returns `true` if the [`ExprRef`] is enclosed by parentheses in the source code. -pub(crate) fn is_expression_parenthesized( - expr: ExprRef, - comment_ranges: &CommentRanges, - contents: &str, -) -> bool { - // First test if there's a closing parentheses because it tends to be cheaper. - if matches!( - first_non_trivia_token(expr.end(), contents), - Some(SimpleToken { - kind: SimpleTokenKind::RParen, - .. - }) - ) { - matches!( - BackwardsTokenizer::up_to(expr.start(), contents, comment_ranges) - .skip_trivia() - .next(), - Some(SimpleToken { - kind: SimpleTokenKind::LParen, - .. - }) - ) - } else { - false - } -} - /// Formats `content` enclosed by the `left` and `right` parentheses. The implementation also ensures /// that expanding the parenthesized expression (or any of its children) doesn't enforce the /// optional parentheses around the outer-most expression to materialize. @@ -466,20 +433,31 @@ impl Format> for FormatEmptyParenthesized<'_> { #[cfg(test)] mod tests { - use ruff_python_ast::ExprRef; use ruff_python_parser::parse_expression; - use ruff_python_trivia::CommentRanges; - - use crate::expression::parentheses::is_expression_parenthesized; + use ruff_python_trivia::TriviaRanges; + use ruff_text_size::Ranged; #[test] - fn test_has_parentheses() { - let expression = r#"(b().c("")).d()"#; - let parsed = parse_expression(expression).unwrap(); - assert!(!is_expression_parenthesized( - ExprRef::from(parsed.expr()), - &CommentRanges::default(), - expression - )); + fn parenthesized_ranges() { + let cases = [ + ("value", false), + ("(value)", true), + ("((value))", true), + ("call(value)", false), + ("(value + other).attribute", false), + ("(\n # leading\n value + other\n)", true), + ("(\n value # trailing\n)", true), + (r#"(b().c("")).d()"#, false), + ]; + + for (source, expected) in cases { + let parsed = parse_expression(source).unwrap(); + let trivia = TriviaRanges::from(parsed.tokens()); + assert_eq!( + trivia.parenthesized().contains(parsed.expr().range()), + expected, + "parentheses mismatch for {source:?}", + ); + } } } diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index 25dad34ef6fc2c..d3b52ab513d99e 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -10,7 +10,7 @@ use ruff_formatter::prelude::*; use ruff_formatter::{FormatError, Formatted, PrintError, Printed, SourceCode, format, write}; use ruff_python_ast::{AnyNodeRef, Mod}; use ruff_python_parser::{ParseError, ParseOptions, Parsed, parse}; -use ruff_python_trivia::CommentRanges; +use ruff_python_trivia::TriviaRanges; use ruff_text_size::{Ranged, TextRange}; use crate::comments::{ @@ -139,23 +139,23 @@ pub fn format_module_source( ) -> Result { let source_type = options.source_type(); let parsed = parse(source, ParseOptions::from(source_type))?; - let comment_ranges = CommentRanges::from(parsed.tokens()); - let formatted = format_module_ast(&parsed, &comment_ranges, source, options)?; + let trivia = TriviaRanges::from(parsed.tokens()); + let formatted = format_module_ast(&parsed, &trivia, source, options)?; Ok(formatted.print()?) } pub fn format_module_ast<'a>( parsed: &'a Parsed, - comment_ranges: &'a CommentRanges, + trivia: &'a TriviaRanges, source: &'a str, options: PyFormatOptions, ) -> FormatResult>> { - format_node(parsed, comment_ranges, source, options) + format_node(parsed, trivia, source, options) } fn format_node<'a, N>( parsed: &'a Parsed, - comment_ranges: &'a CommentRanges, + trivia: &'a TriviaRanges, source: &'a str, options: PyFormatOptions, ) -> FormatResult>> @@ -164,10 +164,10 @@ where &'a N: Into>, { let source_code = SourceCode::new(source); - let comments = Comments::from_ast(parsed.syntax(), source_code, comment_ranges); + let comments = Comments::from_ast(parsed.syntax(), source_code, trivia); let formatted = format!( - PyFormatContext::new(options, source, comments, parsed.tokens()), + PyFormatContext::new(options, source, comments, trivia, parsed.tokens()), [parsed.syntax().format()] )?; formatted @@ -186,10 +186,10 @@ pub fn formatted_file(db: &dyn Db, file: File) -> Result, FormatM return Err(FormatModuleError::ParseError(first.clone())); } - let comment_ranges = CommentRanges::from(parsed.tokens()); + let trivia = TriviaRanges::from(parsed.tokens()); let source = source_text(db, file); - let formatted = format_node(&parsed, &comment_ranges, &source, options)?; + let formatted = format_node(&parsed, &trivia, &source, options)?; let printed = formatted.print()?; if printed.as_code() == &*source { @@ -200,9 +200,9 @@ pub fn formatted_file(db: &dyn Db, file: File) -> Result, FormatM } /// Public function for generating a printable string of the debug comments. -pub fn pretty_comments(module: &Mod, comment_ranges: &CommentRanges, source: &str) -> String { +pub fn pretty_comments(module: &Mod, trivia: &TriviaRanges, source: &str) -> String { let source_code = SourceCode::new(source); - let comments = Comments::from_ast(module, source_code, comment_ranges); + let comments = Comments::from_ast(module, source_code, trivia); std::format!("{comments:#?}", comments = comments.debug(source_code)) } @@ -216,7 +216,7 @@ mod tests { use ruff_python_ast::PySourceType; use ruff_python_parser::{ParseOptions, parse}; - use ruff_python_trivia::CommentRanges; + use ruff_python_trivia::TriviaRanges; use ruff_text_size::{TextRange, TextSize}; use crate::{PyFormatOptions, format_module_ast, format_module_source, format_range}; @@ -257,9 +257,9 @@ class A: ... // Parse the AST. let source_path = "code_inline.py"; let parsed = parse(source, ParseOptions::from(source_type)).unwrap(); - let comment_ranges = CommentRanges::from(parsed.tokens()); + let trivia = TriviaRanges::from(parsed.tokens()); let options = PyFormatOptions::from_extension(Path::new(source_path)); - let formatted = format_module_ast(&parsed, &comment_ranges, source, options).unwrap(); + let formatted = format_module_ast(&parsed, &trivia, source, options).unwrap(); // Uncomment the `dbg` to print the IR. // Use `dbg_write!(f, []) instead of `write!(f, [])` in your formatting code to print some IR diff --git a/crates/ruff_python_formatter/src/other/comprehension.rs b/crates/ruff_python_formatter/src/other/comprehension.rs index cfa75b3e881a5f..597b8182b7f75c 100644 --- a/crates/ruff_python_formatter/src/other/comprehension.rs +++ b/crates/ruff_python_formatter/src/other/comprehension.rs @@ -5,7 +5,6 @@ use ruff_text_size::{Ranged, TextRange}; use crate::comments::{leading_comments, trailing_comments}; use crate::expression::expr_tuple::TupleParentheses; -use crate::expression::parentheses::is_expression_parenthesized; use crate::prelude::*; #[derive(Default)] @@ -36,11 +35,8 @@ impl FormatNodeRule for FormatComprehension { // ] // ``` let will_be_parenthesized = self.preserve_parentheses - && is_expression_parenthesized( - self.expression.into(), - f.context().comments().ranges(), - f.context().source(), - ); + && f.context() + .is_expression_parenthesized(self.expression.into()); if has_leading_comments && !will_be_parenthesized { soft_line_break_or_space().fmt(f) diff --git a/crates/ruff_python_formatter/src/other/interpolated_string_element.rs b/crates/ruff_python_formatter/src/other/interpolated_string_element.rs index e28083b368a865..a93b94254007cd 100644 --- a/crates/ruff_python_formatter/src/other/interpolated_string_element.rs +++ b/crates/ruff_python_formatter/src/other/interpolated_string_element.rs @@ -303,7 +303,7 @@ fn needs_bracket_spacing(expr: &Expr, context: &PyFormatContext) -> bool { } matches!( - left_most(expr, context.comments().ranges(), context.source()), + left_most(expr, context.trivia()), Expr::Dict(_) | Expr::DictComp(_) | Expr::Set(_) | Expr::SetComp(_) ) } diff --git a/crates/ruff_python_formatter/src/other/parameter.rs b/crates/ruff_python_formatter/src/other/parameter.rs index 483280082eb6c7..c2eeb6aa6a21a6 100644 --- a/crates/ruff_python_formatter/src/other/parameter.rs +++ b/crates/ruff_python_formatter/src/other/parameter.rs @@ -1,4 +1,3 @@ -use crate::expression::parentheses::is_expression_parenthesized; use crate::prelude::*; use ruff_python_ast::Parameter; @@ -20,11 +19,7 @@ impl FormatNodeRule for FormatParameter { token(":").fmt(f)?; if f.context().comments().has_leading(annotation) - && !is_expression_parenthesized( - annotation.into(), - f.context().comments().ranges(), - f.context().source(), - ) + && !f.context().is_expression_parenthesized(annotation.into()) { hard_line_break().fmt(f)?; } else { diff --git a/crates/ruff_python_formatter/src/other/with_item.rs b/crates/ruff_python_formatter/src/other/with_item.rs index a309edd6e16418..323a4b08180d4b 100644 --- a/crates/ruff_python_formatter/src/other/with_item.rs +++ b/crates/ruff_python_formatter/src/other/with_item.rs @@ -2,9 +2,7 @@ use ruff_formatter::{FormatRuleWithOptions, write}; use ruff_python_ast::WithItem; use crate::expression::maybe_parenthesize_expression; -use crate::expression::parentheses::{ - Parentheses, Parenthesize, is_expression_parenthesized, parenthesized, -}; +use crate::expression::parentheses::{Parentheses, Parenthesize, parenthesized}; use crate::prelude::*; #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -106,11 +104,7 @@ impl FormatNodeRule for FormatWithItem { // if the `with` has a single item without a target. // E.g., it returns `true` for `with (a)` even though the parentheses // belong to the with statement and not the expression but it can't determine that. - let is_parenthesized = is_expression_parenthesized( - context_expr.into(), - f.context().comments().ranges(), - f.context().source(), - ); + let is_parenthesized = f.context().is_expression_parenthesized(context_expr.into()); match self.layout { // Remove the parentheses of the `with_items` if the with statement adds parentheses diff --git a/crates/ruff_python_formatter/src/pattern/mod.rs b/crates/ruff_python_formatter/src/pattern/mod.rs index a83e03ba4fd108..2b9dda12ffe9d9 100644 --- a/crates/ruff_python_formatter/src/pattern/mod.rs +++ b/crates/ruff_python_formatter/src/pattern/mod.rs @@ -1,9 +1,8 @@ use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions}; use ruff_python_ast::{AnyNodeRef, Expr, PatternMatchAs}; use ruff_python_ast::{MatchCase, Pattern}; -use ruff_python_trivia::CommentRanges; use ruff_python_trivia::{ - BackwardsTokenizer, SimpleToken, SimpleTokenKind, first_non_trivia_token, + BackwardsTokenizer, CommentRanges, SimpleToken, SimpleTokenKind, first_non_trivia_token, }; use ruff_text_size::Ranged; use std::cmp::Ordering; @@ -56,7 +55,7 @@ impl FormatRule> for FormatPattern { let parenthesize = match self.parentheses { Parentheses::Preserve => is_pattern_parenthesized( pattern, - f.context().comments().ranges(), + f.context().trivia().comments(), f.context().source(), ), Parentheses::Always => true, @@ -261,7 +260,7 @@ pub(crate) fn can_pattern_omit_optional_parentheses( true } else { // If the pattern has no own parentheses or it is empty (e.g. ([])), check for surrounding parentheses (that should be preserved). - is_pattern_parenthesized(pattern, context.comments().ranges(), context.source()) + is_pattern_parenthesized(pattern, context.trivia().comments(), context.source()) } } @@ -345,7 +344,7 @@ impl<'a> CanOmitOptionalParenthesesVisitor<'a> { self.last = Some(pattern); // Rule only applies for non-parenthesized patterns. - if is_pattern_parenthesized(pattern, context.comments().ranges(), context.source()) { + if is_pattern_parenthesized(pattern, context.trivia().comments(), context.source()) { self.any_parenthesized_expressions = true; } else { self.visit_pattern(pattern, context); diff --git a/crates/ruff_python_formatter/src/range.rs b/crates/ruff_python_formatter/src/range.rs index 488b5585f4f886..688219a33d73ae 100644 --- a/crates/ruff_python_formatter/src/range.rs +++ b/crates/ruff_python_formatter/src/range.rs @@ -8,7 +8,7 @@ use ruff_python_ast::visitor::source_order::{SourceOrderVisitor, TraversalSignal use ruff_python_ast::{AnyNodeRef, Stmt, StmtMatch, StmtTry}; use ruff_python_parser::{ParseOptions, parse}; use ruff_python_trivia::{ - BackwardsTokenizer, CommentRanges, SimpleToken, SimpleTokenKind, indentation_at_offset, + BackwardsTokenizer, SimpleToken, SimpleTokenKind, TriviaRanges, indentation_at_offset, }; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -76,13 +76,14 @@ pub fn format_range( let parsed = parse(source, ParseOptions::from(options.source_type()))?; let source_code = SourceCode::new(source); - let comment_ranges = CommentRanges::from(parsed.tokens()); - let comments = Comments::from_ast(parsed.syntax(), source_code, &comment_ranges); + let trivia = TriviaRanges::from(parsed.tokens()); + let comments = Comments::from_ast(parsed.syntax(), source_code, &trivia); let mut context = PyFormatContext::new( options.with_source_map_generation(SourceMapGeneration::Enabled), source, comments, + &trivia, parsed.tokens(), ); @@ -506,7 +507,7 @@ impl NarrowRange<'_> { }) = BackwardsTokenizer::up_to( first_child.start(), self.context.source(), - self.context.comments().ranges(), + self.context.trivia().comments(), ) .skip_trivia() .next() diff --git a/crates/ruff_python_formatter/src/statement/stmt_assign.rs b/crates/ruff_python_formatter/src/statement/stmt_assign.rs index a8ecf3fedb7b2d..8b42ea90a08607 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_assign.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_assign.rs @@ -11,8 +11,7 @@ use crate::comments::{ use crate::context::{NodeLevel, WithNodeLevel}; use crate::expression::expr_lambda::ExprLambdaLayout; use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize, is_expression_parenthesized, - optional_parentheses, + NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize, optional_parentheses, }; use crate::expression::{ can_omit_optional_parentheses, has_own_parentheses, has_parentheses, @@ -72,11 +71,7 @@ impl FormatNodeRule for FormatStmtAssign { // Avoid parenthesizing the value for single-target assignments where the // target has its own parentheses (list, dict, tuple, ...) and the target expands. else if has_target_own_parentheses(first, f.context()) - && !is_expression_parenthesized( - first.into(), - f.context().comments().ranges(), - f.context().source(), - ) + && !f.context().is_expression_parenthesized(first.into()) { FormatStatementsLastExpression::RightToLeft { before_operator: AnyBeforeOperator::Expression(first), diff --git a/crates/ruff_python_formatter/src/statement/stmt_aug_assign.rs b/crates/ruff_python_formatter/src/statement/stmt_aug_assign.rs index 6643959b3bfd2d..d7ba9ddffeb6ff 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_aug_assign.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_aug_assign.rs @@ -1,7 +1,6 @@ use ruff_formatter::write; use ruff_python_ast::StmtAugAssign; -use crate::expression::parentheses::is_expression_parenthesized; use crate::prelude::*; use crate::statement::stmt_assign::{ AnyAssignmentOperator, AnyBeforeOperator, FormatStatementsLastExpression, @@ -24,11 +23,7 @@ impl FormatNodeRule for FormatStmtAugAssign { } = item; if has_target_own_parentheses(target, f.context()) - && !is_expression_parenthesized( - target.into(), - f.context().comments().ranges(), - f.context().source(), - ) + && !f.context().is_expression_parenthesized(target.into()) { FormatStatementsLastExpression::RightToLeft { before_operator: AnyBeforeOperator::Expression(target), diff --git a/crates/ruff_python_formatter/src/statement/stmt_with.rs b/crates/ruff_python_formatter/src/statement/stmt_with.rs index 4e2c6bc8769496..e6876755eb4392 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_with.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_with.rs @@ -7,9 +7,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::builders::parenthesize_if_expands; use crate::comments::SourceComment; use crate::expression::can_omit_optional_parentheses; -use crate::expression::parentheses::{ - is_expression_parenthesized, optional_parentheses, parenthesized, -}; +use crate::expression::parentheses::{optional_parentheses, parenthesized}; use crate::other::commas; use crate::other::with_item::WithItemLayout; use crate::prelude::*; @@ -292,11 +290,7 @@ impl<'a> WithItemsLayout<'a> { // Preserve the parentheses around the context expression instead of parenthesizing the entire // with items. - if is_expression_parenthesized( - (&single.context_expr).into(), - context.comments().ranges(), - context.source(), - ) { + if context.is_expression_parenthesized((&single.context_expr).into()) { return Ok(Self::SingleParenthesizedContextManager(single)); } } diff --git a/crates/ruff_python_formatter/src/statement/suite.rs b/crates/ruff_python_formatter/src/statement/suite.rs index b1bcef5a288bfc..913548768eb574 100644 --- a/crates/ruff_python_formatter/src/statement/suite.rs +++ b/crates/ruff_python_formatter/src/statement/suite.rs @@ -985,7 +985,7 @@ fn new_logical_line_between_statements(source: &str, between_statement_range: Te mod tests { use ruff_formatter::format; use ruff_python_parser::parse_module; - use ruff_python_trivia::CommentRanges; + use ruff_python_trivia::TriviaRanges; use crate::PyFormatOptions; use crate::comments::Comments; @@ -1015,12 +1015,13 @@ def trailing_func(): "; let parsed = parse_module(source).unwrap(); - let comment_ranges = CommentRanges::from(parsed.tokens()); + let trivia = TriviaRanges::from(parsed.tokens()); let context = PyFormatContext::new( PyFormatOptions::default(), source, - Comments::from_ranges(&comment_ranges), + Comments::empty(), + &trivia, parsed.tokens(), ); diff --git a/crates/ruff_python_formatter/src/string/docstring.rs b/crates/ruff_python_formatter/src/string/docstring.rs index f5c73c3f70952c..5347317a23ef14 100644 --- a/crates/ruff_python_formatter/src/string/docstring.rs +++ b/crates/ruff_python_formatter/src/string/docstring.rs @@ -12,7 +12,7 @@ use regex::Regex; use ruff_formatter::printer::SourceMapGeneration; use ruff_python_ast::{AnyStringFlags, StringFlags, str::Quote}; use ruff_python_parser::ParseOptions; -use ruff_python_trivia::CommentRanges; +use ruff_python_trivia::TriviaRanges; use { ruff_formatter::{FormatOptions, IndentStyle, LineWidth, Printed, write}, ruff_python_trivia::{PythonWhitespace, is_python_whitespace}, @@ -1581,11 +1581,11 @@ fn docstring_format_source( ) -> Result { let source_type = options.source_type(); let parsed = ruff_python_parser::parse(source, ParseOptions::from(source_type))?; - let comment_ranges = CommentRanges::from(parsed.tokens()); + let trivia = TriviaRanges::from(parsed.tokens()); let source_code = ruff_formatter::SourceCode::new(source); - let comments = crate::Comments::from_ast(parsed.syntax(), source_code, &comment_ranges); + let comments = crate::Comments::from_ast(parsed.syntax(), source_code, &trivia); - let ctx = PyFormatContext::new(options, source, comments, parsed.tokens()) + let ctx = PyFormatContext::new(options, source, comments, &trivia, parsed.tokens()) .in_docstring(docstring_quote_style); let formatted = crate::format!(ctx, [parsed.syntax().format()])?; formatted diff --git a/crates/ruff_python_trivia/Cargo.toml b/crates/ruff_python_trivia/Cargo.toml index c37de06e3d35f9..73ff8f20b64987 100644 --- a/crates/ruff_python_trivia/Cargo.toml +++ b/crates/ruff_python_trivia/Cargo.toml @@ -17,6 +17,7 @@ ruff_source_file = { workspace = true } ruff_text_size = { workspace = true } itertools = { workspace = true } +rustc-hash = { workspace = true } unicode-ident = { workspace = true } [lints] diff --git a/crates/ruff_python_trivia/src/comment_ranges.rs b/crates/ruff_python_trivia/src/comment_ranges.rs index 9610a3d387e63e..12c1f99a003952 100644 --- a/crates/ruff_python_trivia/src/comment_ranges.rs +++ b/crates/ruff_python_trivia/src/comment_ranges.rs @@ -2,13 +2,69 @@ use std::fmt::{Debug, Formatter}; use std::ops::Deref; use itertools::Itertools; +use rustc_hash::FxHashSet; use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::{has_leading_content, has_trailing_content, is_python_whitespace}; -/// Stores the ranges of comments sorted by [`TextRange::start`] in increasing order. No two ranges are overlapping. +/// Token-derived range indexes shared by comment placement and formatting. +#[derive(Clone, Default)] +pub struct TriviaRanges { + comments: CommentRanges, + parenthesized_expressions: ParenthesizedExpressions, +} + +impl TriviaRanges { + /// Creates a combined set of token-derived range indexes. + pub fn new( + comments: CommentRanges, + parenthesized_expressions: ParenthesizedExpressions, + ) -> Self { + Self { + comments, + parenthesized_expressions, + } + } + + /// Returns the indexed comment ranges. + pub fn comments(&self) -> &CommentRanges { + &self.comments + } + + /// Returns the indexed parenthesized expression ranges. + pub fn parenthesized(&self) -> &ParenthesizedExpressions { + &self.parenthesized_expressions + } +} + +impl Debug for TriviaRanges { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.comments.fmt(f) + } +} + +/// Index of source ranges enclosed by matching parentheses. +#[derive(Clone, Default)] +pub struct ParenthesizedExpressions { + ranges: FxHashSet, +} + +impl ParenthesizedExpressions { + /// Creates an index from parenthesized expression ranges. + pub fn new(ranges: FxHashSet) -> Self { + Self { ranges } + } + + /// Returns `true` if the index contains `range`. + pub fn contains(&self, range: TextRange) -> bool { + self.ranges.contains(&range) + } +} + +/// Stores the ranges of comments sorted by [`TextRange::start`] in increasing order. No two ranges +/// are overlapping. #[derive(Clone, Default)] pub struct CommentRanges { raw: Vec, diff --git a/crates/ruff_python_trivia/src/lib.rs b/crates/ruff_python_trivia/src/lib.rs index 782662170b6fab..f5cf9e67bb95c0 100644 --- a/crates/ruff_python_trivia/src/lib.rs +++ b/crates/ruff_python_trivia/src/lib.rs @@ -6,7 +6,7 @@ pub mod textwrap; mod tokenizer; mod whitespace; -pub use comment_ranges::CommentRanges; +pub use comment_ranges::{CommentRanges, ParenthesizedExpressions, TriviaRanges}; pub use comments::*; pub use cursor::*; pub use pragmas::*; diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index 71ee004ac9f7ff..d6f9560e55d163 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -21,7 +21,7 @@ use ruff_python_codegen::Stylist; use ruff_python_formatter::{PyFormatContext, QuoteStyle, format_module_ast, pretty_comments}; use ruff_python_index::Indexer; use ruff_python_parser::{Mode, ParseOptions, Parsed, parse, parse_unchecked}; -use ruff_python_trivia::CommentRanges; +use ruff_python_trivia::TriviaRanges; use ruff_ranged_value::{ValueSource, ValueSourceGuard}; use ruff_source_file::{OneIndexed, PositionEncoding as SourcePositionEncoding, SourceLocation}; use ruff_text_size::Ranged; @@ -496,8 +496,8 @@ impl Workspace { pub fn comments(&self, contents: &str) -> Result { let parsed = ParsedModule::from_source(contents)?; - let comment_ranges = CommentRanges::from(parsed.parsed.tokens()); - let comments = pretty_comments(parsed.parsed.syntax(), &comment_ranges, contents); + let trivia_ranges = TriviaRanges::from(parsed.parsed.tokens()); + let comments = pretty_comments(parsed.parsed.syntax(), &trivia_ranges, contents); Ok(comments) } @@ -522,17 +522,17 @@ pub(crate) fn into_error(err: E) -> Error { struct ParsedModule<'a> { source_code: &'a str, parsed: Parsed, - comment_ranges: CommentRanges, + trivia_ranges: TriviaRanges, } impl<'a> ParsedModule<'a> { fn from_source(source_code: &'a str) -> Result { let parsed = parse(source_code, ParseOptions::from(Mode::Module)).map_err(into_error)?; - let comment_ranges = CommentRanges::from(parsed.tokens()); + let trivia_ranges = TriviaRanges::from(parsed.tokens()); Ok(Self { source_code, parsed, - comment_ranges, + trivia_ranges, }) } @@ -543,12 +543,7 @@ impl<'a> ParsedModule<'a> { .to_format_options(PySourceType::default(), self.source_code, None) .with_source_map_generation(SourceMapGeneration::Enabled); - format_module_ast( - &self.parsed, - &self.comment_ranges, - self.source_code, - options, - ) + format_module_ast(&self.parsed, &self.trivia_ranges, self.source_code, options) } }