Skip to content

Commit

Permalink
feat: drop catch binding when optional catch binding is supported (#1660
Browse files Browse the repository at this point in the history
)
  • Loading branch information
sapphi-red authored Oct 13, 2021
1 parent 11d7eac commit d74ad57
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 0 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@

This change was contributed by [@heypiotr](https://github.com/heypiotr).

* Remove unused `catch` bindings when minifying ([#1660](https://github.com/evanw/esbuild/pull/1660))

With this release, esbuild will now remove unused `catch` bindings when minifying:

```js
// Original code
try {
throw 0;
} catch (e) {
}

// Old output (with --minify)
try{throw 0}catch(t){}

// New output (with --minify)
try{throw 0}catch{}
```

This takes advantage of the new [optional catch binding](https://github.com/tc39/proposal-optional-catch-binding) syntax feature that was introduced in ES2019. This minification rule is only enabled when optional catch bindings are supported by the target environment. Specifically, it's not enabled when using `--target=es2018` or older. Make sure to set esbuild's `target` setting correctly when minifying if the code will be running in an older JavaScript environment.

This change was contributed by [@sapphi-red](https://github.com/sapphi-red).

## 0.13.5

* Improve watch mode accuracy ([#1113](https://github.com/evanw/esbuild/issues/1113))
Expand Down
29 changes: 29 additions & 0 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -7552,6 +7552,35 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt
}
}
}

case *js_ast.STry:
// Drop an unused identifier binding if the optional catch binding feature is supported
if !p.options.unsupportedJSFeatures.Has(compat.OptionalCatchBinding) && s.Catch != nil {
if id, ok := s.Catch.BindingOrNil.Data.(*js_ast.BIdentifier); ok {
if symbol := p.symbols[id.Ref.InnerIndex]; symbol.UseCountEstimate == 0 {
if symbol.Link != js_ast.InvalidRef {
// We cannot transform "try { x() } catch (y) { var y = 1 }" into
// "try { x() } catch { var y = 1 }" even though "y" is never used
// because the hoisted variable "y" would have different values
// after the statement ends due to a strange JavaScript quirk:
//
// try { x() } catch (y) { var y = 1 }
// console.log(y) // undefined
//
// try { x() } catch { var y = 1 }
// console.log(y) // 1
//
} else if p.currentScope.ContainsDirectEval {
// We cannot transform "try { x() } catch (y) { eval('z = y') }"
// into "try { x() } catch { eval('z = y') }" because the variable
// "y" is actually still used.
} else {
// "try { x() } catch (y) {}" => "try { x() } catch {}"
s.Catch.BindingOrNil.Data = nil
}
}
}
}
}

result = append(result, stmt)
Expand Down
15 changes: 15 additions & 0 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4807,3 +4807,18 @@ func TestASCIIOnly(t *testing.T) {
expectPrintedTargetASCII(t, 5, "export var π", "export var \\u03C0;\n")
expectParseErrorTargetASCII(t, 5, "export var 𐀀", es5)
}

func TestMangleCatch(t *testing.T) {
expectPrintedMangle(t, "try { throw 0 } catch (e) { console.log(0) }", "try {\n throw 0;\n} catch {\n console.log(0);\n}\n")
expectPrintedMangle(t, "try { throw 0 } catch (e) { console.log(0, e) }", "try {\n throw 0;\n} catch (e) {\n console.log(0, e);\n}\n")
expectPrintedMangle(t, "try { throw 0 } catch (e) { 0 && console.log(0, e) }", "try {\n throw 0;\n} catch {\n}\n")
expectPrintedMangle(t, "try { thrower() } catch ([a]) { console.log(0) }", "try {\n thrower();\n} catch ([a]) {\n console.log(0);\n}\n")
expectPrintedMangle(t, "try { thrower() } catch ({ a }) { console.log(0) }", "try {\n thrower();\n} catch ({ a }) {\n console.log(0);\n}\n")
expectPrintedMangleTarget(t, 2018, "try { throw 0 } catch (e) { console.log(0) }", "try {\n throw 0;\n} catch (e) {\n console.log(0);\n}\n")

expectPrintedMangle(t, "try { throw 1 } catch (x) { y(x); var x = 2; y(x) }", "try {\n throw 1;\n} catch (x) {\n y(x);\n var x = 2;\n y(x);\n}\n")
expectPrintedMangle(t, "try { throw 1 } catch (x) { var x = 2; y(x) }", "try {\n throw 1;\n} catch (x) {\n var x = 2;\n y(x);\n}\n")
expectPrintedMangle(t, "try { throw 1 } catch (x) { var x = 2 }", "try {\n throw 1;\n} catch (x) {\n var x = 2;\n}\n")
expectPrintedMangle(t, "try { throw 1 } catch (x) { eval('x') }", "try {\n throw 1;\n} catch (x) {\n eval(\"x\");\n}\n")
expectPrintedMangle(t, "if (y) try { throw 1 } catch (x) {} else eval('x')", "if (y)\n try {\n throw 1;\n } catch {\n }\nelse\n eval(\"x\");\n")
}
57 changes: 57 additions & 0 deletions scripts/end-to-end-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -2225,6 +2225,63 @@
}),
)
}

// Check try/catch simplification
tests.push(
test(['in.js', '--outfile=node.js'].concat(minify), {
'in.js': `
try {
try {
throw 0
} finally {
var x = 1
}
} catch {
}
if (x !== 1) throw 'fail'
`,
}),
test(['in.js', '--outfile=node.js'].concat(minify), {
'in.js': `
let y
try {
throw 1
} catch (x) {
eval('y = x')
}
if (y !== 1) throw 'fail'
`,
}),
test(['in.js', '--outfile=node.js'].concat(minify), {
'in.js': `
try {
throw 0
} catch (x) {
var x = 1
}
if (x !== void 0) throw 'fail'
`,
}),
test(['in.js', '--outfile=node.js'].concat(minify), {
'in.js': `
let works
try {
throw { get a() { works = true } }
} catch ({ a }) {}
if (!works) throw 'fail'
`,
}),
test(['in.js', '--outfile=node.js'].concat(minify), {
'in.js': `
let works
try {
throw { *[Symbol.iterator]() { works = true } }
} catch ([x]) {
}
if (!works) throw 'fail'
`,
}),
)
}

// Test minification of top-level symbols
Expand Down

0 comments on commit d74ad57

Please sign in to comment.