mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(formatter): keep parens around TSInstantiationExpression before ! and export (#27626)
Should keep parens around `(make<T>)!()`, otherwise parse error.
This commit is contained in:
1 parent
82ecc43053
commit
21dee96d67
5 files changed
+109
-25
No files matched your search
@@ -779,3 +779,29 @@ Prettier keeps an end-of-line comment there (`foo // c` + `(a)`, the same as our
|
||||
Its function declarations keep the same-line block comment outside (`function foo /* c */(a) {}`).
|
||||
Before `<`, Prettier glues an own-line comment back onto the callee's line (`foo// c` + `<T>(a)`); we keep it own-line.
|
||||
Before an optional call's `?.`, Prettier moves an own-line comment into the arguments as well; we keep it own-line.
|
||||
|
||||
## instantiation-expression-parens
|
||||
|
||||
- Why: semantics (Prettier's output no longer parses; prettier/prettier#20177)
|
||||
- Pin: `tests/fixtures/ts/parenthesis/instantiation-expression-parens.ts`
|
||||
|
||||
```ts
|
||||
// input
|
||||
const a = (make<T>)!;
|
||||
export default (class {})<string>;
|
||||
(class {})<string>;
|
||||
|
||||
// ours
|
||||
const a = (make<T>)!;
|
||||
export default (class {}<string>);
|
||||
(class {})<string>;
|
||||
|
||||
// prettier
|
||||
const a = make<T>!;
|
||||
export default class {}<string>;
|
||||
class {}<string>;
|
||||
```
|
||||
|
||||
Prettier drops the parens around an instantiation expression before `!`,
|
||||
and the ones that keep a leading `class`/`function` from starting a declaration or statement.
|
||||
Fixed in Prettier `main` by prettier/prettier#20177; we follow that output.
|
||||
@@ -960,14 +960,18 @@ fn class_extends_needs_parens_through_non_null(expression: &Expression<'_>) -> b
|
||||
|
||||
impl NeedsParentheses<'_> for AstNode<'_, TSInstantiationExpression<'_>> {
|
||||
fn needs_parentheses(&self, _f: &JsFormatter<'_, '_>) -> bool {
|
||||
let expr = match self.parent() {
|
||||
AstNodes::StaticMemberExpression(expr) => &expr.object,
|
||||
AstNodes::ComputedMemberExpression(expr) => &expr.object,
|
||||
AstNodes::PrivateFieldExpression(expr) => &expr.object,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
self.span == expr.span()
|
||||
match self.parent() {
|
||||
AstNodes::StaticMemberExpression(expr) => self.span == expr.object.span(),
|
||||
AstNodes::ComputedMemberExpression(expr) => self.span == expr.object.span(),
|
||||
AstNodes::PrivateFieldExpression(expr) => self.span == expr.object.span(),
|
||||
// `(f<T>)!`, since `f<T>!` does not parse
|
||||
AstNodes::TSNonNullExpression(_) => true,
|
||||
// `export default (class {}<T>)`
|
||||
AstNodes::ExportDefaultDeclaration(_) => {
|
||||
export_default_leftmost_needs_parens(self.expression())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1151,7 +1155,8 @@ fn is_first_in_statement(
|
||||
| AstNodes::ChainExpression(_)
|
||||
| AstNodes::TSAsExpression(_)
|
||||
| AstNodes::TSSatisfiesExpression(_)
|
||||
| AstNodes::TSNonNullExpression(_) => {}
|
||||
| AstNodes::TSNonNullExpression(_)
|
||||
| AstNodes::TSInstantiationExpression(_) => {}
|
||||
AstNodes::SequenceExpression(sequence) => {
|
||||
if sequence.expressions.first().unwrap().span() != current_span {
|
||||
break;
|
||||
@@ -1228,28 +1233,29 @@ fn ts_as_or_satisfies_needs_parens(
|
||||
| AstNodes::LogicalExpression(_)
|
||||
| AstNodes::BinaryExpression(_) => true,
|
||||
// `export default (function foo() {} as bar)` and `export default (class {} as bar)`.
|
||||
// Without parens, the leading `function`/`class` token makes `export default` parse the RHS as a declaration,
|
||||
// detaching the cast.
|
||||
// The leftmost expression is checked (not just `inner`) so chained casts and member chains are covered too,
|
||||
// e.g. `export default (function () {} as A as B)` and `export default (function () {}.bar as A)`.
|
||||
//
|
||||
// When the leftmost function/class already wraps itself,
|
||||
// the outer cast doesn't need its own parens, or we'd emit `((function () {})() as A)`.
|
||||
// Mirrors `Function::needs_parentheses`.
|
||||
AstNodes::ExportDefaultDeclaration(_) => {
|
||||
let leftmost = ExpressionLeftSide::leftmost(inner);
|
||||
matches!(
|
||||
leftmost.as_ref(),
|
||||
Expression::FunctionExpression(_) | Expression::ClassExpression(_)
|
||||
) && !matches!(leftmost.parent(), AstNodes::TaggedTemplateExpression(_))
|
||||
&& !leftmost.is_call_like_callee()
|
||||
}
|
||||
AstNodes::ExportDefaultDeclaration(_) => export_default_leftmost_needs_parens(inner),
|
||||
_ => {
|
||||
type_cast_like_needs_parens(span, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a wrapper directly under `export default` needs parens because its leftmost expression is a function/class.
|
||||
/// Without parens, the leading `function`/`class` token makes `export default` parse the RHS as a declaration,
|
||||
/// detaching the wrapper (e.g. a cast or type arguments).
|
||||
/// The leftmost expression is checked (not just `expr`) so chained wrappers and member chains are covered too,
|
||||
/// e.g. `export default (function () {} as A as B)` and `export default (function () {}.bar as A)`.
|
||||
///
|
||||
/// When the leftmost function/class already wraps itself,
|
||||
/// the wrapper doesn't need its own parens, or we'd emit `((function () {})() as A)`.
|
||||
/// Mirrors `Function::needs_parentheses`.
|
||||
fn export_default_leftmost_needs_parens(expr: &AstNode<'_, Expression<'_>>) -> bool {
|
||||
let leftmost = ExpressionLeftSide::leftmost(expr);
|
||||
matches!(leftmost.as_ref(), Expression::FunctionExpression(_) | Expression::ClassExpression(_))
|
||||
&& !matches!(leftmost.parent(), AstNodes::TaggedTemplateExpression(_))
|
||||
&& !leftmost.is_call_like_callee()
|
||||
}
|
||||
|
||||
fn is_class_extends(span: Span, parent: &AstNodes<'_>) -> bool {
|
||||
if let AstNodes::Class(c) = parent {
|
||||
return c.heritage().is_some_and(|heritage| heritage.expression().span() == span);
|
||||
|
||||
@@ -55,6 +55,7 @@ impl<'a, 'b> ExpressionLeftSide<'a, 'b> {
|
||||
AstNodes::TSAsExpression(expr) => Some(expr.expression().into()),
|
||||
AstNodes::TSSatisfiesExpression(expr) => Some(expr.expression().into()),
|
||||
AstNodes::TSNonNullExpression(expr) => Some(expr.expression().into()),
|
||||
AstNodes::TSInstantiationExpression(expr) => Some(expr.expression().into()),
|
||||
AstNodes::AssignmentExpression(expr) => Some(Self::AssignmentTarget(expr.left())),
|
||||
AstNodes::UpdateExpression(expr) => {
|
||||
if expr.prefix {
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Every line deviates from Prettier (DIVERGENCES.md#instantiation-expression-parens).
|
||||
const a = (make<T>)!;
|
||||
(make<T>)!.b;
|
||||
(make<T>)!();
|
||||
export default (class {})<string>;
|
||||
export default (function () {})<string>;
|
||||
export default (class {})<string> as A;
|
||||
(class {})<string>;
|
||||
(function () {})<string>;
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
---
|
||||
source: crates/oxc_formatter/tests/fixtures/mod.rs
|
||||
---
|
||||
==================== Input ====================
|
||||
// Every line deviates from Prettier (DIVERGENCES.md#instantiation-expression-parens).
|
||||
const a = (make<T>)!;
|
||||
(make<T>)!.b;
|
||||
(make<T>)!();
|
||||
export default (class {})<string>;
|
||||
export default (function () {})<string>;
|
||||
export default (class {})<string> as A;
|
||||
(class {})<string>;
|
||||
(function () {})<string>;
|
||||
|
||||
==================== Output ====================
|
||||
------------------
|
||||
{ printWidth: 80 }
|
||||
------------------
|
||||
// Every line deviates from Prettier (DIVERGENCES.md#instantiation-expression-parens).
|
||||
const a = (make<T>)!;
|
||||
(make<T>)!.b;
|
||||
(make<T>)!();
|
||||
export default (class {}<string>);
|
||||
export default (function () {}<string>);
|
||||
export default (class {}<string> as A);
|
||||
(class {})<string>;
|
||||
(function () {})<string>;
|
||||
|
||||
-------------------
|
||||
{ printWidth: 100 }
|
||||
-------------------
|
||||
// Every line deviates from Prettier (DIVERGENCES.md#instantiation-expression-parens).
|
||||
const a = (make<T>)!;
|
||||
(make<T>)!.b;
|
||||
(make<T>)!();
|
||||
export default (class {}<string>);
|
||||
export default (function () {}<string>);
|
||||
export default (class {}<string> as A);
|
||||
(class {})<string>;
|
||||
(function () {})<string>;
|
||||
|
||||
===================== End =====================
|
||||
Reference in new issue
Block a user