diff --git a/internal/js_ast/js_ast.go b/internal/js_ast/js_ast.go index 9c3eed3855e..fcdf42c7a29 100644 --- a/internal/js_ast/js_ast.go +++ b/internal/js_ast/js_ast.go @@ -1482,10 +1482,11 @@ type Scope struct { // This will be non-nil if this is a TypeScript "namespace" or "enum" TSNamespace *TSNamespaceScope - Parent *Scope - Children []*Scope - Members map[string]ScopeMember - Generated []Ref + Parent *Scope + Children []*Scope + Members map[string]ScopeMember + HoistFnRef map[string]*Ref + Generated []Ref // The location of the "use strict" directive for ExplicitStrictMode UseStrictLoc logger.Loc diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 6e94b5c14fb..e96b9a7c7b8 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -910,10 +910,11 @@ func (p *parser) selectLocalKind(kind js_ast.LocalKind) js_ast.LocalKind { func (p *parser) pushScopeForParsePass(kind js_ast.ScopeKind, loc logger.Loc) int { parent := p.currentScope scope := &js_ast.Scope{ - Kind: kind, - Parent: parent, - Members: make(map[string]js_ast.ScopeMember), - Label: js_ast.LocRef{Ref: js_ast.InvalidRef}, + Kind: kind, + Parent: parent, + Members: make(map[string]js_ast.ScopeMember), + HoistFnRef: make(map[string]*js_ast.Ref), + Label: js_ast.LocRef{Ref: js_ast.InvalidRef}, } if parent != nil { parent.Children = append(parent.Children, scope) @@ -1107,6 +1108,7 @@ const ( mergeForbidden = iota mergeReplaceWithNew mergeOverwriteWithNew + mergeHoist mergeKeepExisting mergeBecomePrivateGetSetPair mergeBecomePrivateStaticGetSetPair @@ -1147,14 +1149,12 @@ func (p *parser) canMergeSymbols(scope *js_ast.Scope, existing js_ast.SymbolKind } } - // "var foo; var foo;" - // "var foo; function foo() {}" - // "function foo() {} var foo;" + // only top level function can be overwritten // "function *foo() {} function *foo() {}" but not "{ function *foo() {} function *foo() {} }" if new.IsHoistedOrFunction() && existing.IsHoistedOrFunction() && (scope.Kind == js_ast.ScopeEntry || scope.Kind == js_ast.ScopeFunctionBody || (new.IsHoisted() && existing.IsHoisted())) { - return mergeReplaceWithNew + return mergeHoist } // "get #foo() {} set #foo() {}" @@ -1219,11 +1219,41 @@ func (p *parser) declareSymbol(kind js_ast.SymbolKind, loc logger.Loc, name stri case mergeReplaceWithNew: symbol.Link = ref - // If these are both functions, remove the overwritten declaration - if p.options.minifySyntax && kind.IsFunction() && symbol.Kind.IsFunction() { - symbol.Flags |= js_ast.RemoveOverwrittenFunctionDeclaration + case mergeHoist: + if !kind.IsFunction() && !symbol.Kind.IsFunction() { + ref = existing.Ref + break + } + + fnRef := p.currentScope.HoistFnRef[name] + if fnRef == nil && symbol.Kind.IsFunction() { + p.currentScope.HoistFnRef[name] = &existing.Ref + fnRef = &existing.Ref + } + + if p.options.minifySyntax && fnRef != nil && kind.IsFunction() { + fnSymbol := &p.symbols[fnRef.InnerIndex] + fnSymbol.Flags |= js_ast.RemoveOverwrittenFunctionDeclaration } + if kind.IsFunction() && symbol.Kind.IsFunction() { + symbol.Link = ref + p.currentScope.HoistFnRef[name] = &ref + break + } + + if kind.IsFunction() { + // The past are variables, we encountered the first function + p.currentScope.HoistFnRef[name] = &ref + p.symbols[existing.Ref.InnerIndex].Link = ref + + // do not update currentScope.Members + return ref + } + + // The past are functions, we encountered the first variable + p.symbols[existing.Ref.InnerIndex].Link = ref + case mergeBecomePrivateGetSetPair: ref = existing.Ref symbol.Kind = js_ast.SymbolPrivateGetSetPair diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index edb4b35166e..2d4501430d7 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -1357,8 +1357,10 @@ func TestFunction(t *testing.T) { expectPrintedMangle(t, "function f() {} var f", "function f() {\n}\nvar f;\n") expectPrintedMangle(t, "var f; function f() { x() } function f() { y() }", "var f;\nfunction f() {\n y();\n}\n") expectPrintedMangle(t, "function f() { x() } function f() { y() } var f", "function f() {\n y();\n}\nvar f;\n") - expectPrintedMangle(t, "function f() { x() } var f; function f() { y() }", "function f() {\n x();\n}\nvar f;\nfunction f() {\n y();\n}\n") + expectPrintedMangle(t, "function f() { x() } var f; function f() { y() }", "var f;\nfunction f() {\n y();\n}\n") expectPrintedMangle(t, "export function f() { x() } function f() { y() }", "export function f() {\n x();\n}\nfunction f() {\n y();\n}\n") + + expectPrintedMangle(t, "var x = x || {}; console.log(x); var x = x || {};", "var x = x || {};\nconsole.log(x);\nvar x = x || {};\n") } func TestClass(t *testing.T) {