Skip to content

Commit 9531160

Browse files
committed
feat(formatter): complete printing StaticMemberExpression
1 parent 87a35d7 commit 9531160

File tree

4 files changed

+155
-42
lines changed

4 files changed

+155
-42
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
use oxc_ast::ast::*;
2+
3+
use crate::{
4+
JsLabels, format_args,
5+
formatter::{
6+
Buffer, Format, FormatResult, Formatter, prelude::*, trivia::format_dangling_comments,
7+
},
8+
generated::ast_nodes::{AstNode, AstNodes},
9+
options::Expand,
10+
write,
11+
};
12+
13+
use super::FormatWrite;
14+
15+
impl<'a> FormatWrite<'a> for AstNode<'a, ComputedMemberExpression<'a>> {
16+
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
17+
write!(f, self.object())?;
18+
19+
if matches!(self.expression, Expression::NumericLiteral(_)) {
20+
write!(f, [self.optional().then_some("?."), "[", self.expression(), "]"])
21+
} else {
22+
write!(
23+
f,
24+
group(&format_args!(
25+
self.optional().then_some("?."),
26+
"[",
27+
soft_block_indent(self.expression()),
28+
"]"
29+
))
30+
)
31+
}
32+
}
33+
}
34+
35+
impl<'a> FormatWrite<'a> for AstNode<'a, StaticMemberExpression<'a>> {
36+
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
37+
let is_member_chain = {
38+
let mut recording = f.start_recording();
39+
write!(recording, [self.object()])?;
40+
recording.stop().has_label(LabelId::of(JsLabels::MemberChain))
41+
};
42+
43+
match layout(self, is_member_chain) {
44+
StaticMemberLayout::NoBreak => {
45+
let format_no_break =
46+
format_with(|f| write!(f, [operator_token(self.optional()), self.property()]));
47+
48+
if is_member_chain {
49+
write!(f, [labelled(LabelId::of(JsLabels::MemberChain), &format_no_break)])
50+
} else {
51+
write!(f, [format_no_break])
52+
}
53+
}
54+
StaticMemberLayout::BreakAfterObject => {
55+
write!(
56+
f,
57+
[group(&indent(&format_args!(
58+
soft_line_break(),
59+
operator_token(self.optional()),
60+
self.property(),
61+
)))]
62+
)
63+
}
64+
}
65+
}
66+
}
67+
68+
#[derive(Debug, Copy, Clone)]
69+
enum StaticMemberLayout {
70+
/// Forces that there's no line break between the object, operator, and member
71+
NoBreak,
72+
73+
/// Breaks the static member expression after the object if the whole expression doesn't fit on a single line
74+
BreakAfterObject,
75+
}
76+
77+
fn operator_token(optional: bool) -> &'static str {
78+
if optional { "?." } else { "." }
79+
}
80+
81+
fn layout<'a>(
82+
node: &AstNode<'a, StaticMemberExpression<'a>>,
83+
is_member_chain: bool,
84+
) -> StaticMemberLayout {
85+
let parent = node.parent;
86+
let object = &node.object;
87+
88+
let is_nested = match parent {
89+
AstNodes::AssignmentExpression(_) | AstNodes::VariableDeclarator(_) => {
90+
let no_break = match object {
91+
Expression::CallExpression(call_expression) => {
92+
!call_expression.arguments.is_empty()
93+
}
94+
Expression::TSNonNullExpression(non_null_assertion) => {
95+
match &non_null_assertion.expression {
96+
Expression::CallExpression(call_expression) => {
97+
!call_expression.arguments.is_empty()
98+
}
99+
_ => false,
100+
}
101+
}
102+
_ => false,
103+
};
104+
105+
if no_break || is_member_chain {
106+
return StaticMemberLayout::NoBreak;
107+
}
108+
true
109+
}
110+
AstNodes::StaticMemberExpression(_) | AstNodes::ComputedMemberExpression(_) => true,
111+
_ => false,
112+
};
113+
114+
if !is_nested && matches!(object, Expression::Identifier(_)) {
115+
return StaticMemberLayout::NoBreak;
116+
}
117+
118+
let mut first_non_static_member_ancestor = parent;
119+
while matches!(
120+
first_non_static_member_ancestor,
121+
AstNodes::StaticMemberExpression(_) | AstNodes::ComputedMemberExpression(_)
122+
) {
123+
first_non_static_member_ancestor = first_non_static_member_ancestor.parent();
124+
}
125+
126+
match first_non_static_member_ancestor {
127+
AstNodes::NewExpression(_) => StaticMemberLayout::NoBreak,
128+
AstNodes::AssignmentExpression(assignment) => {
129+
if matches!(assignment.left, AssignmentTarget::AssignmentTargetIdentifier(_)) {
130+
StaticMemberLayout::BreakAfterObject
131+
} else {
132+
StaticMemberLayout::NoBreak
133+
}
134+
}
135+
_ => StaticMemberLayout::BreakAfterObject,
136+
}
137+
}
138+
139+
impl<'a> FormatWrite<'a> for AstNode<'a, PrivateFieldExpression<'a>> {
140+
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
141+
write!(f, [self.object(), self.optional().then_some("?"), ".", self.field()])
142+
}
143+
}

