From 9b4b436e1ff4e6d435ce9dffe380ee51c5e4b0b5 Mon Sep 17 00:00:00 2001 From: Brian Ng Date: Sun, 16 Sep 2018 22:09:23 -0500 Subject: [PATCH] Fix parsing of newline between 'async' and 'function' (#8698) --- packages/babel-parser/src/parser/statement.js | 44 +++++-- packages/babel-parser/src/util/whitespace.js | 2 + .../async-functions/export-async/options.json | 2 +- .../export-default-newline/input.js | 2 + .../export-default-newline/options.json | 3 + .../export-default-newline/output.json | 119 ++++++++++++++++++ .../async-functions/export-invalid/input.js | 1 + .../export-invalid/options.json | 4 + .../es2017/async-functions/newline/input.js | 2 + .../async-functions/newline/output.json | 119 ++++++++++++++++++ 10 files changed, 285 insertions(+), 13 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/input.js create mode 100644 packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/options.json create mode 100644 packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/output.json create mode 100644 packages/babel-parser/test/fixtures/es2017/async-functions/export-invalid/input.js create mode 100644 packages/babel-parser/test/fixtures/es2017/async-functions/export-invalid/options.json create mode 100644 packages/babel-parser/test/fixtures/es2017/async-functions/newline/input.js create mode 100644 packages/babel-parser/test/fixtures/es2017/async-functions/newline/output.json diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 7bf7f55d07..9e890d2ef6 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -3,7 +3,8 @@ import * as N from "../types"; import { types as tt, type TokenType } from "../tokenizer/types"; import ExpressionParser from "./expression"; -import { lineBreak } from "../util/whitespace"; +import { isIdentifierChar } from "../util/identifier"; +import { lineBreak, skipWhiteSpace } from "../util/whitespace"; // Reused empty array added for node fields that are always empty. @@ -1425,18 +1426,37 @@ export default class StatementParser extends ExpressionParser { return this.finishNode(node, "ExportNamedDeclaration"); } + isAsyncFunction() { + if (!this.isContextual("async")) return false; + + const { input, pos } = this.state; + + skipWhiteSpace.lastIndex = pos; + const skip = skipWhiteSpace.exec(input); + + if (!skip || !skip.length) return false; + + const next = pos + skip[0].length; + + return ( + !lineBreak.test(input.slice(pos, next)) && + input.slice(next, next + 8) === "function" && + (next + 8 === input.length || !isIdentifierChar(input.charAt(next + 8))) + ); + } + parseExportDefaultExpression(): N.Expression | N.Declaration { const expr = this.startNode(); - if (this.eat(tt._function)) { - return this.parseFunction(expr, true, false, false, true); - } else if ( - this.isContextual("async") && - this.lookahead().type === tt._function - ) { - // async function declaration - this.eatContextual("async"); - this.eat(tt._function); - return this.parseFunction(expr, true, false, true, true); + + const isAsync = this.isAsyncFunction(); + + if (this.eat(tt._function) || isAsync) { + if (isAsync) { + this.eatContextual("async"); + this.expect(tt._function); + } + + return this.parseFunction(expr, true, false, isAsync, true); } else if (this.match(tt._class)) { return this.parseClass(expr, true, true); } else if (this.match(tt.at)) { @@ -1569,7 +1589,7 @@ export default class StatementParser extends ExpressionParser { this.state.type.keyword === "let" || this.state.type.keyword === "function" || this.state.type.keyword === "class" || - this.isContextual("async") + this.isAsyncFunction() ); } diff --git a/packages/babel-parser/src/util/whitespace.js b/packages/babel-parser/src/util/whitespace.js index 656e772fb4..d82d87413d 100644 --- a/packages/babel-parser/src/util/whitespace.js +++ b/packages/babel-parser/src/util/whitespace.js @@ -21,6 +21,8 @@ export function isNewLine(code: number): boolean { } } +export const skipWhiteSpace = /(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g; + // https://tc39.github.io/ecma262/#sec-white-space export function isWhitespace(code: number): boolean { switch (code) { diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/export-async/options.json b/packages/babel-parser/test/fixtures/es2017/async-functions/export-async/options.json index 8c70964e8c..9b76c337e1 100644 --- a/packages/babel-parser/test/fixtures/es2017/async-functions/export-async/options.json +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/export-async/options.json @@ -1,4 +1,4 @@ { "sourceType": "module", - "throws": "Unexpected token, expected \"function\" (1:12)" + "throws": "Unexpected token, expected \"{\" (1:7)" } diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/input.js new file mode 100644 index 0000000000..01c5415bc5 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/input.js @@ -0,0 +1,2 @@ +export default async +function bar() {} diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/options.json b/packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/options.json new file mode 100644 index 0000000000..2104ca4328 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/options.json @@ -0,0 +1,3 @@ +{ + "sourceType": "module" +} diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/output.json b/packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/output.json new file mode 100644 index 0000000000..9179ea920b --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/export-default-newline/output.json @@ -0,0 +1,119 @@ +{ + "type": "File", + "start": 0, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 17 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 17 + } + }, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExportDefaultDeclaration", + "start": 0, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "declaration": { + "type": "Identifier", + "start": 15, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 20 + }, + "identifierName": "async" + }, + "name": "async" + } + }, + { + "type": "FunctionDeclaration", + "start": 21, + "end": 38, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 17 + } + }, + "id": { + "type": "Identifier", + "start": 30, + "end": 33, + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 12 + }, + "identifierName": "bar" + }, + "name": "bar" + }, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 36, + "end": 38, + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 17 + } + }, + "body": [], + "directives": [] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/export-invalid/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/export-invalid/input.js new file mode 100644 index 0000000000..a7d31c894d --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/export-invalid/input.js @@ -0,0 +1 @@ +export default async functionX () {} diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/export-invalid/options.json b/packages/babel-parser/test/fixtures/es2017/async-functions/export-invalid/options.json new file mode 100644 index 0000000000..5da632286f --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/export-invalid/options.json @@ -0,0 +1,4 @@ +{ + "sourceType": "module", + "throws": "Unexpected token, expected \"function\" (1:21)" +} diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/newline/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/newline/input.js new file mode 100644 index 0000000000..8bb94fe81d --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/newline/input.js @@ -0,0 +1,2 @@ +async +function foo() { } diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/newline/output.json b/packages/babel-parser/test/fixtures/es2017/async-functions/newline/output.json new file mode 100644 index 0000000000..bed2c37da9 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/newline/output.json @@ -0,0 +1,119 @@ +{ + "type": "File", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 18 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 18 + } + }, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 5, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 5 + } + }, + "expression": { + "type": "Identifier", + "start": 0, + "end": 5, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 5 + }, + "identifierName": "async" + }, + "name": "async" + } + }, + { + "type": "FunctionDeclaration", + "start": 6, + "end": 24, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 18 + } + }, + "id": { + "type": "Identifier", + "start": 15, + "end": 18, + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 12 + }, + "identifierName": "foo" + }, + "name": "foo" + }, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 21, + "end": 24, + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 18 + } + }, + "body": [], + "directives": [] + } + } + ], + "directives": [] + } +} \ No newline at end of file