Skip to content

Commit 1877e60

Browse files
committed
calling Symbol with a primitive will never throw
1 parent 2b91699 commit 1877e60

File tree

5 files changed

+120
-48
lines changed

5 files changed

+120
-48
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
* Allow tree-shaking of the `Symbol` constructor
6+
7+
With this release, calling `Symbol` is now considered to be side-effect free when the argument is known to be a primitive value. This means esbuild can now tree-shake module-level symbol variables:
8+
9+
```js
10+
// Original code
11+
const a = Symbol('foo')
12+
const b = Symbol(bar)
13+
14+
// Old output (with --tree-shaking=true)
15+
const a = Symbol("foo");
16+
const b = Symbol(bar);
17+
18+
// New output (with --tree-shaking=true)
19+
const b = Symbol(bar);
20+
```
21+
322
## 0.27.0
423

524
**This release deliberately contains backwards-incompatible changes.** To avoid automatically picking up releases like this, you should either be pinning the exact version of `esbuild` in your `package.json` file (recommended) or be using a version range syntax that only accepts patch upgrades such as `^0.26.0` or `~0.26.0`. See npm's documentation about [semver](https://docs.npmjs.com/cli/v6/using-npm/semver/) for more information.

internal/bundler_tests/bundler_dce_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4947,3 +4947,33 @@ func TestDCEOfIteratorSuperclassIssue4310(t *testing.T) {
49474947
},
49484948
})
49494949
}
4950+
4951+
func TestDCEOfSymbolCtorCall(t *testing.T) {
4952+
dce_suite.expectBundled(t, bundled{
4953+
files: map[string]string{
4954+
"/entry.js": `
4955+
const y0 = Symbol()
4956+
const y1 = Symbol(undefined)
4957+
const y2 = Symbol(null)
4958+
const y3 = Symbol(true)
4959+
const y4 = Symbol(123)
4960+
const y5 = Symbol(123n)
4961+
const y6 = Symbol('abc')
4962+
const y7 = Symbol(/* @__PURE__ */ (() => Math.random() < 0.5)() ? 'x' : 'y')
4963+
4964+
const n0 = Symbol({})
4965+
const n1 = Symbol(/./)
4966+
const n2 = Symbol(() => 0)
4967+
const n3 = Symbol(x)
4968+
const n4 = new Symbol('abc')
4969+
const n5 = Symbol(1, 2, 3)
4970+
const n6 = Symbol((() => Math.random() < 0.5)() ? 'x' : 'y')
4971+
`,
4972+
},
4973+
entryPaths: []string{"/entry.js"},
4974+
options: config.Options{
4975+
Mode: config.ModeBundle,
4976+
AbsOutputFile: "/out.js",
4977+
},
4978+
})
4979+
}

