Skip to content
Merged
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
44 changes: 42 additions & 2 deletions crates/oxc_formatter/src/formatter/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,29 @@ pub struct Comments<'a> {
/// 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,
/// Optional limit for the unprinted_comments view.
///
/// When set, [`Self::unprinted_comments()`] will only return comments up to this index,
/// effectively hiding comments beyond this point from the formatter.
pub view_limit: Option<usize>,
}

impl<'a> Comments<'a> {
pub fn new(source_text: SourceText<'a>, comments: &'a [Comment]) -> Self {
Comments { source_text, comments, printed_count: 0, handled_type_cast_comment: 0 }
Comments {
source_text,
comments,
printed_count: 0,
handled_type_cast_comment: 0,
view_limit: None,
}
}

/// Returns comments that have not been printed yet.
#[inline]
pub fn unprinted_comments(&self) -> &'a [Comment] {
&self.comments[self.printed_count..]
let end = self.view_limit.unwrap_or(self.comments.len());
&self.comments[self.printed_count..end]
}

/// Returns comments that have already been printed.
Expand Down Expand Up @@ -486,6 +498,34 @@ impl<'a> Comments<'a> {
pub fn is_already_handled_type_cast_comment(&self) -> bool {
self.printed_count == self.handled_type_cast_comment
}

/// Temporarily limits the unprinted comments view to only those before the given position.
/// Returns the previous view limit to allow restoration.
pub fn limit_comments_up_to(&mut self, end_pos: u32) -> Option<usize> {
// Save the original limit for restoration
let original_limit = self.view_limit;

// Find the index of the first comment that starts at or after end_pos
// Using binary search would be more efficient for large comment arrays
let limit_index = self.comments[self.printed_count..]
.iter()
.position(|c| c.span.start >= end_pos)
.map_or(self.comments.len(), |idx| self.printed_count + idx);

// Only update if we're actually limiting the view
if limit_index < self.comments.len() {
self.view_limit = Some(limit_index);
}

original_limit
}

/// Restores the view limit to a previously saved value.
/// This is typically used after temporarily limiting the view with `limit_comments_up_to`.
#[inline]
pub fn restore_view_limit(&mut self, limit: Option<usize>) {
self.view_limit = limit;
}
}

/// Checks if a pattern matches at the given position.
Expand Down
129 changes: 73 additions & 56 deletions crates/oxc_formatter/src/utils/conditional.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
Format, FormatResult, FormatWrite,
formatter::{Formatter, prelude::*, trivia::FormatTrailingComments},
generated::ast_nodes::{AstNode, AstNodes},
utils::expression::FormatExpressionWithoutTrailingComments,
utils::format_node_without_trailing_comments::FormatNodeWithoutTrailingComments,
write,
};

