Skip to content

Commit

Permalink
lower arrow functions for es5 (#182, #297)
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Sep 19, 2020
1 parent 40e0021 commit bee2b29
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 41 deletions.
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

## Unreleased

* Transform arrow functions to function expressions with `--target=es5` ([#182](https://github.com/evanw/esbuild/issues/182) and [#297](https://github.com/evanw/esbuild/issues/297))

Arrow functions are now transformed into function expressions when targeting `es5`. For example, this code:

```js
function foo() {
var x = () => [this, arguments]
return x()
}
```

is transformed into this code:

```js
function foo() {
var _this = this, _arguments = arguments;
var x = function() {
return [_this, _arguments];
};
return x();
}
```

* Fix a bug where `module` was incorrectly minified for non-JavaScript loaders

If you pass a non-JavaScript file such as a `.json` file to esbuild, it will by default generate `module.exports = {...}`. However, the `module` variable would incorrectly be minified when `--minify` is present. This issue has been fixed. This bug did not appear if `--format=cjs` was also present, only if no `--format` flag was specified.
Expand Down
2 changes: 0 additions & 2 deletions internal/bundler/bundler_lower_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -900,9 +900,7 @@ func TestLowerAsyncES5(t *testing.T) {
AbsOutputFile: "/out.js",
},
expectedScanLog: `/arrow-1.js: error: Transforming async functions to the configured target environment is not supported yet
/arrow-1.js: error: Transforming arrow functions to the configured target environment is not supported yet
/arrow-2.js: error: Transforming async functions to the configured target environment is not supported yet
/arrow-2.js: error: Transforming arrow functions to the configured target environment is not supported yet
/export-def-1.js: error: Transforming async functions to the configured target environment is not supported yet
/export-def-2.js: error: Transforming async functions to the configured target environment is not supported yet
/fn-expr.js: error: Transforming async functions to the configured target environment is not supported yet
Expand Down
36 changes: 26 additions & 10 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ type fnOrArrowDataParse struct {
type fnOrArrowDataVisit struct {
superIndexRef *js_ast.Ref

isArrow bool
isAsync bool
isInsideLoop bool
isInsideSwitch bool
Expand Down Expand Up @@ -1610,12 +1611,7 @@ func (p *parser) parseArrowBody(args []js_ast.Arg, data fnOrArrowDataParse) *js_
panic(js_lexer.LexerPanic{})
}

if p.lexer.Token == js_lexer.TEqualsGreaterThan {
p.markSyntaxFeature(compat.Arrow, p.lexer.Range())
p.lexer.Next()
} else {
p.lexer.Expected(js_lexer.TEqualsGreaterThan)
}
p.lexer.Expect(js_lexer.TEqualsGreaterThan)

for _, arg := range args {
p.declareBinding(js_ast.SymbolHoisted, arg.Binding, parseStmtOpts{})
Expand Down Expand Up @@ -7933,6 +7929,12 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
return value, exprOut{}
}

// Capture "this" inside arrow functions that will be lowered into normal
// function expressions for older language environments
if p.fnOrArrowDataVisit.isArrow && p.UnsupportedFeatures.Has(compat.Arrow) && p.fnOnlyDataVisit.isThisNested {
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: p.captureThis()}}, exprOut{}
}

case *js_ast.EImportMeta:
if p.importMetaRef != js_ast.InvalidRef {
// Replace "import.meta" with a reference to the symbol
Expand Down Expand Up @@ -9036,6 +9038,7 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
case *js_ast.EArrow:
oldFnOrArrowData := p.fnOrArrowDataVisit
p.fnOrArrowDataVisit = fnOrArrowDataVisit{
isArrow: true,
isAsync: e.IsAsync,
}

Expand Down Expand Up @@ -9071,6 +9074,17 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
p.fnOnlyDataVisit.isInsideAsyncArrowFn = oldInsideAsyncArrowFn
p.fnOrArrowDataVisit = oldFnOrArrowData

// Convert arrow functions to function expressions when lowering
if p.UnsupportedFeatures.Has(compat.Arrow) {
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EFunction{Fn: js_ast.Fn{
Args: e.Args,
Body: e.Body,
ArgumentsRef: js_ast.InvalidRef,
IsAsync: e.IsAsync,
HasRestArg: e.HasRestArg,
}}}, exprOut{}
}

case *js_ast.EFunction:
p.visitFn(&e.Fn, expr.Loc)

Expand Down Expand Up @@ -9115,10 +9129,12 @@ func (p *parser) handleIdentifier(loc logger.Loc, assignTarget js_ast.AssignTarg
ref := e.Ref

// Capture the "arguments" variable if necessary
if p.fnOnlyDataVisit.isInsideAsyncArrowFn && p.UnsupportedFeatures.Has(compat.AsyncAwait) &&
p.fnOnlyDataVisit.argumentsRef != nil && ref == *p.fnOnlyDataVisit.argumentsRef {
ref = p.captureArguments()
e.Ref = ref
if p.fnOnlyDataVisit.argumentsRef != nil && ref == *p.fnOnlyDataVisit.argumentsRef {
isInsideUnsupportedArrow := p.fnOrArrowDataVisit.isArrow && p.UnsupportedFeatures.Has(compat.Arrow)
isInsideUnsupportedAsyncArrow := p.fnOnlyDataVisit.isInsideAsyncArrowFn && p.UnsupportedFeatures.Has(compat.AsyncAwait)
if isInsideUnsupportedArrow || isInsideUnsupportedAsyncArrow {
return js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: p.captureArguments()}}
}
}

if p.Mode == config.ModeBundle && assignTarget != js_ast.AssignTargetNone {
Expand Down
3 changes: 0 additions & 3 deletions internal/js_parser/js_parser_lower.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@ func (p *parser) markSyntaxFeature(feature compat.Feature, r logger.Range) (didG
case compat.Let:
name = "let"

case compat.Arrow:
name = "arrow functions"

case compat.Class:
name = "class syntax"

Expand Down
11 changes: 3 additions & 8 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3222,15 +3222,10 @@ func TestES5(t *testing.T) {
"<stdin>: error: Transforming const to the configured target environment is not supported yet\n")
expectParseErrorTarget(t, 5, "let x = 2;",
"<stdin>: error: Transforming let to the configured target environment is not supported yet\n")
expectParseErrorTarget(t, 5, "async => foo;",
"<stdin>: error: Transforming arrow functions to the configured target environment is not supported yet\n")
expectPrintedTarget(t, 5, "async => foo;", "(function(async) {\n return foo;\n});\n")
expectPrintedTarget(t, 5, "x => x;", "(function(x) {\n return x;\n});\n")
expectParseErrorTarget(t, 5, "async () => foo;",
"<stdin>: error: Transforming async functions to the configured target environment is not supported yet\n"+
"<stdin>: error: Transforming arrow functions to the configured target environment is not supported yet\n")
expectParseErrorTarget(t, 5, "() => foo;",
"<stdin>: error: Transforming arrow functions to the configured target environment is not supported yet\n")
expectParseErrorTarget(t, 5, "x => x;",
"<stdin>: error: Transforming arrow functions to the configured target environment is not supported yet\n")
"<stdin>: error: Transforming async functions to the configured target environment is not supported yet\n")
expectParseErrorTarget(t, 5, "class Foo {}",
"<stdin>: error: Transforming class syntax to the configured target environment is not supported yet\n")
expectParseErrorTarget(t, 5, "(class {});",
Expand Down
22 changes: 6 additions & 16 deletions internal/js_printer/js_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -936,7 +936,7 @@ func (p *printer) printFnArgs(args []js_ast.Arg, hasRestArg bool, isArrow bool)
}

func (p *printer) printFn(fn js_ast.Fn) {
p.printFnArgs(fn.Args, fn.HasRestArg, false)
p.printFnArgs(fn.Args, fn.HasRestArg, false /* isArrow */)
p.printSpace()
p.printBlock(fn.Body.Stmts)
}
Expand Down Expand Up @@ -1485,9 +1485,7 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags int) {
}

case *js_ast.EArrow:
n := len(p.js)
useFunction := p.options.UnsupportedFeatures.Has(compat.Arrow)
wrap := level >= js_ast.LAssign || (useFunction && (p.stmtStart == n || p.exportDefaultStart == n))
wrap := level >= js_ast.LAssign

if wrap {
p.print("(")
Expand All @@ -1498,21 +1496,13 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags int) {
p.printSpace()
}

if useFunction {
p.printSpaceBeforeIdentifier()
p.print("function")
}

p.printFnArgs(e.Args, e.HasRestArg, !useFunction)
p.printFnArgs(e.Args, e.HasRestArg, true /* isArrow */)
p.printSpace()
p.print("=>")
p.printSpace()

if !useFunction {
p.print("=>")
p.printSpace()
}

wasPrinted := false
if len(e.Body.Stmts) == 1 && e.PreferExpr && !useFunction {
if len(e.Body.Stmts) == 1 && e.PreferExpr {
if s, ok := e.Body.Stmts[0].Data.(*js_ast.SReturn); ok && s.Value != nil {
p.arrowExprStart = len(p.js)
p.printExpr(*s.Value, js_ast.LComma, 0)
Expand Down
4 changes: 3 additions & 1 deletion internal/js_printer/js_printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ func expectPrintedCommon(t *testing.T, name string, contents string, expected st
t.Run(name, func(t *testing.T) {
t.Helper()
log := logger.NewDeferLog()
tree, ok := js_parser.Parse(log, test.SourceForTest(contents), config.Options{})
tree, ok := js_parser.Parse(log, test.SourceForTest(contents), config.Options{
UnsupportedFeatures: options.UnsupportedFeatures,
})
msgs := log.Done()
text := ""
for _, msg := range msgs {
Expand Down
2 changes: 1 addition & 1 deletion internal/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
const SourceIndex = uint32(0)

func CanUseES6(unsupportedFeatures compat.Feature) bool {
return !unsupportedFeatures.Has(compat.Let)
return !unsupportedFeatures.Has(compat.Let) && !unsupportedFeatures.Has(compat.Arrow)
}

func code(isES6 bool) string {
Expand Down

0 comments on commit bee2b29

Please sign in to comment.