crates/oxc_formatter/src/write/mod.rs

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod export_declarations;
1212
mod function;
1313
mod import_declaration;
1414
mod jsx;
15+
mod member_expression;
1516
mod object_like;
1617
mod object_pattern_like;
1718
mod parameter_list;
@@ -286,38 +287,6 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TemplateElement<'a>> {
286287
}
287288
}
288289

289-
impl<'a> FormatWrite<'a> for AstNode<'a, ComputedMemberExpression<'a>> {
290-
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
291-
write!(f, self.object())?;
292-
293-
if matches!(self.expression, Expression::NumericLiteral(_)) {
294-
write!(f, [self.optional().then_some("?."), "[", self.expression(), "]"])
295-
} else {
296-
write!(
297-
f,
298-
group(&format_args!(
299-
self.optional().then_some("?."),
300-
"[",
301-
soft_block_indent(self.expression()),
302-
"]"
303-
))
304-
)
305-
}
306-
}
307-
}
308-
309-
impl<'a> FormatWrite<'a> for AstNode<'a, StaticMemberExpression<'a>> {
310-
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
311-
write!(f, [self.object(), self.optional().then_some("?"), ".", self.property()])
312-
}
313-
}
314-
315-
impl<'a> FormatWrite<'a> for AstNode<'a, PrivateFieldExpression<'a>> {
316-
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
317-
write!(f, [self.object(), self.optional().then_some("?"), ".", self.field()])
318-
}
319-
}
320-
321290
impl<'a> FormatWrite<'a> for AstNode<'a, CallExpression<'a>> {
322291
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
323292
let callee = self.callee();

tasks/prettier_conformance/snapshots/prettier.js.snap.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
js compatibility: 582/699 (83.26%)
1+
js compatibility: 583/699 (83.40%)
22

33
# Failed
44

55
| Spec path | Failed or Passed | Match ratio |
66
| :-------- | :--------------: | :---------: |
77
| js/arrows/curried.js | 💥💥 | 92.55% |
88
| js/arrows/semi/semi.js | 💥✨ | 0.00% |
9-
| js/assignment/issue-10218.js | 💥 | 52.63% |
9+
| js/assignment/issue-15534.js | 💥 | 25.00% |
1010
| js/assignment/sequence.js | 💥 | 71.43% |
1111
| js/chain-expression/issue-15785-3.js | 💥 | 50.00% |
1212
| js/class-comment/misc.js | 💥 | 72.73% |
@@ -69,7 +69,6 @@ js compatibility: 582/699 (83.26%)
6969
| js/last-argument-expansion/dangling-comment-in-arrow-function.js | 💥 | 22.22% |
7070
| js/line-suffix-boundary/boundary.js | 💥 | 36.73% |
7171
| js/logical_expressions/issue-7024.js | 💥 | 66.67% |
72-
| js/method-chain/break-last-member.js | 💥 | 80.56% |
7372
| js/method-chain/comment.js | 💥 | 97.56% |
7473
| js/method-chain/conditional.js | 💥 | 85.19% |
7574
| js/new-expression/new_expression.js | 💥 | 55.56% |
@@ -90,13 +89,13 @@ js compatibility: 582/699 (83.26%)
9089
| js/sequence-expression/ignore.js | 💥 | 42.86% |
9190
| js/strings/escaped.js | 💥💥 | 73.68% |
9291
| js/strings/template-literals.js | 💥💥 | 62.96% |
93-
| js/template/comment.js | 💥 | 23.08% |
92+
| js/template/comment.js | 💥 | 24.00% |
9493
| js/template/graphql.js | 💥 | 81.25% |
9594
| js/template/indent.js | 💥 | 85.71% |
9695
| js/template-align/indent.js | 💥💥 | 14.47% |
9796
| js/template-literals/binary-exporessions.js | 💥 | 0.00% |
9897
| js/template-literals/conditional-expressions.js | 💥 | 0.00% |
99-
| js/template-literals/expressions.js | 💥 | 88.89% |
98+
| js/template-literals/expressions.js | 💥 | 67.67% |
10099
| js/template-literals/indention.js | 💥 | 51.16% |
101100
| js/template-literals/logical-expressions.js | 💥 | 0.00% |
102101
| js/template-literals/sequence-expressions.js | 💥 | 0.00% |
@@ -117,7 +116,7 @@ js compatibility: 582/699 (83.26%)
117116
| jsx/expression-with-types/expression.js | 💥💥💥💥 | 0.00% |
118117
| jsx/fbt/test.js | 💥 | 84.06% |
119118
| jsx/ignore/jsx_ignore.js | 💥 | 82.57% |
120-
| jsx/jsx/await.js | 💥💥💥💥 | 93.75% |
121119
| jsx/jsx/quotes.js | 💥💥💥💥 | 79.41% |
120+
| jsx/jsx/template-literal-in-attr.js | 💥💥💥💥 | 85.71% |
122121
| jsx/single-attribute-per-line/single-attribute-per-line.js | 💥✨ | 43.37% |
123-
| jsx/text-wrap/test.js | 💥 | 98.68% |
122+
| jsx/text-wrap/test.js | 💥 | 98.85% |

tasks/prettier_conformance/snapshots/prettier.ts.snap.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ts compatibility: 323/573 (56.37%)
1+
ts compatibility: 321/573 (56.02%)
22

33
# Failed
44

@@ -7,10 +7,10 @@ ts compatibility: 323/573 (56.37%)
77
| jsx/expression-with-types/expression.js | 💥💥💥💥 | 0.00% |
88
| jsx/fbt/test.js | 💥 | 84.06% |
99
| jsx/ignore/jsx_ignore.js | 💥 | 82.57% |
10-
| jsx/jsx/await.js | 💥💥💥💥 | 93.75% |
1110
| jsx/jsx/quotes.js | 💥💥💥💥 | 79.41% |
11+
| jsx/jsx/template-literal-in-attr.js | 💥💥💥💥 | 85.71% |
1212
| jsx/single-attribute-per-line/single-attribute-per-line.js | 💥✨ | 43.37% |
13-
| jsx/text-wrap/test.js | 💥 | 98.68% |
13+
| jsx/text-wrap/test.js | 💥 | 98.85% |
1414
| typescript/ambient/ambient.ts | 💥 | 88.24% |
1515
| typescript/angular-component-examples/15934-computed.component.ts | 💥💥 | 76.92% |
1616
| typescript/angular-component-examples/15934.component.ts | 💥💥 | 53.85% |
@@ -41,6 +41,7 @@ ts compatibility: 323/573 (56.37%)
4141
| typescript/chain-expression/call-expression.ts | 💥 | 68.75% |
4242
| typescript/chain-expression/member-expression.ts | 💥 | 65.67% |
4343
| typescript/chain-expression/test.ts | 💥 | 0.00% |
44+
| typescript/chain-expression/test2.ts | 💥 | 56.25% |
4445
| typescript/class/constructor.ts | 💥 | 96.15% |
4546
| typescript/class/empty-method-body.ts | 💥 | 80.00% |
4647
| typescript/class/extends_implements.ts | 💥 | 86.30% |
@@ -217,6 +218,7 @@ ts compatibility: 323/573 (56.37%)
217218
| typescript/semi/no-semi.ts | 💥 | 88.89% |
218219
| typescript/template-literal-types/template-literal-types.ts | 💥 | 73.33% |
219220
| typescript/template-literals/as-expression.ts | 💥 | 14.29% |
221+
| typescript/template-literals/expressions.ts | 💥 | 0.00% |
220222
| typescript/test-declarations/test_declarations.ts | 💥💥 | 66.67% |
221223
| typescript/trailing-comma/arrow-functions.tsx | 💥💥💥 | 25.00% |
222224
| typescript/trailing-comma/trailing.ts | 💥💥💥 | 87.66% |

0 commit comments

Comments
 (0)