Expand Down Expand Up @@ -171,32 +171,35 @@ impl<'a> FormatConditionalLike<'a, '_> {
fn layout(&self, f: &mut Formatter<'_, 'a>) -> ConditionalLayout {
let self_span = self.span();

let (is_test, is_consequent) = match self.parent() {
match self.parent() {
AstNodes::ConditionalExpression(parent) => {
let parent_expr = parent.as_ref();
(parent_expr.test.span() == self_span, parent_expr.consequent.span() == self_span)
if parent_expr.test.span() == self_span {
ConditionalLayout::NestedTest
} else if parent_expr.consequent.span() == self_span {
ConditionalLayout::NestedConsequent
} else {
ConditionalLayout::NestedAlternate
}
}
AstNodes::TSConditionalType(parent) => {
let parent_type = parent.as_ref();
// For TS conditional types, both check_type and extends_type are part of the test
let is_test = parent_type.check_type.span() == self_span
|| parent_type.extends_type.span() == self_span;
let is_consequent = parent_type.true_type.span() == self_span;
(is_test, is_consequent)
if is_test {
ConditionalLayout::NestedTest
} else if parent_type.true_type.span() == self_span {
ConditionalLayout::NestedConsequent
} else {
ConditionalLayout::NestedAlternate
}
}
_ => {
let jsx_chain =
f.context().source_type().is_jsx() && self.is_jsx_conditional_chain();
return ConditionalLayout::Root { jsx_chain };
ConditionalLayout::Root { jsx_chain }
}
};

if is_test {
ConditionalLayout::NestedTest
} else if is_consequent {
ConditionalLayout::NestedConsequent
} else {
ConditionalLayout::NestedAlternate
}
}

Expand Down Expand Up @@ -374,7 +377,7 @@ impl<'a> FormatConditionalLike<'a, '_> {
) -> FormatResult<()> {
let format_inner = format_with(|f| match self.conditional {
ConditionalLike::ConditionalExpression(conditional) => {
write!(f, FormatExpressionWithoutTrailingComments(conditional.test()))?;
write!(f, FormatNodeWithoutTrailingComments(conditional.test()))?;
format_trailing_comments(
conditional.test.span().end,
conditional.consequent.span().start,
Expand All @@ -390,8 +393,15 @@ impl<'a> FormatConditionalLike<'a, '_> {
space(),
"extends",
space(),
conditional.extends_type()
FormatNodeWithoutTrailingComments(conditional.extends_type())
]
)?;

format_trailing_comments(
conditional.extends_type.span().end,
conditional.true_type.span().start,
b'?',
f,
)
}
});
Expand All @@ -411,53 +421,60 @@ impl<'a> FormatConditionalLike<'a, '_> {
) -> FormatResult<()> {
write!(f, [soft_line_break_or_space(), "?", space()])?;

let format_consequent = format_with(|f| match self.conditional {
ConditionalLike::ConditionalExpression(conditional) => {
let is_consequent_nested = match self.conditional {
let format_consequent = format_with(|f| {
let format_consequent_with_trailing_comments =
format_once(|f| match self.conditional {
ConditionalLike::ConditionalExpression(conditional) => {
matches!(conditional.consequent, Expression::ConditionalExpression(_))
write!(f, FormatNodeWithoutTrailingComments(conditional.consequent()))?;
format_trailing_comments(
conditional.consequent.span().end,
conditional.alternate.span().start,
b':',
f,
)
}
ConditionalLike::TSConditionalType(conditional) => {
matches!(conditional.true_type, TSType::TSConditionalType(_))
}
};

let format_consequent = format_once(|f| {
write!(f, FormatExpressionWithoutTrailingComments(conditional.consequent()))?;
format_trailing_comments(
conditional.consequent.span().end,
conditional.alternate.span().start,
b':',
f,
)
});

let format_consequent = format_with(|f| {
if f.options().indent_style.is_space() {
write!(f, [align(2, &format_consequent)])
} else {
write!(f, [indent(&format_consequent)])
write!(f, FormatNodeWithoutTrailingComments(conditional.true_type()))?;
format_trailing_comments(
conditional.true_type.span().end,
conditional.false_type.span().start,
b':',
f,
)
}
});

if is_consequent_nested {
// Add parentheses around the consequent if it is a conditional expression and fits on the same line
// so that it's easier to identify the parts that belong to a conditional expression.
// `a ? b ? c: d : e` -> `a ? (b ? c: d) : e`
write!(
f,
[
if_group_fits_on_line(&text("(")),
format_consequent,
if_group_fits_on_line(&text(")"))
]
)
let format_consequent_with_proper_indentation = format_with(|f| {
if f.options().indent_style.is_space() {
write!(f, [align(2, &format_consequent_with_trailing_comments)])
} else {
write!(f, format_consequent)
write!(f, [indent(&format_consequent_with_trailing_comments)])
}
}
ConditionalLike::TSConditionalType(conditional) => {
write!(f, [conditional.true_type()])
});

let is_nested_consequent = match self.conditional {
ConditionalLike::ConditionalExpression(conditional) => {
matches!(conditional.consequent, Expression::ConditionalExpression(_))
}
ConditionalLike::TSConditionalType(conditional) => {
matches!(conditional.true_type, TSType::TSConditionalType(_))
}
};

if is_nested_consequent {
// Add parentheses around the consequent if it is a conditional expression and fits on the same line
// so that it's easier to identify the parts that belong to a conditional expression.
// `a ? b ? c: d : e` -> `a ? (b ? c: d) : e`
write!(
f,
[
if_group_fits_on_line(&text("(")),
format_consequent_with_proper_indentation,
if_group_fits_on_line(&text(")"))
]
)
} else {
write!(f, format_consequent_with_proper_indentation)
}
});

Expand Down Expand Up @@ -662,7 +679,7 @@ impl<'a> Format<'a> for FormatJsxChainExpression<'a, '_> {
}
.fmt(f)
} else {
FormatExpressionWithoutTrailingComments(self.expression).fmt(f)
FormatNodeWithoutTrailingComments(self.expression).fmt(f)
}
});

Expand Down
Loading
Loading