Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions crates/oxc_formatter/src/formatter/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ pub struct Comments<'a> {
printed_count: usize,
/// The index of the type cast comment that has been printed already.
/// Used to prevent duplicate processing of special TypeScript type cast comments.
handled_type_cast_comment: usize,
last_handled_type_cast_comment: usize,
type_cast_node_span: Span,
/// Optional limit for the unprinted_comments view.
///
/// When set, [`Self::unprinted_comments()`] will only return comments up to this index,
Expand All @@ -141,7 +142,8 @@ impl<'a> Comments<'a> {
source_text,
comments,
printed_count: 0,
handled_type_cast_comment: 0,
last_handled_type_cast_comment: 0,
type_cast_node_span: Span::default(),
view_limit: None,
}
}
Expand Down Expand Up @@ -236,7 +238,7 @@ impl<'a> Comments<'a> {

/// Checks if there are any comments between the given positions.
pub fn has_comment_in_range(&self, start: u32, end: u32) -> bool {
self.comments_before_iter(end).any(|comment| comment.span.end >= start)
self.comments_before_iter(end).any(|comment| comment.span.end > start)
}

/// Checks if there are any comments within the given span.
Expand All @@ -253,10 +255,8 @@ impl<'a> Comments<'a> {

/// Checks if there are any leading own-line comments before the given position.
pub fn has_leading_own_line_comment(&self, start: u32) -> bool {
self.comments_before_iter(start).any(|comment| {
self.source_text.is_own_line_comment(comment)
|| self.source_text.lines_after(comment.span.end) > 0
})
self.comments_before_iter(start)
.any(|comment| self.source_text.lines_after(comment.span.end) > 0)
}

/// Checks if there are leading or trailing comments around `current_span`.
Expand Down Expand Up @@ -507,14 +507,20 @@ impl<'a> Comments<'a> {
)
}

/// Marks the most recently printed type cast comment as handled.
pub fn mark_as_handled_type_cast_comment(&mut self) {
self.handled_type_cast_comment = self.printed_count;
/// Marks the given span as a type cast node.
pub fn mark_as_type_cast_node(&mut self, node: &impl GetSpan) {
self.type_cast_node_span = node.span();
self.last_handled_type_cast_comment = self.printed_count;
}

/// Checks if the most recently printed type cast comment has been handled.
pub fn is_already_handled_type_cast_comment(&self) -> bool {
self.printed_count == self.handled_type_cast_comment
pub fn is_handled_type_cast_comment(&self) -> bool {
self.printed_count == self.last_handled_type_cast_comment
}

#[inline]
pub fn is_type_cast_node(&self, node: &impl GetSpan) -> bool {
self.type_cast_node_span == node.span()
}

/// Temporarily limits the unprinted comments view to only those before the given position.
Expand Down
27 changes: 19 additions & 8 deletions crates/oxc_formatter/src/formatter/source_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,24 @@ impl<'a> SourceText<'a> {
self.text_for(&span).chars().count()
}

/// Count consecutive line breaks after position
/// Count consecutive line breaks after position, returning `0` if only whitespace follows
pub fn lines_after(&self, end: u32) -> usize {
self.slice_from(end)
.chars()
.filter(|&c| !is_white_space_single_line(c))
.take_while(|&c| is_line_terminator(c))
.count()
let mut count = 0;
for char in self.slice_from(end).chars() {
if is_white_space_single_line(char) {
continue;
}

if is_line_terminator(char) {
count += 1;
continue;
}

return count;
}

// No non-whitespace characters found after position, so return `0` to avoid adding extra new lines
0
}

/// Count line breaks between syntax nodes, considering comments and parentheses
Expand All @@ -146,7 +157,7 @@ impl<'a> SourceText<'a> {

// Should skip the leading comments of the node.
if let Some(comment) = comments.first()
&& comment.span.end < start
&& comment.span.end <= start
{
start = comment.span.start;
}
Expand Down Expand Up @@ -188,7 +199,7 @@ impl<'a> SourceText<'a> {
count += 1;
}

count
0
}

pub fn is_own_line_comment(&self, comment: &Comment) -> bool {
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_formatter/src/generated/ast_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4189,7 +4189,7 @@ impl<'a> AstNode<'a, ComputedMemberExpression<'a>> {

#[inline]
pub fn expression(&self) -> &AstNode<'a, Expression<'a>> {
let following_node = self.following_node;
let following_node = None;
self.allocator.alloc(AstNode {
inner: &self.inner.expression,
allocator: self.allocator,
Expand Down
98 changes: 98 additions & 0 deletions crates/oxc_formatter/src/parentheses/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, Expression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, IdentifierReference<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

match self.name.as_str() {
"async" => {
matches!(self.parent, AstNodes::ForOfStatement(stmt) if !stmt.r#await && stmt.left.span().contains_inclusive(self.span))
Expand Down Expand Up @@ -165,6 +169,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, Super> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, NumericLiteral<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

if let AstNodes::StaticMemberExpression(member) = self.parent {
return member.object.without_parentheses().span() == self.span();
}
Expand All @@ -174,6 +182,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, NumericLiteral<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, StringLiteral<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

if let AstNodes::ExpressionStatement(stmt) = self.parent {
// `() => "foo"`
if let AstNodes::FunctionBody(arrow) = stmt.parent {
Expand Down Expand Up @@ -207,6 +219,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, ArrayExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, ObjectExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

let parent = self.parent;
is_class_extends(self.span, parent)
|| is_first_in_statement(
Expand Down Expand Up @@ -239,6 +255,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, ComputedMemberExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, StaticMemberExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

matches!(self.parent, AstNodes::NewExpression(_)) && {
ExpressionLeftSide::Expression(self.object()).iter().any(|expr| {
matches!(expr, ExpressionLeftSide::Expression(e) if
Expand All @@ -258,6 +278,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, PrivateFieldExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, CallExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

match self.parent {
AstNodes::NewExpression(_) => true,
AstNodes::ExportDefaultDeclaration(_) => {
Expand All @@ -280,12 +304,20 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, CallExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, NewExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

is_class_extends(self.span, self.parent)
}
}

impl<'a> NeedsParentheses<'a> for AstNode<'a, UpdateExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

let parent = self.parent;
if self.prefix()
&& let AstNodes::UnaryExpression(unary) = parent
Expand All @@ -303,6 +335,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, UpdateExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, UnaryExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

let parent = self.parent;
match parent {
AstNodes::UnaryExpression(parent_unary) => {
Expand All @@ -323,6 +359,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, UnaryExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, BinaryExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

(self.operator.is_in() && is_in_for_initializer(self))
|| binary_like_needs_parens(BinaryLikeExpression::BinaryExpression(self))
}
Expand Down Expand Up @@ -371,12 +411,20 @@ fn is_in_for_initializer(expr: &AstNode<'_, BinaryExpression<'_>>) -> bool {
impl<'a> NeedsParentheses<'a> for AstNode<'a, PrivateInExpression<'a>> {
#[inline]
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

is_class_extends(self.span, self.parent)
}
}

impl<'a> NeedsParentheses<'a> for AstNode<'a, LogicalExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

let parent = self.parent;
if let AstNodes::LogicalExpression(parent) = parent {
parent.operator() != self.operator()
Expand All @@ -392,6 +440,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, LogicalExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, ConditionalExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

let parent = self.parent;
if matches!(
parent,
Expand Down Expand Up @@ -420,6 +472,11 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, Function<'a>> {
if self.r#type() != FunctionType::FunctionExpression {
return false;
}

if f.comments().is_type_cast_node(self) {
return false;
}

let parent = self.parent;
matches!(
parent,
Expand All @@ -436,6 +493,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, Function<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, AssignmentExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

match self.parent {
// Expression statements, only object destructuring needs parens:
// - `a = b` = no parens
Expand Down Expand Up @@ -519,6 +580,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, AssignmentExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, SequenceExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

!matches!(
self.parent,
AstNodes::ReturnStatement(_)
Expand All @@ -534,12 +599,20 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, SequenceExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, AwaitExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

await_or_yield_needs_parens(self.span(), self.parent)
}
}

impl<'a> NeedsParentheses<'a> for AstNode<'a, ChainExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

match self.parent {
AstNodes::NewExpression(_) => true,
AstNodes::CallExpression(call) => !call.optional,
Expand All @@ -557,6 +630,11 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, Class<'a>> {
if self.r#type() != ClassType::ClassExpression {
return false;
}

if f.comments().is_type_cast_node(self) {
return false;
}

let parent = self.parent;
match parent {
AstNodes::CallExpression(_)
Expand All @@ -583,6 +661,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, ParenthesizedExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, ArrowFunctionExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

let parent = self.parent;
if matches!(
parent,
Expand All @@ -606,6 +688,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, ArrowFunctionExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, YieldExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

let parent = self.parent;
matches!(parent, AstNodes::AwaitExpression(_) | AstNodes::TSTypeAssertion(_))
|| await_or_yield_needs_parens(self.span(), parent)
Expand All @@ -614,6 +700,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, YieldExpression<'a>> {

impl<'a> NeedsParentheses<'a> for AstNode<'a, ImportExpression<'a>> {
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

matches!(self.parent, AstNodes::NewExpression(_))
}
}
Expand Down Expand Up @@ -1022,12 +1112,20 @@ fn jsx_element_or_fragment_needs_paren(span: Span, parent: &AstNodes<'_>) -> boo

impl NeedsParentheses<'_> for AstNode<'_, JSXElement<'_>> {
fn needs_parentheses(&self, f: &Formatter<'_, '_>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

jsx_element_or_fragment_needs_paren(self.span, self.parent)
}
}

impl NeedsParentheses<'_> for AstNode<'_, JSXFragment<'_>> {
fn needs_parentheses(&self, f: &Formatter<'_, '_>) -> bool {
if f.comments().is_type_cast_node(self) {
return false;
}

jsx_element_or_fragment_needs_paren(self.span, self.parent)
}
}
Loading
Loading