Skip to content

Commit

Permalink
Implement proposal-top-level-await
Browse files Browse the repository at this point in the history
  • Loading branch information
ghaiklor authored and adrianheine committed May 31, 2018
1 parent 17a73fc commit 65a9137
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ object referring to that same position.
- **allowImportExportEverywhere**: By default, `import` and `export`
declarations can only appear at a program's top level. Setting this
option to `true` allows them anywhere where a statement is allowed.

- **allowAwaitOutsideFunction**: By default, `await` expressions can only appear inside `async` functions. Setting this option to `true` allows to have top-level `await` expressions. They are still not allowed in non-`async` functions, though.

- **allowHashBang**: When this is enabled (off by default), if the
code starts with the characters `#!` (as in a shellscript), the
Expand Down
2 changes: 1 addition & 1 deletion src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ pp.buildBinary = function(startPos, startLoc, left, right, op, logical) {

pp.parseMaybeUnary = function(refDestructuringErrors, sawUnary) {
let startPos = this.start, startLoc = this.startLoc, expr
if (this.inAsync && this.isContextual("await")) {
if (this.isContextual("await") && (this.inAsync || (!this.inFunction && this.options.allowAwaitOutsideFunction))) {

This comment has been minimized.

Copy link
@jdalton

jdalton Jun 1, 2018

Contributor

👆 Boolean checks (as the second half) are probably cheaper than the isContextual call. The order could be switched since both are required, to perform the cheaper check first.

expr = this.parseAwait()
sawUnary = true
} else if (this.type.prefix) {
Expand Down
12 changes: 9 additions & 3 deletions src/loose/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ lp.parseExprOp = function(left, start, minPrec, noIn, indent, line) {

lp.parseMaybeUnary = function(sawUnary) {
let start = this.storeCurrentPos(), expr
if (this.options.ecmaVersion >= 8 && this.inAsync && this.toks.isContextual("await")) {
if (this.options.ecmaVersion >= 8 && this.toks.isContextual("await") &&
(this.inAsync || (!this.inFunction && this.options.allowAwaitOutsideFunction))
) {
expr = this.parseAwait()
sawUnary = true
} else if (this.tok.type.prefix) {
Expand Down Expand Up @@ -514,26 +516,29 @@ lp.parseFunctionParams = function(params) {
}

lp.parseMethod = function(isGenerator, isAsync) {
let node = this.startNode(), oldInAsync = this.inAsync
let node = this.startNode(), oldInAsync = this.inAsync, oldInFunction = this.inFunction
this.initFunction(node)
if (this.options.ecmaVersion >= 6)
node.generator = !!isGenerator
if (this.options.ecmaVersion >= 8)
node.async = !!isAsync
this.inAsync = node.async
this.inFunction = true
node.params = this.parseFunctionParams()
node.body = this.parseBlock()
this.toks.adaptDirectivePrologue(node.body.body)
this.inAsync = oldInAsync
this.inFunction = oldInFunction
return this.finishNode(node, "FunctionExpression")
}

lp.parseArrowExpression = function(node, params, isAsync) {
let oldInAsync = this.inAsync
let oldInAsync = this.inAsync, oldInFunction = this.inFunction
this.initFunction(node)
if (this.options.ecmaVersion >= 8)
node.async = !!isAsync
this.inAsync = node.async
this.inFunction = true
node.params = this.toAssignableList(params, true)
node.expression = this.tok.type !== tt.braceL
if (node.expression) {
Expand All @@ -543,6 +548,7 @@ lp.parseArrowExpression = function(node, params, isAsync) {
this.toks.adaptDirectivePrologue(node.body.body)
}
this.inAsync = oldInAsync
this.inFunction = oldInFunction
return this.finishNode(node, "ArrowFunctionExpression")
}

Expand Down
1 change: 1 addition & 0 deletions src/loose/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class LooseParser {
this.curLineStart = 0
this.nextLineStart = this.lineEnd(this.curLineStart) + 1
this.inAsync = false
this.inFunction = false
// Load plugins
this.options.pluginsLoose = options.pluginsLoose || {}
this.loadPlugins(this.options.pluginsLoose)
Expand Down
4 changes: 3 additions & 1 deletion src/loose/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ lp.parseClass = function(isStatement) {
}

lp.parseFunction = function(node, isStatement, isAsync) {
let oldInAsync = this.inAsync
let oldInAsync = this.inAsync, oldInFunction = this.inFunction
this.initFunction(node)
if (this.options.ecmaVersion >= 6) {
node.generator = this.eat(tt.star)
Expand All @@ -342,10 +342,12 @@ lp.parseFunction = function(node, isStatement, isAsync) {
if (this.tok.type === tt.name) node.id = this.parseIdent()
else if (isStatement === true) node.id = this.dummyIdent()
this.inAsync = node.async
this.inFunction = true
node.params = this.parseFunctionParams()
node.body = this.parseBlock()
this.toks.adaptDirectivePrologue(node.body.body)
this.inAsync = oldInAsync
this.inFunction = oldInFunction
return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression")
}

Expand Down
3 changes: 3 additions & 0 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export const defaultOptions = {
// When enabled, import/export statements are not constrained to
// appearing at the top of the program.
allowImportExportEverywhere: false,
// When enabled, await identifiers are allowed to appear at the top-level scope,
// but they are still not allowed in non-async functions.
allowAwaitOutsideFunction: false,
// When enabled, hashbang directive in the beginning of file
// is allowed and treated as a line comment.
allowHashBang: false,
Expand Down
1 change: 1 addition & 0 deletions test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require("./tests-harmony.js");
require("./tests-es7.js");
require("./tests-asyncawait.js");
require("./tests-await-top-level.js");
require("./tests-trailing-commas-in-func.js");
require("./tests-template-literal-revision.js");
require("./tests-directive.js");
Expand Down
38 changes: 38 additions & 0 deletions test/tests-await-top-level.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
if (typeof exports != "undefined") {
var test = require("./driver.js").test
var testFail = require("./driver.js").testFail
}

//------------------------------------------------------------------------------
// await-top-level
//------------------------------------------------------------------------------

testFail("await 1", "Unexpected token (1:6)", {ecmaVersion: 8})
test("await 1", {
"type": "Program",
"start": 0,
"end": 7,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 7,
"expression": {
"type": "AwaitExpression",
"start": 0,
"end": 7,
"argument": {
"type": "Literal",
"start": 6,
"end": 7,
"value": 1
}
}
}
]
}, {allowAwaitOutsideFunction: true, ecmaVersion: 8})
testFail("function foo() {return await 1}", "Unexpected token (1:29)", {ecmaVersion: 8})
testFail("function foo() {return await 1}", "Unexpected token (1:29)", {
allowAwaitOutsideFunction: true,
ecmaVersion: 8
})

0 comments on commit 65a9137

Please sign in to comment.