internal/bundler_tests/snapshots/snapshots_dce.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,18 @@ var Keep = class extends NotIterator {
663663
TestDCEOfNegatedBigints
664664
---------- /out/entry.js ----------
665665

666+
================================================================================
667+
TestDCEOfSymbolCtorCall
668+
---------- /out.js ----------
669+
// entry.js
670+
var n0 = Symbol({});
671+
var n1 = Symbol(/./);
672+
var n2 = Symbol(() => 0);
673+
var n3 = Symbol(x);
674+
var n4 = new Symbol("abc");
675+
var n5 = Symbol(1, 2, 3);
676+
var n6 = /* @__PURE__ */ Symbol((() => Math.random() < 0.5)() ? "x" : "y");
677+
666678
================================================================================
667679
TestDCEOfSymbolInstances
668680
---------- /out/class.js ----------

internal/bundler_tests/snapshots/snapshots_ts.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ console.log(a_exports, b_exports, c_exports, d_exports);
118118
================================================================================
119119
TestTSAbstractClassFieldUseAssign
120120
---------- /out.js ----------
121-
const keepThis = Symbol("keepThis");
121+
const keepThis = /* @__PURE__ */ Symbol("keepThis");
122122
keepThis;
123123
class Foo {
124124
}
@@ -127,7 +127,7 @@ class Foo {
127127
================================================================================
128128
TestTSAbstractClassFieldUseDefine
129129
---------- /out.js ----------
130-
const keepThisToo = Symbol("keepThisToo");
130+
const keepThisToo = /* @__PURE__ */ Symbol("keepThisToo");
131131
class Foo {
132132
keepThis;
133133
[keepThisToo];

internal/js_parser/js_parser.go

Lines changed: 57 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15135,63 +15135,74 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
1513515135
}
1513615136
}
1513715137

15138-
// Optimize references to global constructors
15139-
if p.options.minifySyntax && t.CanBeRemovedIfUnused && len(e.Args) <= 1 && !hasSpread {
15138+
// Handle certain special cases
15139+
if len(e.Args) <= 1 && !hasSpread {
1514015140
if symbol := &p.symbols[t.Ref.InnerIndex]; symbol.Kind == ast.SymbolUnbound {
15141-
// Note: We construct expressions by assigning to "expr.Data" so
15142-
// that the source map position for the constructor is preserved
1514315141
switch symbol.OriginalName {
15144-
case "Boolean":
15145-
if len(e.Args) == 0 {
15146-
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EBoolean{Value: false}}, exprOut{}
15147-
} else {
15148-
expr.Data = &js_ast.EUnary{Value: p.astHelpers.SimplifyBooleanExpr(e.Args[0]), Op: js_ast.UnOpNot}
15149-
return js_ast.Not(expr), exprOut{}
15142+
case "Symbol":
15143+
// Calling the "Symbol()" constructor with a primitive will never throw
15144+
if len(e.Args) == 0 || js_ast.KnownPrimitiveType(e.Args[0].Data) != js_ast.PrimitiveUnknown {
15145+
e.CanBeUnwrappedIfUnused = true
1515015146
}
15147+
}
1515115148

15152-
case "Number":
15153-
if len(e.Args) == 0 {
15154-
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: 0}}, exprOut{}
15155-
} else {
15156-
arg := e.Args[0]
15157-
15158-
switch js_ast.KnownPrimitiveType(arg.Data) {
15159-
case js_ast.PrimitiveNumber:
15160-
return arg, exprOut{}
15161-
15162-
case
15163-
js_ast.PrimitiveUndefined, // NaN
15164-
js_ast.PrimitiveNull, // 0
15165-
js_ast.PrimitiveBoolean, // 0 or 1
15166-
js_ast.PrimitiveString: // StringToNumber
15167-
if number, ok := js_ast.ToNumberWithoutSideEffects(arg.Data); ok {
15168-
expr.Data = &js_ast.ENumber{Value: number}
15169-
} else {
15170-
expr.Data = &js_ast.EUnary{Value: arg, Op: js_ast.UnOpPos}
15149+
// Optimize references to global constructors
15150+
if p.options.minifySyntax && t.CanBeRemovedIfUnused {
15151+
// Note: We construct expressions by assigning to "expr.Data" so
15152+
// that the source map position for the constructor is preserved
15153+
switch symbol.OriginalName {
15154+
case "Boolean":
15155+
if len(e.Args) == 0 {
15156+
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EBoolean{Value: false}}, exprOut{}
15157+
} else {
15158+
expr.Data = &js_ast.EUnary{Value: p.astHelpers.SimplifyBooleanExpr(e.Args[0]), Op: js_ast.UnOpNot}
15159+
return js_ast.Not(expr), exprOut{}
15160+
}
15161+
15162+
case "Number":
15163+
if len(e.Args) == 0 {
15164+
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: 0}}, exprOut{}
15165+
} else {
15166+
arg := e.Args[0]
15167+
15168+
switch js_ast.KnownPrimitiveType(arg.Data) {
15169+
case js_ast.PrimitiveNumber:
15170+
return arg, exprOut{}
15171+
15172+
case
15173+
js_ast.PrimitiveUndefined, // NaN
15174+
js_ast.PrimitiveNull, // 0
15175+
js_ast.PrimitiveBoolean, // 0 or 1
15176+
js_ast.PrimitiveString: // StringToNumber
15177+
if number, ok := js_ast.ToNumberWithoutSideEffects(arg.Data); ok {
15178+
expr.Data = &js_ast.ENumber{Value: number}
15179+
} else {
15180+
expr.Data = &js_ast.EUnary{Value: arg, Op: js_ast.UnOpPos}
15181+
}
15182+
return expr, exprOut{}
1517115183
}
15172-
return expr, exprOut{}
1517315184
}
15174-
}
1517515185

15176-
case "String":
15177-
if len(e.Args) == 0 {
15178-
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EString{Value: nil}}, exprOut{}
15179-
} else {
15180-
arg := e.Args[0]
15186+
case "String":
15187+
if len(e.Args) == 0 {
15188+
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EString{Value: nil}}, exprOut{}
15189+
} else {
15190+
arg := e.Args[0]
1518115191

15182-
switch js_ast.KnownPrimitiveType(arg.Data) {
15183-
case js_ast.PrimitiveString:
15184-
return arg, exprOut{}
15192+
switch js_ast.KnownPrimitiveType(arg.Data) {
15193+
case js_ast.PrimitiveString:
15194+
return arg, exprOut{}
15195+
}
1518515196
}
15186-
}
1518715197

15188-
case "BigInt":
15189-
if len(e.Args) == 1 {
15190-
arg := e.Args[0]
15198+
case "BigInt":
15199+
if len(e.Args) == 1 {
15200+
arg := e.Args[0]
1519115201

15192-
switch js_ast.KnownPrimitiveType(arg.Data) {
15193-
case js_ast.PrimitiveBigInt:
15194-
return arg, exprOut{}
15202+
switch js_ast.KnownPrimitiveType(arg.Data) {
15203+
case js_ast.PrimitiveBigInt:
15204+
return arg, exprOut{}
15205+
}
1519515206
}
1519615207
}
1519715208
}

0 commit comments

Comments
 (0)