From 7ece5567511b25fa559082a8fd9aef8c23db66a1 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Wed, 13 Sep 2023 19:56:30 -0400 Subject: [PATCH] fix #3322: avoid temporaries before `"use strict"` --- CHANGELOG.md | 26 ++++++++++++++++++++++ internal/js_parser/js_parser.go | 18 ++++++++++++++- internal/js_parser/js_parser_lower_test.go | 12 ++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3204b608088..74ebdda90fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,32 @@ Note that this bug only affected code using the `local-css` loader. It did not affect code using the `css` loader. +* Avoid inserting temporary variables before `use strict` ([#3322](https://github.com/evanw/esbuild/issues/3322)) + + This release fixes a bug where esbuild could incorrectly insert automatically-generated temporary variables before `use strict` directives: + + ```js + // Original code + function foo() { + 'use strict' + a.b?.c() + } + + // Old output (with --target=es6) + function foo() { + var _a; + "use strict"; + (_a = a.b) == null ? void 0 : _a.c(); + } + + // New output (with --target=es6) + function foo() { + "use strict"; + var _a; + (_a = a.b) == null ? void 0 : _a.c(); + } + ``` + * Adjust TypeScript `enum` output to better approximate `tsc` ([#3329](https://github.com/evanw/esbuild/issues/3329)) TypeScript enum values can be either number literals or string literals. Numbers create a bidirectional mapping between the name and the value but strings only create a unidirectional mapping from the name to the value. When the enum value is neither a number literal nor a string literal, TypeScript and esbuild both default to treating it as a number: diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 558eede6570..3272b9a3d76 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -8377,7 +8377,23 @@ func (p *parser) visitStmtsAndPrependTempRefs(stmts []js_ast.Stmt, opts prependT } } if len(decls) > 0 { - stmts = append([]js_ast.Stmt{{Data: &js_ast.SLocal{Kind: js_ast.LocalVar, Decls: decls}}}, stmts...) + // Skip past leading directives and comments + split := 0 + for split < len(stmts) { + switch stmts[split].Data.(type) { + case *js_ast.SComment, *js_ast.SDirective: + split++ + continue + } + break + } + stmts = append( + append( + append( + []js_ast.Stmt{}, + stmts[:split]...), + js_ast.Stmt{Data: &js_ast.SLocal{Kind: js_ast.LocalVar, Decls: decls}}), + stmts[split:]...) } p.tempRefsToDeclare = oldTempRefs diff --git a/internal/js_parser/js_parser_lower_test.go b/internal/js_parser/js_parser_lower_test.go index 53978194949..d2b0c9b3336 100644 --- a/internal/js_parser/js_parser_lower_test.go +++ b/internal/js_parser/js_parser_lower_test.go @@ -99,6 +99,10 @@ func TestLowerNullishCoalescing(t *testing.T) { "function foo() {\n var _a, _b;\n if (x) {\n (_b = (_a = a()) != null ? _a : b()) != null ? _b : c();\n }\n}\n") expectPrintedTarget(t, 2019, "() => a ?? b", "() => a != null ? a : b;\n") expectPrintedTarget(t, 2019, "() => a() ?? b()", "() => {\n var _a;\n return (_a = a()) != null ? _a : b();\n};\n") + + // Temporary variables should not come before "use strict" + expectPrintedTarget(t, 2019, "function f() { /*! @license */ 'use strict'; a = b.c ?? d }", + "function f() {\n /*! @license */\n \"use strict\";\n var _a;\n a = (_a = b.c) != null ? _a : d;\n}\n") } func TestLowerNullishCoalescingAssign(t *testing.T) { @@ -152,6 +156,10 @@ class Foo { } _x = new WeakMap(); `) + + // Temporary variables should not come before "use strict" + expectPrintedTarget(t, 2019, "function f() { /*! @license */ 'use strict'; a.b ??= c.d }", + "function f() {\n /*! @license */\n \"use strict\";\n var _a;\n (_a = a.b) != null ? _a : a.b = c.d;\n}\n") } func TestLowerLogicalAssign(t *testing.T) { @@ -704,6 +712,10 @@ func TestLowerOptionalChain(t *testing.T) { expectPrintedTarget(t, 2020, "(x?.y)``", "(x?.y)``;\n") expectPrintedTarget(t, 2019, "(x?.y)``", "var _a;\n(x == null ? void 0 : x.y).call(x, _a || (_a = __template([\"\"])));\n") expectPrintedTarget(t, 5, "(x?.y)``", "var _a;\n(x == null ? void 0 : x.y).call(x, _a || (_a = __template([\"\"])));\n") + + // Temporary variables should not come before "use strict" + expectPrintedTarget(t, 2019, "function f() { /*! @license */ 'use strict'; a.b?.c() }", + "function f() {\n /*! @license */\n \"use strict\";\n var _a;\n (_a = a.b) == null ? void 0 : _a.c();\n}\n") } func TestLowerOptionalCatchBinding(t *testing.T) {