fix(formatter): print x.b and x.#b samely (#28193)

There is no reason to handle them separately.

This change also fixes idempotency issue with comments.
This commit is contained in:
leaysgur committed 2026-10-27 06:09:37 +00:00
1 parent 9ddccb7105
commit b8f4f34bba
27 files changed
+970 -209

No files matched your search

+88
View File
@@ -1079,3 +1079,91 @@ class A {
A class member named `in` or `instanceof` would continue the previous property's value as a binary operator, so the `;` before it is kept. A class member named `in` or `instanceof` would continue the previous property's value as a binary operator, so the `;` before it is kept.
Prettier checks only an identifier key, but also prints the string key `"in"` unquoted, producing a syntax error. Prettier checks only an identifier key, but also prints the string key `"in"` unquoted, producing a syntax error.
We check a string key by its value, which also keeps the `;` before a key that stays quoted (TypeScript, `quoteProps: "preserve"`), where Prettier's output stays valid. We check a string key by its value, which also keeps the `;` before a key that stays quoted (TypeScript, `quoteProps: "preserve"`), where Prettier's output stays valid.
## private-member-like-static
- Why: uniform-rule (same construct, same output: `a.b`)
- Pin: `tests/fixtures/js/private-fields/private-member-like-static.js`
```js
// input (printWidth: 80)
foo(someIdentifierName.#privateFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreaking);
this.#Factory.someMethodName(argumentOne).anotherMethodName(argumentTwo).third();
const a = obj.#decorator(someArgument)((aaaaaaaaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccc) => {
return a;
});
@A.#decorator a() {}
// ours: the same as `.b`
foo(
someIdentifierName.#privateFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreaking,
);
this.#Factory.someMethodName(argumentOne)
.anotherMethodName(argumentTwo)
.third();
const a = obj.#decorator(someArgument)((
aaaaaaaaaaaa,
bbbbbbbbbbbbbbbb,
cccccccccccccccc,
) => {
return a;
});
@A.#decorator a() {}
// prettier
foo(
someIdentifierName
.#privateFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreaking,
);
this.#Factory
.someMethodName(argumentOne)
.anotherMethodName(argumentTwo)
.third();
const a = obj.#decorator(someArgument)(
(aaaaaaaaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccc) => {
return a;
},
);
@(A.#decorator) a() {}
```
Both are a non-computed member access, so `.#b` follows every rule of `.b`.
Prettier prints `.b` like ours in all four, only `.#b` differently.
`@A.#decorator` is valid without parens (`DecoratorMemberExpression` accepts a `PrivateIdentifier`).
## member-property-own-line-comment
- Why: invariant
- Pin: `tests/fixtures/js/comments/member-property-own-line-comment.js`
```js
// input
x = a
// comment
.b;
x = a
// comment
.#b;
// ours
x =
a
// comment
.b;
x =
a
// comment
.#b;
// prettier
x =
// comment
a.b;
x =
a
.// comment
#b;
```
An own-line comment before a member's `.` stays before it, for `.b` and `.#b` alike.
Prettier moves it before the object for `.b`, and after the `.` for `.#b`; both cross a token.
@@ -56,6 +56,21 @@ impl<'parent, 'a> AstNodes<'parent, 'a> {
} }
} }
/// Check if this is a member expression: `a.b`, `a[b]`, or `a.#b`
pub fn is_member_expression(&self) -> bool {
matches!(
self,
AstNodes::StaticMemberExpression(_)
| AstNodes::ComputedMemberExpression(_)
| AstNodes::PrivateFieldExpression(_)
)
}
/// Check if this is a non-computed member expression: `a.b` or `a.#b`
pub fn is_non_computed_member_expression(&self) -> bool {
matches!(self, AstNodes::StaticMemberExpression(_) | AstNodes::PrivateFieldExpression(_))
}
/// Check if this is a binary-like expression: `a || b`, `a + b`, or `#x in b` /// Check if this is a binary-like expression: `a || b`, `a + b`, or `#x in b`
pub fn is_binary_like(&self) -> bool { pub fn is_binary_like(&self) -> bool {
matches!( matches!(
@@ -1196,9 +1196,6 @@ fn jsx_element_or_fragment_needs_paren(span: Span, parent: &AstNodes<'_, '_>) ->
AstNodes::TSAsExpression(_) AstNodes::TSAsExpression(_)
| AstNodes::TSSatisfiesExpression(_) | AstNodes::TSSatisfiesExpression(_)
| AstNodes::AwaitExpression(_) | AstNodes::AwaitExpression(_)
| AstNodes::StaticMemberExpression(_)
| AstNodes::ComputedMemberExpression(_)
| AstNodes::PrivateFieldExpression(_)
| AstNodes::SequenceExpression(_) | AstNodes::SequenceExpression(_)
| AstNodes::UnaryExpression(_) | AstNodes::UnaryExpression(_)
| AstNodes::TSNonNullExpression(_) | AstNodes::TSNonNullExpression(_)
@@ -1206,8 +1203,7 @@ fn jsx_element_or_fragment_needs_paren(span: Span, parent: &AstNodes<'_, '_>) ->
| AstNodes::TaggedTemplateExpression(_) | AstNodes::TaggedTemplateExpression(_)
| AstNodes::JSXSpreadAttribute(_) | AstNodes::JSXSpreadAttribute(_)
| AstNodes::JSXSpreadChild(_) => true, | AstNodes::JSXSpreadChild(_) => true,
_ if parent.is_call_like_callee_span(span) => true, _ => parent.is_member_expression() || parent.is_call_like_callee_span(span),
_ => false,
} }
} }
@@ -132,15 +132,10 @@ fn format_as_or_satisfies_expression<'a>(
} }
fn is_callee_or_object_context(span: Span, parent: &AstNodes<'_, '_>) -> bool { fn is_callee_or_object_context(span: Span, parent: &AstNodes<'_, '_>) -> bool {
match parent { parent.is_member_object_span(span)
// Static member
AstNodes::StaticMemberExpression(_) => true,
AstNodes::ComputedMemberExpression(member) => member.object.span() == span,
// Or CallExpression callee (Not NewExpression, to align with Prettier) // Or CallExpression callee (Not NewExpression, to align with Prettier)
// https://github.com/prettier/prettier/blob/fdfa6701767f5140a85902ecc9fb6444f5b4e3f8/src/language-js/print/cast-expression.js#L28-L33 // https://github.com/prettier/prettier/blob/fdfa6701767f5140a85902ecc9fb6444f5b4e3f8/src/language-js/print/cast-expression.js#L28-L33
// NOTE: We may revert this if resolved: https://github.com/prettier/prettier/issues/18406 // NOTE: We may revert this if resolved: https://github.com/prettier/prettier/issues/18406
// _ => parent.is_call_like_callee_span(span), // || parent.is_call_like_callee_span(span)
AstNodes::CallExpression(call) => call.callee.span() == span, || matches!(parent, AstNodes::CallExpression(call) if call.callee.span() == span)
_ => false,
}
} }
@@ -230,8 +230,11 @@ impl<'a> Format<'a, JsFormatContext<'a>> for BinaryLikeExpression<'a, '_> {
AstNodes::UnaryExpression(unary) if unary_argument_takes_comment_parens(unary, f) => { AstNodes::UnaryExpression(unary) if unary_argument_takes_comment_parens(unary, f) => {
return format_flattened_logical_expression(*self, false, f); return format_flattened_logical_expression(*self, false, f);
} }
AstNodes::StaticMemberExpression(_) | AstNodes::UnaryExpression(_) => true, AstNodes::UnaryExpression(_) => true,
_ => parent.is_call_like_callee_span(self.span()), _ => {
parent.is_non_computed_member_expression()
|| parent.is_call_like_callee_span(self.span())
}
}; };
if is_inside_parenthesis { if is_inside_parenthesis {
@@ -1122,12 +1122,16 @@ fn is_decorated_function(argument: &AstNode<'_, '_, Argument<'_>>) -> bool {
// Check if the decorator (callee.callee) is a simple identifier or member expression // Check if the decorator (callee.callee) is a simple identifier or member expression
let decorator = &callee_call.callee; let decorator = &callee_call.callee;
let is_valid_decorator = matches!(decorator, Expression::Identifier(_)) let is_valid_decorator = match decorator {
|| matches!( Expression::Identifier(_) => true,
decorator, Expression::StaticMemberExpression(member) => {
Expression::StaticMemberExpression(member) matches!(&member.object, Expression::Identifier(_))
if matches!(&member.object, Expression::Identifier(_)) }
); Expression::PrivateFieldExpression(member) => {
matches!(&member.object, Expression::Identifier(_))
}
_ => false,
};
if !is_valid_decorator { if !is_valid_decorator {
return false; return false;
@@ -42,10 +42,8 @@ impl<'a> FormatWrite<'a> for AstNode<'_, 'a, CallExpression<'a>> {
None None
}; };
if matches!( if callee.is_member_expression()
callee.as_ref(), && !is_verbatim_multiline_template_sole_arg(arguments.as_slice(), Some(self), f)
Expression::StaticMemberExpression(_) | Expression::ComputedMemberExpression(_)
) && !is_verbatim_multiline_template_sole_arg(arguments.as_slice(), Some(self), f)
&& !is_simple_module_import(&self.arguments(), f.comments()) && !is_simple_module_import(&self.arguments(), f.comments())
&& !is_test_call_expression(self, f.comments()) && !is_test_call_expression(self, f.comments())
{ {
+9 -9
View File
@@ -52,7 +52,7 @@ impl<'a> Format<'a, JsFormatContext<'a>> for AstNode<'_, 'a, ArenaVec<'a, Decora
} }
} }
fn is_identifier_or_static_member_only(callee: &Expression) -> bool { fn is_identifier_or_non_computed_member_only(callee: &Expression) -> bool {
let mut expr = callee; let mut expr = callee;
loop { loop {
match expr { match expr {
@@ -60,6 +60,9 @@ fn is_identifier_or_static_member_only(callee: &Expression) -> bool {
Expression::StaticMemberExpression(static_member) => { Expression::StaticMemberExpression(static_member) => {
expr = &static_member.object; expr = &static_member.object;
} }
Expression::PrivateFieldExpression(private_field) => {
expr = &private_field.object;
}
_ => break, _ => break,
} }
} }
@@ -73,16 +76,13 @@ impl<'a> FormatWrite<'a> for AstNode<'_, 'a, Decorator<'a>> {
// Determine if parentheses are required around decorator expressions // Determine if parentheses are required around decorator expressions
let needs_parentheses = match &self.expression { let needs_parentheses = match &self.expression {
// Identifiers: `@decorator` needs no parens
Expression::Identifier(_) => false,
// Call expressions: `@obj.method()` needs no parens, `@(complex().method)()` needs parens // Call expressions: `@obj.method()` needs no parens, `@(complex().method)()` needs parens
Expression::CallExpression(call) => !is_identifier_or_static_member_only(&call.callee), Expression::CallExpression(call) => {
// Static member expressions: `@obj.prop` needs no parens, `@(complex[key])` needs parens !is_identifier_or_non_computed_member_only(&call.callee)
Expression::StaticMemberExpression(static_member) => {
!is_identifier_or_static_member_only(&static_member.object)
} }
// All other expressions need parentheses: `@(a + b)`, `@(condition ? a : b)` // `@decorator`, `@obj.prop` and `@obj.#prop` need no parens,
_ => true, // all other expressions need parens: `@(complex[key])`, `@(a + b)`
expression => !is_identifier_or_non_computed_member_only(expression),
}; };
if needs_parentheses { if needs_parentheses {
@@ -95,25 +95,11 @@ impl<'a> AnyJsxTagWithChildren<'a, '_> {
| AstNodes::JSXExpressionContainer(_) | AstNodes::JSXExpressionContainer(_)
| AstNodes::ConditionalExpression(_) | AstNodes::ConditionalExpression(_)
| AstNodes::ExpressionStatement(_) => WrapState::NoWrap, | AstNodes::ExpressionStatement(_) => WrapState::NoWrap,
AstNodes::StaticMemberExpression(member) => {
if member.optional {
WrapState::NoWrap
} else {
WrapState::WrapOnBreak
}
}
// It is a argument of a call expression // It is a argument of a call expression
AstNodes::CallExpression(call) if call.is_argument_span(self.span()) => { AstNodes::CallExpression(call) if call.is_argument_span(self.span()) => {
WrapState::NoWrap WrapState::NoWrap
} }
AstNodes::NewExpression(new) if new.is_argument_span(self.span()) => WrapState::NoWrap, AstNodes::NewExpression(new) if new.is_argument_span(self.span()) => WrapState::NoWrap,
AstNodes::ComputedMemberExpression(member) => {
if member.optional {
WrapState::NoWrap
} else {
WrapState::WrapOnBreak
}
}
// Concise arrow body: `() => <div></div>` // Concise arrow body: `() => <div></div>`
#[expect(clippy::match_same_arms)] #[expect(clippy::match_same_arms)]
AstNodes::ArrowFunctionExpression(_) => WrapState::WrapOnBreak, AstNodes::ArrowFunctionExpression(_) => WrapState::WrapOnBreak,
@@ -26,48 +26,66 @@ impl<'a> FormatWrite<'a> for AstNode<'_, 'a, ComputedMemberExpression<'a>> {
impl<'a> FormatWrite<'a> for AstNode<'_, 'a, StaticMemberExpression<'a>> { impl<'a> FormatWrite<'a> for AstNode<'_, 'a, StaticMemberExpression<'a>> {
fn write(&self, f: &mut JsFormatter<'_, 'a>) { fn write(&self, f: &mut JsFormatter<'_, 'a>) {
// Before the recording: printing the object consumes its cast comment write_non_computed_member(self.object(), self.optional(), self.property(), f);
let object_is_cast_target = is_cast_target(self.object.span(), f); }
let is_member_chain = { }
let start = f.elements().len();
write!(f, [self.object()]);
f.elements()[start..].has_label(LabelId::of(JsLabels::MemberChain))
};
match layout(self, is_member_chain, object_is_cast_target, f) { /// `a.#b` is laid out the same as `a.b`.
StaticMemberLayout::NoBreak => { impl<'a> FormatWrite<'a> for AstNode<'_, 'a, PrivateFieldExpression<'a>> {
let format_no_break = fn write(&self, f: &mut JsFormatter<'_, 'a>) {
format_with(|f| write!(f, [operator_token(self.optional()), self.property()])); write_non_computed_member(self.object(), self.optional(), self.field(), f);
}
}
if is_member_chain { /// Writes `a.b` or `a.#b`.
write!(f, [labelled(LabelId::of(JsLabels::MemberChain), &format_no_break)]); fn write_non_computed_member<'b, 'a, P: GetSpan>(
} else { object: AstNode<'b, 'a, Expression<'a>>,
write!(f, [format_no_break]); optional: bool,
} property: AstNode<'b, 'a, P>,
} f: &mut JsFormatter<'_, 'a>,
StaticMemberLayout::BreakAfterObject => { ) where
write!( AstNode<'b, 'a, P>: Format<'a, JsFormatContext<'a>>,
f, {
[group(&indent(&format_args!( // Before the recording: printing the object consumes its cast comment
soft_line_break(), let object_is_cast_target = is_cast_target(object.span(), f);
&format_with(|f| { let is_member_chain = {
if f.comments().has_leading_own_line_comment(self.property.span.start) { let start = f.elements().len();
let comments = f write!(f, [object]);
.context() f.elements()[start..].has_label(LabelId::of(JsLabels::MemberChain))
.comments() };
.comments_before(self.property.span.start); let property_start = property.span().start;
write!( // The member expression itself
f, let member = property.parent();
[FormatLeadingComments::Comments(comments), soft_line_break()]
); match layout(&object, property_start, member, is_member_chain, object_is_cast_target, f) {
} StaticMemberLayout::NoBreak => {
}), let format_no_break = format_with(|f| write!(f, [operator_token(optional), property]));
operator_token(self.optional()),
self.property(), if is_member_chain {
)))] write!(f, [labelled(LabelId::of(JsLabels::MemberChain), &format_no_break)]);
); } else {
write!(f, [format_no_break]);
} }
} }
StaticMemberLayout::BreakAfterObject => {
write!(
f,
[group(&indent(&format_args!(
soft_line_break(),
&format_with(|f| {
if f.comments().has_leading_own_line_comment(property_start) {
let comments = f.context().comments().comments_before(property_start);
write!(
f,
[FormatLeadingComments::Comments(comments), soft_line_break()]
);
}
}),
operator_token(optional),
property,
)))]
);
}
} }
} }
@@ -87,23 +105,24 @@ fn operator_token(optional: bool) -> &'static str {
/// `object_is_cast_target`: /// `object_is_cast_target`:
/// a cast-target object is neither a call nor an identifier to the rules below (see `is_cast_target`). /// a cast-target object is neither a call nor an identifier to the rules below (see `is_cast_target`).
fn layout<'a>( fn layout<'a>(
node: &AstNode<'_, 'a, StaticMemberExpression<'a>>, object: &Expression<'a>,
property_start: u32,
member: &AstNodes<'_, 'a>,
is_member_chain: bool, is_member_chain: bool,
object_is_cast_target: bool, object_is_cast_target: bool,
f: &JsFormatter<'_, 'a>, f: &JsFormatter<'_, 'a>,
) -> StaticMemberLayout { ) -> StaticMemberLayout {
if f.comments().has_leading_own_line_comment(node.property.span.start) { if f.comments().has_leading_own_line_comment(property_start) {
return StaticMemberLayout::BreakAfterObject; return StaticMemberLayout::BreakAfterObject;
} }
// `a.b.c!` and `a.b?.c` // `a.b.c!` and `a.b?.c`
// `TSNonNullExpression` is a wrapper node for `!`, and `ChainExpression` is a wrapper node for `?.`, // `TSNonNullExpression` is a wrapper node for `!`, and `ChainExpression` is a wrapper node for `?.`,
// so we need to skip them to find the real parent node. // so we need to skip them to find the real parent node.
let mut parent = node.parent(); let mut parent = member.parent();
while matches!(parent, AstNodes::TSNonNullExpression(_) | AstNodes::ChainExpression(_)) { while matches!(parent, AstNodes::TSNonNullExpression(_) | AstNodes::ChainExpression(_)) {
parent = parent.parent(); parent = parent.parent();
} }
let object = &node.object;
let is_nested = match parent { let is_nested = match parent {
AstNodes::AssignmentExpression(_) | AstNodes::VariableDeclarator(_) => { AstNodes::AssignmentExpression(_) | AstNodes::VariableDeclarator(_) => {
@@ -117,8 +136,7 @@ fn layout<'a>(
false false
} }
AstNodes::StaticMemberExpression(_) | AstNodes::ComputedMemberExpression(_) => true, _ => parent.is_member_expression(),
_ => false,
}; };
if !is_nested && !object_is_cast_target && matches!(object, Expression::Identifier(_)) { if !is_nested && !object_is_cast_target && matches!(object, Expression::Identifier(_)) {
@@ -126,18 +144,17 @@ fn layout<'a>(
} }
let mut first_non_static_member_ancestor = parent; let mut first_non_static_member_ancestor = parent;
while matches!( while first_non_static_member_ancestor.is_member_expression()
first_non_static_member_ancestor, || matches!(
AstNodes::StaticMemberExpression(_) first_non_static_member_ancestor,
| AstNodes::ComputedMemberExpression(_) AstNodes::ChainExpression(_) | AstNodes::TSNonNullExpression(_)
| AstNodes::ChainExpression(_) )
| AstNodes::TSNonNullExpression(_) {
) {
first_non_static_member_ancestor = first_non_static_member_ancestor.parent(); first_non_static_member_ancestor = first_non_static_member_ancestor.parent();
} }
match first_non_static_member_ancestor { match first_non_static_member_ancestor {
AstNodes::NewExpression(expr) if expr.callee.span().contains_inclusive(node.span()) => { AstNodes::NewExpression(expr) if expr.callee.span().contains_inclusive(member.span()) => {
StaticMemberLayout::NoBreak StaticMemberLayout::NoBreak
} }
AstNodes::AssignmentExpression(assignment) => { AstNodes::AssignmentExpression(assignment) => {
@@ -150,9 +167,3 @@ fn layout<'a>(
_ => StaticMemberLayout::BreakAfterObject, _ => StaticMemberLayout::BreakAfterObject,
} }
} }
impl<'a> FormatWrite<'a> for AstNode<'_, 'a, PrivateFieldExpression<'a>> {
fn write(&self, f: &mut JsFormatter<'_, 'a>) {
write!(f, [self.object(), self.optional().then_some("?"), ".", self.field()]);
}
}
+2 -5
View File
@@ -454,11 +454,8 @@ impl<'a> FormatWrite<'a> for AstNode<'_, 'a, AwaitExpression<'a>> {
fn write(&self, f: &mut JsFormatter<'_, 'a>) { fn write(&self, f: &mut JsFormatter<'_, 'a>) {
let format_inner = format_with(|f| write!(f, ["await", space(), self.argument()])); let format_inner = format_with(|f| write!(f, ["await", space(), self.argument()]));
let is_callee_or_object = match self.parent() { let is_callee_or_object = self.parent().is_member_object_span(self.span)
AstNodes::StaticMemberExpression(_) => true, || self.parent().is_call_like_callee_span(self.span);
AstNodes::ComputedMemberExpression(member) => member.object.span() == self.span(),
_ => self.parent().is_call_like_callee_span(self.span),
};
if !is_callee_or_object { if !is_callee_or_object {
write!(f, [format_inner]); write!(f, [format_inner]);
@@ -210,25 +210,18 @@ fn has_argument_leading_comments(argument: &AstNode<Expression>, f: &JsFormatter
// This check is based on // This check is based on
// <https://github.com/prettier/prettier/blob/7584432401a47a26943dd7a9ca9a8e032ead7285/src/language-js/comments/handle-comments.js#L335-L349> // <https://github.com/prettier/prettier/blob/7584432401a47a26943dd7a9ca9a8e032ead7285/src/language-js/comments/handle-comments.js#L335-L349>
if let LeftSide::Expression(left_side) = left_side { if let LeftSide::Expression(left_side) = left_side {
let has_leading_own_line_comment = match left_side { let member = match left_side {
Expression::ChainExpression(chain) => { Expression::ChainExpression(chain) => chain.expression.as_member_expression(),
if let ChainElement::StaticMemberExpression(member) = &chain.expression { _ => left_side.as_member_expression(),
let comments = f
.comments()
.comments_in_range(member.object.span().end, member.property.span.end);
is_own_line_comment_or_multi_line_comment(comments)
} else {
false
}
}
Expression::StaticMemberExpression(member) => {
let comments = f
.comments()
.comments_in_range(member.object.span().end, member.property.span.end);
is_own_line_comment_or_multi_line_comment(comments)
}
_ => false,
}; };
// `a.b` and `a.#b`, the comments after the object
let has_leading_own_line_comment = member.is_some_and(|member| {
!member.is_computed()
&& is_own_line_comment_or_multi_line_comment(
f.comments()
.comments_in_range(member.object().span().end, member.span().end),
)
});
if has_leading_own_line_comment { if has_leading_own_line_comment {
return true; return true;
+5 -17
View File
@@ -270,21 +270,9 @@ impl<'a> FormatConditionalLike<'a, '_> {
break; break;
} }
} }
AstNodes::StaticMemberExpression(member) => { member if member.is_member_object_span(expression_span) => {
if member.object.span() == expression_span { expression_span = member.span();
expression_span = member.span(); parent = member.parent();
parent = member.parent();
} else {
break;
}
}
AstNodes::ComputedMemberExpression(member) => {
if member.object.span() == expression_span {
expression_span = member.span();
parent = member.parent();
} else {
break;
}
} }
AstNodes::CallExpression(call) => { AstNodes::CallExpression(call) => {
if call.callee.span() == expression_span { if call.callee.span() == expression_span {
@@ -351,12 +339,12 @@ impl<'a> FormatConditionalLike<'a, '_> {
} }
} }
/// Returns `true` if this is the root conditional expression and the parent is a [`StaticMemberExpression`]. /// Returns `true` if this is the root conditional expression and the parent is a non-computed member: `.b` or `.#b`
#[inline] #[inline]
fn is_parent_static_member_expression(&self, layout: ConditionalLayout) -> bool { fn is_parent_static_member_expression(&self, layout: ConditionalLayout) -> bool {
layout.is_root() layout.is_root()
&& self.is_conditional_expression() && self.is_conditional_expression()
&& matches!(self.parent(), AstNodes::StaticMemberExpression(_)) && self.parent().is_non_computed_member_expression()
} }
/// Formats the test part of the conditional /// Formats the test part of the conditional
@@ -3,25 +3,27 @@ use std::ops::Deref;
use crate::{ use crate::{
ast_nodes::{AstNode, AstNodes}, ast_nodes::{AstNode, AstNodes},
format_args, format_args,
formatter::{JsFormatter, prelude::*, trivia::FormatLeadingComments}, formatter::{
JsFormatter,
prelude::*,
trivia::{FormatLeadingComments, format_trailing_comments},
},
utils::{call_expression::callee_opener, statement_body::write_comments_before_opener}, utils::{call_expression::callee_opener, statement_body::write_comments_before_opener},
write, write,
}; };
use oxc_ast::ast::*; use oxc_ast::ast::*;
use oxc_formatter_core::Format; use oxc_formatter_core::Format;
use oxc_span::GetSpan; use oxc_span::{GetSpan, Span};
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum CallExpressionPosition { pub enum CallExpressionPosition {
/// At the start of a call chain. /// At the start of a call chain.
/// `of` in `of().test` /// `of` in `of().test`
Start, Start,
/// Somewhere in the middle. /// Somewhere in the middle.
/// ///
/// `b` in `a.b().c()` /// `b` in `a.b().c()`
Middle, Middle,
/// At the end of a call chain (root) /// At the end of a call chain (root)
/// `c` in `a.b.c()` /// `c` in `a.b.c()`
End, End,
@@ -32,24 +34,22 @@ pub enum CallExpressionPosition {
pub enum ChainMember<'a, 'b> { pub enum ChainMember<'a, 'b> {
/// Holds onto a [oxc_ast::ast::StaticMemberExpression] /// Holds onto a [oxc_ast::ast::StaticMemberExpression]
StaticMember(&'b AstNode<'b, 'a, StaticMemberExpression<'a>>), StaticMember(&'b AstNode<'b, 'a, StaticMemberExpression<'a>>),
/// Holds onto a [oxc_ast::ast::PrivateFieldExpression], treated the same as `StaticMember`
PrivateField(&'b AstNode<'b, 'a, PrivateFieldExpression<'a>>),
/// Holds onto a [oxc_ast::ast::CallExpression] /// Holds onto a [oxc_ast::ast::CallExpression]
CallExpression { CallExpression {
expression: &'b AstNode<'b, 'a, CallExpression<'a>>, expression: &'b AstNode<'b, 'a, CallExpression<'a>>,
position: CallExpressionPosition, position: CallExpressionPosition,
}, },
/// Holds onto a [oxc_ast::ast::ComputedMemberExpression] /// Holds onto a [oxc_ast::ast::ComputedMemberExpression]
ComputedMember(&'b AstNode<'b, 'a, ComputedMemberExpression<'a>>), ComputedMember(&'b AstNode<'b, 'a, ComputedMemberExpression<'a>>),
TSNonNullExpression(&'b AstNode<'b, 'a, TSNonNullExpression<'a>>), TSNonNullExpression(&'b AstNode<'b, 'a, TSNonNullExpression<'a>>),
/// Any other node that is not one of the above
/// Any other node that are not [oxc_ast::ast::CallExpression] or [oxc_ast::ast::StaticMemberExpression]
/// Are tracked using this variant /// Are tracked using this variant
Node(&'b AstNode<'b, 'a, Expression<'a>>), Node(&'b AstNode<'b, 'a, Expression<'a>>),
} }
impl ChainMember<'_, '_> { impl<'a> ChainMember<'a, '_> {
pub(crate) const fn is_call_expression(&self) -> bool { pub(crate) const fn is_call_expression(&self) -> bool {
matches!(self, Self::CallExpression { .. }) matches!(self, Self::CallExpression { .. })
} }
@@ -58,9 +58,25 @@ impl ChainMember<'_, '_> {
matches!(self, Self::ComputedMember { .. }) matches!(self, Self::ComputedMember { .. })
} }
/// The object, the property span and the property name of `a.b` or `a.#b`
pub fn as_non_computed_member(&self) -> Option<(&'a Expression<'a>, Span, &'a str)> {
match self {
Self::StaticMember(member) => {
let member = member.inner();
Some((&member.object, member.property.span, member.property.name.as_str()))
}
Self::PrivateField(member) => {
let member = member.inner();
Some((&member.object, member.field.span, member.field.name.as_str()))
}
_ => None,
}
}
pub fn span(&self) -> oxc_span::Span { pub fn span(&self) -> oxc_span::Span {
match self { match self {
Self::StaticMember(member) => member.span(), Self::StaticMember(member) => member.span(),
Self::PrivateField(member) => member.span(),
Self::TSNonNullExpression(e) => e.span(), Self::TSNonNullExpression(e) => e.span(),
Self::CallExpression { expression, .. } => expression.span(), Self::CallExpression { expression, .. } => expression.span(),
Self::ComputedMember(member) => member.span(), Self::ComputedMember(member) => member.span(),
@@ -73,26 +89,10 @@ impl<'a> Format<'a, JsFormatContext<'a>> for ChainMember<'a, '_> {
fn fmt(&self, f: &mut JsFormatter<'_, 'a>) { fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
match self { match self {
Self::StaticMember(member) => { Self::StaticMember(member) => {
write!( write_member_lookup(member.optional(), member.property(), f);
f, }
[ Self::PrivateField(member) => {
FormatLeadingComments::Comments( write_member_lookup(member.optional(), member.field(), f);
f.context().comments().comments_before(member.property().span().start)
),
member.optional().then_some("?"),
".",
member.property()
]
);
// The callee of `A.b(c)` / `A.b<T>(c)`: its comments keep their side of the opener
if let AstNodes::CallExpression(call) = member.parent()
&& !call.optional
{
write_comments_before_opener(member.span().end, callee_opener(call), f);
} else {
member.format_trailing_comments(f);
}
} }
Self::TSNonNullExpression(e) => { Self::TSNonNullExpression(e) => {
e.format_leading_comments(f); e.format_leading_comments(f);
@@ -135,6 +135,43 @@ impl<'a> Format<'a, JsFormatContext<'a>> for ChainMember<'a, '_> {
} }
} }
/// Writes `.b` of `a.b` or `.#b` of `a.#b` with its comments.
fn write_member_lookup<'b, 'a, P: GetSpan>(
optional: bool,
property: AstNode<'b, 'a, P>,
f: &mut JsFormatter<'_, 'a>,
) where
AstNode<'b, 'a, P>: Format<'a, JsFormatContext<'a>>,
{
write!(
f,
[
FormatLeadingComments::Comments(
f.context().comments().comments_before(property.span().start)
),
optional.then_some("?"),
".",
property
]
);
// The member expression itself
let member = property.parent();
// The callee of `A.b(c)` / `A.b<T>(c)`: its comments keep their side of the opener
if let AstNodes::CallExpression(call) = member.parent()
&& !call.optional
{
write_comments_before_opener(member.span().end, callee_opener(call), f);
} else {
format_trailing_comments(
member.parent().span(),
member.span(),
property.following_span_start(),
)
.fmt(f);
}
}
pub struct FormatComputedMemberExpressionWithoutObject<'a, 'b>( pub struct FormatComputedMemberExpressionWithoutObject<'a, 'b>(
pub &'b AstNode<'b, 'a, ComputedMemberExpression<'a>>, pub &'b AstNode<'b, 'a, ComputedMemberExpression<'a>>,
); );
@@ -189,14 +189,16 @@ impl<'a, 'b> MemberChainGroup<'a, 'b> {
} }
fn needs_empty_line_before(&self, f: &JsFormatter) -> bool { fn needs_empty_line_before(&self, f: &JsFormatter) -> bool {
let Some(ChainMember::StaticMember(expression)) = self.members.first() else { let Some((object, property_span, _)) =
self.members.first().and_then(ChainMember::as_non_computed_member)
else {
return false; return false;
}; };
// Prettier doesn't preserve blank lines after a terminal call like `fn()` // Prettier doesn't preserve blank lines after a terminal call like `fn()`
// whose callee is not part of the chain: `fn()\n\n.bar()` becomes `fn().bar()`. // whose callee is not part of the chain: `fn()\n\n.bar()` becomes `fn().bar()`.
// <https://github.com/prettier/prettier/blob/812a4d0071270f61a7aa549d625b618be7e09d71/src/language-js/print/member-chain.js#L101-L121> // <https://github.com/prettier/prettier/blob/812a4d0071270f61a7aa549d625b618be7e09d71/src/language-js/print/member-chain.js#L101-L121>
if let Expression::CallExpression(call) = expression.object().as_ref() if let Expression::CallExpression(call) = object
&& !call.callee.is_member_expression() && !call.callee.is_member_expression()
&& !call.callee.is_call_expression() && !call.callee.is_call_expression()
{ {
@@ -208,8 +210,8 @@ impl<'a, 'b> MemberChainGroup<'a, 'b> {
// `A \n\n/* comment . */ . / comment . */ B` // `A \n\n/* comment . */ . / comment . */ B`
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Check whether has more than 1 continuous new lines before the operator (`.`) // Check whether has more than 1 continuous new lines before the operator (`.`)
let start = expression.object().span().end; let start = object.span().end;
let mut end = expression.property().span().start; let mut end = property_span.start;
// We should found the real `end` position that is before comments and it is not after the operator `.` // We should found the real `end` position that is before comments and it is not after the operator `.`
if let Some(printed_comment) = f if let Some(printed_comment) = f
@@ -107,8 +107,10 @@ impl<'a, 'b> MemberChain<'a, 'b> {
Expression::ThisExpression(_) => true, Expression::ThisExpression(_) => true,
_ => false, _ => false,
} }
} else if let Some(ChainMember::StaticMember(expression)) = self.head.members().last() { } else if let Some((_, _, name)) =
has_computed_property || is_factory(&expression.property().name) self.head.members().last().and_then(ChainMember::as_non_computed_member)
{
has_computed_property || is_factory(name)
} else { } else {
false false
} }
@@ -185,11 +187,10 @@ impl<'a, 'b> MemberChain<'a, 'b> {
/// Check if a member has comments between object and property or end-of-line comments after it /// Check if a member has comments between object and property or end-of-line comments after it
fn has_comment_in_member(member: &ChainMember, comments: &Comments) -> bool { fn has_comment_in_member(member: &ChainMember, comments: &Comments) -> bool {
matches!( member.as_non_computed_member().is_some_and(|(object, property_span, _)| {
member, comments.has_comment_in_range(object.span().end, property_span.start)
ChainMember::StaticMember(member) || comments.has_end_of_line_comment_after(member.span().end)
if comments.has_comment_in_range(member.object().span().end, member.property().span.start) || comments.has_end_of_line_comment_after(member.span.end) })
)
} }
fn has_comment(&self, f: &JsFormatter<'_, 'a>) -> bool { fn has_comment(&self, f: &JsFormatter<'_, 'a>) -> bool {
@@ -291,7 +292,9 @@ fn get_split_index_of_head_and_tail_groups(members: &[ChainMember<'_, '_>]) -> u
.position(|member| { .position(|member| {
!matches!( !matches!(
member, member,
ChainMember::StaticMember { .. } | ChainMember::ComputedMember { .. } ChainMember::StaticMember { .. }
| ChainMember::PrivateField { .. }
| ChainMember::ComputedMember { .. }
) )
}) })
.map_or(rest.len(), |index| { .map_or(rest.len(), |index| {
@@ -324,7 +327,9 @@ fn compute_remaining_groups<'a, 'b>(
ChainMember::ComputedMember(_) if is_computed_array_member_access(&member) => { ChainMember::ComputedMember(_) if is_computed_array_member_access(&member) => {
groups_builder.start_or_continue_group(member); groups_builder.start_or_continue_group(member);
} }
ChainMember::StaticMember(_) | ChainMember::ComputedMember(_) => { ChainMember::StaticMember(_)
| ChainMember::PrivateField(_)
| ChainMember::ComputedMember(_) => {
// if we have seen a CallExpression, we want to close the group. // if we have seen a CallExpression, we want to close the group.
// The resultant group will be something like: [ . , then, () ]; // The resultant group will be something like: [ . , then, () ];
// `.` and `then` belong to the previous StaticMemberExpression, // `.` and `then` belong to the previous StaticMemberExpression,
@@ -420,12 +425,7 @@ fn chain_members_iter<'a, 'b>(
next: &mut Option<AstNode<'b, 'a, Expression<'a>>>| { next: &mut Option<AstNode<'b, 'a, Expression<'a>>>| {
let callee = expr.callee(); let callee = expr.callee();
let is_chain = matches!( let is_chain = callee.is_member_expression() || callee.is_call_expression();
callee.as_ref(),
Expression::StaticMemberExpression(_)
| Expression::ComputedMemberExpression(_)
| Expression::CallExpression(_)
);
if is_chain { if is_chain {
*next = Some(callee); *next = Some(callee);
@@ -462,12 +462,7 @@ fn chain_members_iter<'a, 'b>(
let member = match node { let member = match node {
AstNodeKind::CallExpression(expr) => { AstNodeKind::CallExpression(expr) => {
let callee = expr.callee(); let callee = expr.callee();
let is_chain = matches!( let is_chain = callee.is_member_expression() || callee.is_call_expression();
callee.as_ref(),
Expression::StaticMemberExpression(_)
| Expression::ComputedMemberExpression(_)
| Expression::CallExpression(_)
);
let position = if is_chain { let position = if is_chain {
CallExpressionPosition::Middle CallExpressionPosition::Middle
} else { } else {
@@ -480,6 +475,11 @@ fn chain_members_iter<'a, 'b>(
next = Some(expr.object()); next = Some(expr.object());
ChainMember::StaticMember(expr) ChainMember::StaticMember(expr)
} }
AstNodeKind::PrivateFieldExpression(expr) => {
let expr = expr.in_arena(allocator);
next = Some(expr.object());
ChainMember::PrivateField(expr)
}
AstNodeKind::ComputedMemberExpression(expr) => { AstNodeKind::ComputedMemberExpression(expr) => {
let expr = expr.in_arena(allocator); let expr = expr.in_arena(allocator);
next = Some(expr.object()); next = Some(expr.object());
@@ -0,0 +1,20 @@
// DIVERGENCES.md#member-property-own-line-comment
class A {
#b;
m() {
x = a
// comment
.b;
x = a
// comment
.#b;
return a
// comment
.#b;
}
n() {
return a
// comment
.b;
}
}
@@ -0,0 +1,87 @@
---
source: crates/oxc_formatter/tests/fixtures/mod.rs
---
==================== Input ====================
// DIVERGENCES.md#member-property-own-line-comment
class A {
#b;
m() {
x = a
// comment
.b;
x = a
// comment
.#b;
return a
// comment
.#b;
}
n() {
return a
// comment
.b;
}
}
==================== Output ====================
------------------
{ printWidth: 80 }
------------------
// DIVERGENCES.md#member-property-own-line-comment
class A {
#b;
m() {
x =
a
// comment
.b;
x =
a
// comment
.#b;
return (
a
// comment
.#b
);
}
n() {
return (
a
// comment
.b
);
}
}
-------------------
{ printWidth: 100 }
-------------------
// DIVERGENCES.md#member-property-own-line-comment
class A {
#b;
m() {
x =
a
// comment
.b;
x =
a
// comment
.#b;
return (
a
// comment
.#b
);
}
n() {
return (
a
// comment
.b
);
}
}
===================== End =====================
@@ -0,0 +1,4 @@
(<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}</div>)?.b;
(<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}</div>)?.[0];
(<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}</div>)?.b();
x = (<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnn}</div>)?.b;
@@ -0,0 +1,50 @@
---
source: crates/oxc_formatter/tests/fixtures/mod.rs
---
==================== Input ====================
(<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}</div>)?.b;
(<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}</div>)?.[0];
(<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}</div>)?.b();
x = (<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnn}</div>)?.b;
==================== Output ====================
------------------
{ printWidth: 80 }
------------------
(
<div className="some-long-class-name">
{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}
</div>
)?.b;
(
<div className="some-long-class-name">
{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}
</div>
)?.[0];
(
<div className="some-long-class-name">
{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}
</div>
)?.b();
x = (
<div className="some-long-class-name">
{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnn}
</div>
)?.b;
-------------------
{ printWidth: 100 }
-------------------
(<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}</div>)
?.b;
(
<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}</div>
)?.[0];
(
<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnnn}</div>
)?.b();
x = (
<div className="some-long-class-name">{someVeryLongContentExpressionnnnnnnnnnnnnnnnnnnnnn}</div>
)?.b;
===================== End =====================
@@ -0,0 +1,32 @@
// `a.#b` (class A) is laid out the same as `a._b` (class B)
class A {
#b;
#c;
async m() {
(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb).#b;
(await someVeryLongFunctionName(argumentNumberOne, argumentNumberTwo, argumentNumberThree)).#b;
(someCondition ? someVeryLongConsequentValueeeeeeeeeeee : someVeryLongAlternateValueeeeeeeeeeeeeeee).#b;
(someCondition ? someVeryLongConsequentValueeeeeeeeeeee : someVeryLongAlternateValueeeeeeeeeee).#b.method();
result = someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeeeeeee.#b;
result = someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeee.#b.#c;
this.#b.someMethodName().anotherMethodName().yetAnotherMethodName().finalMethodNameeeeeeeeeeeeee();
this.#b(argumentNumberOne, argumentNumberTwo, argumentNumberThree, argumentNumberFourrrrrrrrrr);
const value = someFunctionName(argumentNumberOne, argumentNumberTwo, argumentNumberThreeeeeeee).#b;
}
}
class B {
_b;
_c;
async m() {
(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)._b;
(await someVeryLongFunctionName(argumentNumberOne, argumentNumberTwo, argumentNumberThree))._b;
(someCondition ? someVeryLongConsequentValueeeeeeeeeeee : someVeryLongAlternateValueeeeeeeeeeeeeeee)._b;
(someCondition ? someVeryLongConsequentValueeeeeeeeeeee : someVeryLongAlternateValueeeeeeeeeee)._b.method();
result = someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeeeeeee._b;
result = someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeee._b._c;
this._b.someMethodName().anotherMethodName().yetAnotherMethodName().finalMethodNameeeeeeeeeeeeee();
this._b(argumentNumberOne, argumentNumberTwo, argumentNumberThree, argumentNumberFourrrrrrrrrr);
const value = someFunctionName(argumentNumberOne, argumentNumberTwo, argumentNumberThreeeeeeee)._b;
}
}
@@ -0,0 +1,213 @@
---
source: crates/oxc_formatter/tests/fixtures/mod.rs
---
==================== Input ====================
// `a.#b` (class A) is laid out the same as `a._b` (class B)
class A {
#b;
#c;
async m() {
(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb).#b;
(await someVeryLongFunctionName(argumentNumberOne, argumentNumberTwo, argumentNumberThree)).#b;
(someCondition ? someVeryLongConsequentValueeeeeeeeeeee : someVeryLongAlternateValueeeeeeeeeeeeeeee).#b;
(someCondition ? someVeryLongConsequentValueeeeeeeeeeee : someVeryLongAlternateValueeeeeeeeeee).#b.method();
result = someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeeeeeee.#b;
result = someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeee.#b.#c;
this.#b.someMethodName().anotherMethodName().yetAnotherMethodName().finalMethodNameeeeeeeeeeeeee();
this.#b(argumentNumberOne, argumentNumberTwo, argumentNumberThree, argumentNumberFourrrrrrrrrr);
const value = someFunctionName(argumentNumberOne, argumentNumberTwo, argumentNumberThreeeeeeee).#b;
}
}
class B {
_b;
_c;
async m() {
(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)._b;
(await someVeryLongFunctionName(argumentNumberOne, argumentNumberTwo, argumentNumberThree))._b;
(someCondition ? someVeryLongConsequentValueeeeeeeeeeee : someVeryLongAlternateValueeeeeeeeeeeeeeee)._b;
(someCondition ? someVeryLongConsequentValueeeeeeeeeeee : someVeryLongAlternateValueeeeeeeeeee)._b.method();
result = someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeeeeeee._b;
result = someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeee._b._c;
this._b.someMethodName().anotherMethodName().yetAnotherMethodName().finalMethodNameeeeeeeeeeeeee();
this._b(argumentNumberOne, argumentNumberTwo, argumentNumberThree, argumentNumberFourrrrrrrrrr);
const value = someFunctionName(argumentNumberOne, argumentNumberTwo, argumentNumberThreeeeeeee)._b;
}
}
==================== Output ====================
------------------
{ printWidth: 80 }
------------------
// `a.#b` (class A) is laid out the same as `a._b` (class B)
class A {
#b;
#c;
async m() {
(
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
).#b;
(
await someVeryLongFunctionName(
argumentNumberOne,
argumentNumberTwo,
argumentNumberThree,
)
).#b;
(someCondition
? someVeryLongConsequentValueeeeeeeeeeee
: someVeryLongAlternateValueeeeeeeeeeeeeeee
).#b;
(someCondition
? someVeryLongConsequentValueeeeeeeeeeee
: someVeryLongAlternateValueeeeeeeeeee
).#b.method();
result =
someVeryLongObjectName.someVeryLongPropertyName
.anotherVeryLongPropertyNameeeeeeeeeee.#b;
result =
someVeryLongObjectName.someVeryLongPropertyName
.anotherVeryLongPropertyNameeeeeee.#b.#c;
this.#b
.someMethodName()
.anotherMethodName()
.yetAnotherMethodName()
.finalMethodNameeeeeeeeeeeeee();
this.#b(
argumentNumberOne,
argumentNumberTwo,
argumentNumberThree,
argumentNumberFourrrrrrrrrr,
);
const value = someFunctionName(
argumentNumberOne,
argumentNumberTwo,
argumentNumberThreeeeeeee,
).#b;
}
}
class B {
_b;
_c;
async m() {
(
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)._b;
(
await someVeryLongFunctionName(
argumentNumberOne,
argumentNumberTwo,
argumentNumberThree,
)
)._b;
(someCondition
? someVeryLongConsequentValueeeeeeeeeeee
: someVeryLongAlternateValueeeeeeeeeeeeeeee
)._b;
(someCondition
? someVeryLongConsequentValueeeeeeeeeeee
: someVeryLongAlternateValueeeeeeeeeee
)._b.method();
result =
someVeryLongObjectName.someVeryLongPropertyName
.anotherVeryLongPropertyNameeeeeeeeeee._b;
result =
someVeryLongObjectName.someVeryLongPropertyName
.anotherVeryLongPropertyNameeeeeee._b._c;
this._b
.someMethodName()
.anotherMethodName()
.yetAnotherMethodName()
.finalMethodNameeeeeeeeeeeeee();
this._b(
argumentNumberOne,
argumentNumberTwo,
argumentNumberThree,
argumentNumberFourrrrrrrrrr,
);
const value = someFunctionName(
argumentNumberOne,
argumentNumberTwo,
argumentNumberThreeeeeeee,
)._b;
}
}
-------------------
{ printWidth: 100 }
-------------------
// `a.#b` (class A) is laid out the same as `a._b` (class B)
class A {
#b;
#c;
async m() {
(
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
).#b;
(await someVeryLongFunctionName(argumentNumberOne, argumentNumberTwo, argumentNumberThree)).#b;
(someCondition
? someVeryLongConsequentValueeeeeeeeeeee
: someVeryLongAlternateValueeeeeeeeeeeeeeee
).#b;
(someCondition
? someVeryLongConsequentValueeeeeeeeeeee
: someVeryLongAlternateValueeeeeeeeeee
).#b.method();
result =
someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeeeeeee.#b;
result =
someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeee.#b.#c;
this.#b
.someMethodName()
.anotherMethodName()
.yetAnotherMethodName()
.finalMethodNameeeeeeeeeeeeee();
this.#b(argumentNumberOne, argumentNumberTwo, argumentNumberThree, argumentNumberFourrrrrrrrrr);
const value = someFunctionName(
argumentNumberOne,
argumentNumberTwo,
argumentNumberThreeeeeeee,
).#b;
}
}
class B {
_b;
_c;
async m() {
(
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)._b;
(await someVeryLongFunctionName(argumentNumberOne, argumentNumberTwo, argumentNumberThree))._b;
(someCondition
? someVeryLongConsequentValueeeeeeeeeeee
: someVeryLongAlternateValueeeeeeeeeeeeeeee
)._b;
(someCondition
? someVeryLongConsequentValueeeeeeeeeeee
: someVeryLongAlternateValueeeeeeeeeee
)._b.method();
result =
someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeeeeeee._b;
result =
someVeryLongObjectName.someVeryLongPropertyName.anotherVeryLongPropertyNameeeeeee._b._c;
this._b
.someMethodName()
.anotherMethodName()
.yetAnotherMethodName()
.finalMethodNameeeeeeeeeeeeee();
this._b(argumentNumberOne, argumentNumberTwo, argumentNumberThree, argumentNumberFourrrrrrrrrr);
const value = someFunctionName(
argumentNumberOne,
argumentNumberTwo,
argumentNumberThreeeeeeee,
)._b;
}
}
===================== End =====================
@@ -0,0 +1,28 @@
// DIVERGENCES.md#private-member-like-static
class A {
static #decorator;
static decorator;
static #Factory;
static Factory;
#privateFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreaking;
publicFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreakingXX;
m() {
foo(someIdentifierName.#privateFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreaking);
foo(someIdentifierName.publicFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreakingXX);
this.#Factory.someMethodName(argumentOne).anotherMethodName(argumentTwo).third();
this.Factory.someMethodName(argumentOneX).anotherMethodName(argumentTwo).third();
}
n(obj) {
const a = obj.#decorator(someArgument)((aaaaaaaaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccc) => {
return a;
});
const b = obj.decorator(someArgument)((aaaaaaaaaaaa, bbbbbbbbbbbbbbbbb, cccccccccccccccc) => {
return a;
});
}
@A.#decorator a() {}
@A.decorator b() {}
}
@@ -0,0 +1,115 @@
---
source: crates/oxc_formatter/tests/fixtures/mod.rs
---
==================== Input ====================
// DIVERGENCES.md#private-member-like-static
class A {
static #decorator;
static decorator;
static #Factory;
static Factory;
#privateFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreaking;
publicFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreakingXX;
m() {
foo(someIdentifierName.#privateFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreaking);
foo(someIdentifierName.publicFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreakingXX);
this.#Factory.someMethodName(argumentOne).anotherMethodName(argumentTwo).third();
this.Factory.someMethodName(argumentOneX).anotherMethodName(argumentTwo).third();
}
n(obj) {
const a = obj.#decorator(someArgument)((aaaaaaaaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccc) => {
return a;
});
const b = obj.decorator(someArgument)((aaaaaaaaaaaa, bbbbbbbbbbbbbbbbb, cccccccccccccccc) => {
return a;
});
}
@A.#decorator a() {}
@A.decorator b() {}
}
==================== Output ====================
------------------
{ printWidth: 80 }
------------------
// DIVERGENCES.md#private-member-like-static
class A {
static #decorator;
static decorator;
static #Factory;
static Factory;
#privateFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreaking;
publicFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreakingXX;
m() {
foo(
someIdentifierName.#privateFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreaking,
);
foo(
someIdentifierName.publicFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreakingXX,
);
this.#Factory.someMethodName(argumentOne)
.anotherMethodName(argumentTwo)
.third();
this.Factory.someMethodName(argumentOneX)
.anotherMethodName(argumentTwo)
.third();
}
n(obj) {
const a = obj.#decorator(someArgument)((
aaaaaaaaaaaa,
bbbbbbbbbbbbbbbb,
cccccccccccccccc,
) => {
return a;
});
const b = obj.decorator(someArgument)((
aaaaaaaaaaaa,
bbbbbbbbbbbbbbbbb,
cccccccccccccccc,
) => {
return a;
});
}
@A.#decorator a() {}
@A.decorator b() {}
}
-------------------
{ printWidth: 100 }
-------------------
// DIVERGENCES.md#private-member-like-static
class A {
static #decorator;
static decorator;
static #Factory;
static Factory;
#privateFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreaking;
publicFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreakingXX;
m() {
foo(someIdentifierName.#privateFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreaking);
foo(someIdentifierName.publicFieldWithAVeryLongNameThatDoesNotFitOnTheLineEvenAfterBreakingXX);
this.#Factory.someMethodName(argumentOne).anotherMethodName(argumentTwo).third();
this.Factory.someMethodName(argumentOneX).anotherMethodName(argumentTwo).third();
}
n(obj) {
const a = obj.#decorator(someArgument)((aaaaaaaaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccc) => {
return a;
});
const b = obj.decorator(someArgument)((aaaaaaaaaaaa, bbbbbbbbbbbbbbbbb, cccccccccccccccc) => {
return a;
});
}
@A.#decorator a() {}
@A.decorator b() {}
}
===================== End =====================
@@ -0,0 +1,18 @@
// `a.#b` (class A) is laid out the same as `a._b` (class B)
class A {
#b;
m() {
(someVeryLongExpressionName as unknown as SomeVeryLongTypeName<WithSomeTypeArgumentsss>).#b;
(someVeryLongExpressionName satisfies SomeVeryLongTypeName<WithSomeTypeArgumentsssssss>).#b;
(<SomeVeryLongTypeName<WithSomeTypeArguments>>someVeryLongExpressionNameeeeeeeeeeeeeeeeeee).#b;
}
}
class B {
_b;
m() {
(someVeryLongExpressionName as unknown as SomeVeryLongTypeName<WithSomeTypeArgumentsss>)._b;
(someVeryLongExpressionName satisfies SomeVeryLongTypeName<WithSomeTypeArgumentsssssss>)._b;
(<SomeVeryLongTypeName<WithSomeTypeArguments>>someVeryLongExpressionNameeeeeeeeeeeeeeeeeee)._b;
}
}
@@ -0,0 +1,81 @@
---
source: crates/oxc_formatter/tests/fixtures/mod.rs
---
==================== Input ====================
// `a.#b` (class A) is laid out the same as `a._b` (class B)
class A {
#b;
m() {
(someVeryLongExpressionName as unknown as SomeVeryLongTypeName<WithSomeTypeArgumentsss>).#b;
(someVeryLongExpressionName satisfies SomeVeryLongTypeName<WithSomeTypeArgumentsssssss>).#b;
(<SomeVeryLongTypeName<WithSomeTypeArguments>>someVeryLongExpressionNameeeeeeeeeeeeeeeeeee).#b;
}
}
class B {
_b;
m() {
(someVeryLongExpressionName as unknown as SomeVeryLongTypeName<WithSomeTypeArgumentsss>)._b;
(someVeryLongExpressionName satisfies SomeVeryLongTypeName<WithSomeTypeArgumentsssssss>)._b;
(<SomeVeryLongTypeName<WithSomeTypeArguments>>someVeryLongExpressionNameeeeeeeeeeeeeeeeeee)._b;
}
}
==================== Output ====================
------------------
{ printWidth: 80 }
------------------
// `a.#b` (class A) is laid out the same as `a._b` (class B)
class A {
#b;
m() {
(
someVeryLongExpressionName as unknown as SomeVeryLongTypeName<WithSomeTypeArgumentsss>
).#b;
(
someVeryLongExpressionName satisfies SomeVeryLongTypeName<WithSomeTypeArgumentsssssss>
).#b;
(<SomeVeryLongTypeName<WithSomeTypeArguments>>(
someVeryLongExpressionNameeeeeeeeeeeeeeeeeee
)).#b;
}
}
class B {
_b;
m() {
(
someVeryLongExpressionName as unknown as SomeVeryLongTypeName<WithSomeTypeArgumentsss>
)._b;
(
someVeryLongExpressionName satisfies SomeVeryLongTypeName<WithSomeTypeArgumentsssssss>
)._b;
(<SomeVeryLongTypeName<WithSomeTypeArguments>>(
someVeryLongExpressionNameeeeeeeeeeeeeeeeeee
))._b;
}
}
-------------------
{ printWidth: 100 }
-------------------
// `a.#b` (class A) is laid out the same as `a._b` (class B)
class A {
#b;
m() {
(someVeryLongExpressionName as unknown as SomeVeryLongTypeName<WithSomeTypeArgumentsss>).#b;
(someVeryLongExpressionName satisfies SomeVeryLongTypeName<WithSomeTypeArgumentsssssss>).#b;
(<SomeVeryLongTypeName<WithSomeTypeArguments>>someVeryLongExpressionNameeeeeeeeeeeeeeeeeee).#b;
}
}
class B {
_b;
m() {
(someVeryLongExpressionName as unknown as SomeVeryLongTypeName<WithSomeTypeArgumentsss>)._b;
(someVeryLongExpressionName satisfies SomeVeryLongTypeName<WithSomeTypeArgumentsssssss>)._b;
(<SomeVeryLongTypeName<WithSomeTypeArguments>>someVeryLongExpressionNameeeeeeeeeeeeeeeeeee)._b;
}
}
===================== End =====================
@@ -33,14 +33,14 @@ RadixUIAdoptionSection.jsx:
pdf.mjs: pdf.mjs:
file size: 567296 # 567.30 kB file size: 567296 # 567.30 kB
sys allocs: 9736 sys allocs: 9755
sys reallocs: 167 sys reallocs: 167
sys deallocs: 9735 sys deallocs: 9754
sys alloc bytes: 2384856 # 2.38 MB sys alloc bytes: 2384856 # 2.38 MB
sys peak growth: 1156488 # 1.16 MB sys peak growth: 1156488 # 1.16 MB
arena allocs: 20583 arena allocs: 21673
arena reallocs: 428 arena reallocs: 446
arena size: 11029152 # 11.03 MB arena size: 11090656 # 11.09 MB
antd.js: antd.js:
file size: 6686316 # 6.69 MB file size: 6686316 # 6.69 MB
@@ -71,6 +71,6 @@ kitchen-sink.tsx:
sys deallocs: 12609 sys deallocs: 12609
sys alloc bytes: 3737938 # 3.74 MB sys alloc bytes: 3737938 # 3.74 MB
sys peak growth: 1491884 # 1.49 MB sys peak growth: 1491884 # 1.49 MB
arena allocs: 30666 arena allocs: 30688
arena reallocs: 1041 arena reallocs: 1043
arena size: 14614680 # 14.61 MB arena size: 14615800 # 14.62 MB