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
5 changes: 3 additions & 2 deletions tsc/internal/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2593,8 +2593,9 @@ func (p *Printer) emitNewExpression(node *ast.NewExpression) {
state := p.enterNode(node.AsNode())
p.emitToken(ast.KindNewKeyword, node.Pos(), WriteKindKeyword, node.AsNode())
p.writeSpace()
if ast.SkipPartiallyEmittedExpressions(node.Expression).Kind == ast.KindCallExpression {
// Parenthesize `C()` inside of a NewExpression so it is treated as `new (C())` and not `new C()`
if hasUnparenthesizedCallAtStartOfMemberChain(node.Expression) {
// Parenthesize `C()` or `C().D` inside of a NewExpression so it is treated as `new (C())` or `new (C().D)`
// and not `new C()` or `new C().D`
p.emitExpression(node.Expression, ast.OperatorPrecedenceParentheses)
} else {
p.emitExpression(node.Expression, ast.OperatorPrecedenceMember)
Expand Down
24 changes: 24 additions & 0 deletions tsc/internal/printer/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,30 @@ func isNewExpressionWithoutArguments(node *ast.Node) bool {
return node.Kind == ast.KindNewExpression && node.ArgumentList() == nil
}

// Determines whether the expression of a NewExpression would be emitted with an unparenthesized CallExpression at the
// start of its member chain, such as `C()` in `C().D`. Such an expression must be parenthesized when used as the
// expression of a NewExpression, since `new C().D()` would be parsed as `(new C()).D()` rather than `new (C().D)()`.
func hasUnparenthesizedCallAtStartOfMemberChain(node *ast.Expression) bool {
for {
node = ast.SkipPartiallyEmittedExpressions(node)
if ast.GetExpressionPrecedence(node) < ast.OperatorPrecedenceMember {
// Each expression in the member chain is emitted with member precedence, so this expression will already
// be parenthesized, which ends the member chain.
return false
}
switch node.Kind {
case ast.KindCallExpression:
return true
case ast.KindPropertyAccessExpression, ast.KindElementAccessExpression, ast.KindExpressionWithTypeArguments:
node = node.Expression()
case ast.KindTaggedTemplateExpression:
node = node.AsTaggedTemplateExpression().Tag
default:
return false
}
}
}

func isBinaryOperation(node *ast.Node, token ast.Kind) bool {
node = ast.SkipPartiallyEmittedExpressions(node)
return node.Kind == ast.KindBinaryExpression &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//// [tests/cases/compiler/newExpressionCalleeParentheses.ts] ////

//// [newExpressionCalleeParentheses.ts]
declare function getPlugins(): any;
declare function getPluginsGeneric<T>(): T;
declare function getPluginsFactory(): <T>() => T;
declare const obj: any;
declare class Foo { [key: string]: any }

// Parentheses are required when a call is at the start of the member chain of a `new` expression's target
const a1 = new (getPlugins() as any).Linker();
const a2 = new (getPlugins() satisfies any).Linker();
const a3 = new (<any>getPlugins()).Linker();
const a4 = new (getPlugins() as any)!.Linker();
const a5 = new (getPlugins() as any)["Linker"]();
const a6 = new (getPlugins() as any).ns.Linker();
const a7 = new (getPlugins() as any).Linker;
const a8 = new (getPlugins() as any)`tag`();
const a9 = new (obj.getPlugins() as any).Linker();
const a10 = new (getPlugins().ns as any).Linker();
const a11 = new ((getPlugins() as any) as any).Linker();
const a12 = new (getPluginsGeneric<any>() as any).Linker();
const a13 = new (getPluginsFactory()<string> as any).Linker();
const a14 = new (getPlugins() as any).Linker(new (getPlugins() as any).Options());

// Parentheses are not required, or are already present
const b1 = new (getPlugins() as any)();
const b2 = new (getPlugins()).Linker();
const b3 = new (new Foo() as any).Linker();
const b4 = new (new Foo as any).Linker();
const b5 = new (obj?.getPlugins() as any).Linker();
const b6 = new (obj[getPlugins()] as any).Linker();
const b7 = new (obj.ns as any).Linker();


//// [newExpressionCalleeParentheses.js]
"use strict";
// Parentheses are required when a call is at the start of the member chain of a `new` expression's target
const a1 = new (getPlugins().Linker)();
const a2 = new (getPlugins().Linker)();
const a3 = new (getPlugins().Linker)();
const a4 = new (getPlugins().Linker)();
const a5 = new (getPlugins()["Linker"])();
const a6 = new (getPlugins().ns.Linker)();
const a7 = new (getPlugins().Linker);
const a8 = new (getPlugins() `tag`)();
const a9 = new (obj.getPlugins().Linker)();
const a10 = new (getPlugins().ns.Linker)();
const a11 = new (getPlugins().Linker)();
const a12 = new (getPluginsGeneric().Linker)();
const a13 = new (getPluginsFactory().Linker)();
const a14 = new (getPlugins().Linker)(new (getPlugins().Options)());
// Parentheses are not required, or are already present
const b1 = new (getPlugins())();
const b2 = new (getPlugins()).Linker();
const b3 = new new Foo().Linker();
const b4 = new (new Foo).Linker();
const b5 = new (obj?.getPlugins()).Linker();
const b6 = new obj[getPlugins()].Linker();
const b7 = new obj.ns.Linker();
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//// [tests/cases/compiler/newExpressionCalleeParentheses.ts] ////

=== newExpressionCalleeParentheses.ts ===
declare function getPlugins(): any;
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

declare function getPluginsGeneric<T>(): T;
>getPluginsGeneric : Symbol(getPluginsGeneric, Decl(newExpressionCalleeParentheses.ts, 0, 35))
>T : Symbol(T, Decl(newExpressionCalleeParentheses.ts, 1, 35))
>T : Symbol(T, Decl(newExpressionCalleeParentheses.ts, 1, 35))

declare function getPluginsFactory(): <T>() => T;
>getPluginsFactory : Symbol(getPluginsFactory, Decl(newExpressionCalleeParentheses.ts, 1, 43))
>T : Symbol(T, Decl(newExpressionCalleeParentheses.ts, 2, 39))
>T : Symbol(T, Decl(newExpressionCalleeParentheses.ts, 2, 39))

declare const obj: any;
>obj : Symbol(obj, Decl(newExpressionCalleeParentheses.ts, 3, 13))

declare class Foo { [key: string]: any }
>Foo : Symbol(Foo, Decl(newExpressionCalleeParentheses.ts, 3, 23))
>key : Symbol(key, Decl(newExpressionCalleeParentheses.ts, 4, 21))

// Parentheses are required when a call is at the start of the member chain of a `new` expression's target
const a1 = new (getPlugins() as any).Linker();
>a1 : Symbol(a1, Decl(newExpressionCalleeParentheses.ts, 7, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const a2 = new (getPlugins() satisfies any).Linker();
>a2 : Symbol(a2, Decl(newExpressionCalleeParentheses.ts, 8, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const a3 = new (<any>getPlugins()).Linker();
>a3 : Symbol(a3, Decl(newExpressionCalleeParentheses.ts, 9, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const a4 = new (getPlugins() as any)!.Linker();
>a4 : Symbol(a4, Decl(newExpressionCalleeParentheses.ts, 10, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const a5 = new (getPlugins() as any)["Linker"]();
>a5 : Symbol(a5, Decl(newExpressionCalleeParentheses.ts, 11, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const a6 = new (getPlugins() as any).ns.Linker();
>a6 : Symbol(a6, Decl(newExpressionCalleeParentheses.ts, 12, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const a7 = new (getPlugins() as any).Linker;
>a7 : Symbol(a7, Decl(newExpressionCalleeParentheses.ts, 13, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const a8 = new (getPlugins() as any)`tag`();
>a8 : Symbol(a8, Decl(newExpressionCalleeParentheses.ts, 14, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const a9 = new (obj.getPlugins() as any).Linker();
>a9 : Symbol(a9, Decl(newExpressionCalleeParentheses.ts, 15, 5))
>obj : Symbol(obj, Decl(newExpressionCalleeParentheses.ts, 3, 13))

const a10 = new (getPlugins().ns as any).Linker();
>a10 : Symbol(a10, Decl(newExpressionCalleeParentheses.ts, 16, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const a11 = new ((getPlugins() as any) as any).Linker();
>a11 : Symbol(a11, Decl(newExpressionCalleeParentheses.ts, 17, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const a12 = new (getPluginsGeneric<any>() as any).Linker();
>a12 : Symbol(a12, Decl(newExpressionCalleeParentheses.ts, 18, 5))
>getPluginsGeneric : Symbol(getPluginsGeneric, Decl(newExpressionCalleeParentheses.ts, 0, 35))

const a13 = new (getPluginsFactory()<string> as any).Linker();
>a13 : Symbol(a13, Decl(newExpressionCalleeParentheses.ts, 19, 5))
>getPluginsFactory : Symbol(getPluginsFactory, Decl(newExpressionCalleeParentheses.ts, 1, 43))

const a14 = new (getPlugins() as any).Linker(new (getPlugins() as any).Options());
>a14 : Symbol(a14, Decl(newExpressionCalleeParentheses.ts, 20, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

// Parentheses are not required, or are already present
const b1 = new (getPlugins() as any)();
>b1 : Symbol(b1, Decl(newExpressionCalleeParentheses.ts, 23, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const b2 = new (getPlugins()).Linker();
>b2 : Symbol(b2, Decl(newExpressionCalleeParentheses.ts, 24, 5))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const b3 = new (new Foo() as any).Linker();
>b3 : Symbol(b3, Decl(newExpressionCalleeParentheses.ts, 25, 5))
>Foo : Symbol(Foo, Decl(newExpressionCalleeParentheses.ts, 3, 23))

const b4 = new (new Foo as any).Linker();
>b4 : Symbol(b4, Decl(newExpressionCalleeParentheses.ts, 26, 5))
>Foo : Symbol(Foo, Decl(newExpressionCalleeParentheses.ts, 3, 23))

const b5 = new (obj?.getPlugins() as any).Linker();
>b5 : Symbol(b5, Decl(newExpressionCalleeParentheses.ts, 27, 5))
>obj : Symbol(obj, Decl(newExpressionCalleeParentheses.ts, 3, 13))

const b6 = new (obj[getPlugins()] as any).Linker();
>b6 : Symbol(b6, Decl(newExpressionCalleeParentheses.ts, 28, 5))
>obj : Symbol(obj, Decl(newExpressionCalleeParentheses.ts, 3, 13))
>getPlugins : Symbol(getPlugins, Decl(newExpressionCalleeParentheses.ts, 0, 0))

const b7 = new (obj.ns as any).Linker();
>b7 : Symbol(b7, Decl(newExpressionCalleeParentheses.ts, 29, 5))
>obj : Symbol(obj, Decl(newExpressionCalleeParentheses.ts, 3, 13))

Loading
Loading