Skip to content

Commit add452e

Browse files
committed
fix #4359: prune empty case before a default
1 parent 14fd59c commit add452e

File tree

4 files changed

+127
-1
lines changed

4 files changed

+127
-1
lines changed

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@
22

33
## Unreleased
44

5+
* More minifications for `switch` statements ([#4359](https://github.com/evanw/esbuild/issues/4359))
6+
7+
This release contains additional minification patterns for reducing `switch` statements. Here is an example:
8+
9+
```js
10+
// Original code
11+
switch (x) {
12+
case 0:
13+
foo()
14+
break
15+
case 1:
16+
default:
17+
bar()
18+
}
19+
20+
// Old output (with --minify)
21+
switch(x){case 0:foo();break;case 1:default:bar()}
22+
23+
// New output (with --minify)
24+
switch(x){case 0:foo();break;default:bar()}
25+
```
26+
527
* Forbid `using` declarations inside `switch` clauses ([#4323](https://github.com/evanw/esbuild/issues/4323))
628

729
This is a rare change to remove something that was previously possible. The [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) proposal introduced `using` declarations. These were previously allowed inside `case` and `default` clauses in `switch` statements. This had well-defined semantics and was already widely implemented (by V8, SpiderMonkey, TypeScript, esbuild, and others). However, it was considered to be too confusing because of how scope works in switch statements, so it has been removed from the specification. This edge case will now be a syntax error. See [tc39/proposal-explicit-resource-management#215](https://github.com/tc39/proposal-explicit-resource-management/issues/215) and [rbuckton/ecma262#14](https://github.com/rbuckton/ecma262/pull/14) for details.

internal/js_parser/js_parser.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11232,8 +11232,19 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_
1123211232
return stmts
1123311233
}
1123411234

11235-
// Attempt to remove statically-determined switch statements
1123611235
if p.options.minifySyntax {
11236+
// Trim empty cases before a trailing default clause
11237+
if len(s.Cases) > 0 {
11238+
if i := len(s.Cases) - 1; s.Cases[i].ValueOrNil.Data == nil {
11239+
// "switch (x) { case 0: default: y() }" => "switch (x) { default: y() }"
11240+
for i > 0 && len(s.Cases[i-1].Body) == 0 && js_ast.IsPrimitiveLiteral(s.Cases[i-1].ValueOrNil.Data) {
11241+
i--
11242+
}
11243+
s.Cases = append(s.Cases[:i], s.Cases[len(s.Cases)-1])
11244+
}
11245+
}
11246+
11247+
// Attempt to remove statically-determined switch statements
1123711248
if len(s.Cases) == 0 {
1123811249
if p.astHelpers.ExprCanBeRemovedIfUnused(s.Test) {
1123911250
// Remove everything

internal/js_parser/js_parser_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3666,6 +3666,10 @@ func TestMangleBlock(t *testing.T) {
36663666
func TestMangleSwitch(t *testing.T) {
36673667
expectPrintedMangle(t, "x(); switch (y) { case z: return w; }", "switch (x(), y) {\n case z:\n return w;\n}\n")
36683668
expectPrintedMangle(t, "if (t) { x(); switch (y) { case z: return w; } }", "if (t)\n switch (x(), y) {\n case z:\n return w;\n }\n")
3669+
3670+
expectPrintedMangle(t, "switch (x) { case p: a(); break; case q: default: b() }", "switch (x) {\n case p:\n a();\n break;\n case q:\n default:\n b();\n}\n")
3671+
expectPrintedMangle(t, "switch (x) { case 0: a(); break; case 1: case 2: default: b() }", "switch (x) {\n case 0:\n a();\n break;\n default:\n b();\n}\n")
3672+
expectPrintedMangle(t, "switch (x) { case 0: default: a(); break; case 0: b() }", "switch (x) {\n case 0:\n default:\n a();\n break;\n case 0:\n b();\n}\n")
36693673
}
36703674

36713675
func TestMangleAddEmptyString(t *testing.T) {

scripts/end-to-end-tests.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3406,6 +3406,95 @@ for (const minify of [[], ['--minify-syntax']]) {
34063406
var x = class { static [-0xFFFF_FFFF_FFFF] = 1 }; if (x['-281474976710655'] !== 1) throw 'fail: -0xFFFF_FFFF_FFFF'
34073407
`,
34083408
}),
3409+
test(['in.js', '--outfile=node.js'].concat(minify), {
3410+
'in.js': `
3411+
function test(x) {
3412+
let log = ''
3413+
switch (x) {
3414+
case 0:
3415+
log += '0'
3416+
case 1:
3417+
default:
3418+
log += '1'
3419+
}
3420+
return log
3421+
}
3422+
if (test(0) !== '01') throw 'fail: 0'
3423+
if (test(1) !== '1') throw 'fail: 1'
3424+
if (test(2) !== '1') throw 'fail: 2'
3425+
`,
3426+
}),
3427+
test(['in.js', '--outfile=node.js'].concat(minify), {
3428+
'in.js': `
3429+
function test(x) {
3430+
let log = ''
3431+
switch (x) {
3432+
case 0:
3433+
log += '0'
3434+
break
3435+
case 1:
3436+
default:
3437+
log += '1'
3438+
}
3439+
return log
3440+
}
3441+
if (test(0) !== '0') throw 'fail: 0'
3442+
if (test(1) !== '1') throw 'fail: 1'
3443+
if (test(2) !== '1') throw 'fail: 2'
3444+
`,
3445+
}),
3446+
test(['in.js', '--outfile=node.js'].concat(minify), {
3447+
'in.js': `
3448+
function test(x) {
3449+
let log = ''
3450+
switch (x) {
3451+
case 0:
3452+
log += '0'
3453+
case 1:
3454+
default:
3455+
log += '1'
3456+
case 2:
3457+
log += '2'
3458+
}
3459+
return log
3460+
}
3461+
if (test(0) !== '012') throw 'fail: 0'
3462+
if (test(1) !== '12') throw 'fail: 1'
3463+
if (test(2) !== '2') throw 'fail: 2'
3464+
if (test(3) !== '12') throw 'fail: 3'
3465+
`,
3466+
}),
3467+
test(['in.js', '--outfile=node.js'].concat(minify), {
3468+
'in.js': `
3469+
function test(x) {
3470+
let log = ''
3471+
switch (x) {
3472+
case 0: // Note: This case must not be removed
3473+
default:
3474+
log += '1'
3475+
case 0:
3476+
log += '2'
3477+
}
3478+
return log
3479+
}
3480+
if (test(0) !== '12') throw 'fail: 0'
3481+
if (test(1) !== '12') throw 'fail: 1'
3482+
`,
3483+
}, {
3484+
expectedStderr: `▲ [WARNING] This case clause will never be evaluated because it duplicates an earlier case clause [duplicate-case]
3485+
3486+
in.js:8:12:
3487+
8 │ case 0:
3488+
╵ ~~~~
3489+
3490+
The earlier case clause is here:
3491+
3492+
in.js:5:12:
3493+
5 │ case 0: // Note: This case must not be removed
3494+
╵ ~~~~
3495+
3496+
`,
3497+
}),
34093498

34103499
// See: https://github.com/evanw/esbuild/issues/3195
34113500
test(['in.js', '--outfile=node.js', '--keep-names'].concat(minify), {

0 commit comments

Comments
 (0)