From 2817069c6f70e8c4db03a700c7818045c7d9943e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 29 Jan 2021 15:52:31 -0500 Subject: [PATCH 1/6] refactor: raise AwaitNotInAsyncContext when an AwaitExpression will be parsed --- packages/babel-parser/src/parser/error.js | 34 ++++++++++++ .../babel-parser/src/parser/expression.js | 26 +++------- packages/babel-parser/src/tokenizer/state.js | 3 -- .../es2015/uncategorised/357/output.json | 2 +- .../es2015/uncategorised/359/output.json | 2 +- .../es2015/uncategorised/361/output.json | 2 +- .../es2015/uncategorised/363/output.json | 2 +- .../es2015/uncategorised/365/output.json | 2 +- .../es2015/uncategorised/367/output.json | 2 +- .../top-level-await/inside-arrow/options.json | 6 +-- .../top-level-await/inside-arrow/output.json | 41 +++++++++++++++ .../inside-function/options.json | 6 +-- .../inside-function/output.json | 52 +++++++++++++++++++ 13 files changed, 145 insertions(+), 35 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/experimental/top-level-await/inside-arrow/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/top-level-await/inside-function/output.json diff --git a/packages/babel-parser/src/parser/error.js b/packages/babel-parser/src/parser/error.js index 4fe08d8c4644..580ec1ce0a67 100644 --- a/packages/babel-parser/src/parser/error.js +++ b/packages/babel-parser/src/parser/error.js @@ -39,6 +39,40 @@ export default class ParserError extends CommentsParser { return this.raiseWithData(pos, undefined, errorTemplate, ...params); } + /** + * Raise a parsing error on given postion pos. If errorRecovery is true, + * it will first search current errors and overwrite the error thrown on the exact + * position before with the new error message. If errorRecovery is false, it + * fallbacks to `raise`. + * + * @param {number} pos + * @param {string} errorTemplate + * @param {...any} params + * @returns {(Error | empty)} + * @memberof ParserError + */ + raiseOverwrite( + pos: number, + errorTemplate: string, + ...params: any + ): Error | empty { + if (this.options.errorRecovery) { + const loc = this.getLocationForPosition(pos); + const message = + errorTemplate.replace(/%(\d+)/g, (_, i: number) => params[i]) + + ` (${loc.line}:${loc.column})`; + const errors = this.state.errors; + for (let i = errors.length - 1; i >= 0; i--) { + const error = errors[i]; + if (error.pos === pos) { + Object.assign(error, { message }); + return; + } + } + } + return this.raiseWithData(pos, undefined, errorTemplate, ...params); + } + raiseWithData( pos: number, data?: { diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index b50aed34e682..96432ef6657d 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -534,19 +534,19 @@ export default class ExpressionParser extends LValParser { const expr = this.parseUpdate(node, update, refExpressionErrors); - const startsExpr = this.hasPlugin("v8intrinsic") - ? this.state.type.startsExpr - : this.state.type.startsExpr && !this.match(tt.modulo); - if (isAwait && startsExpr && !this.isAmbiguousAwait()) { - if (!this.state.invalidAwaitErrors.has(startPos)) { - this.raise( + if (isAwait) { + const startsExpr = this.hasPlugin("v8intrinsic") + ? this.state.type.startsExpr + : this.state.type.startsExpr && !this.match(tt.modulo); + if (startsExpr && !this.isAmbiguousAwait()) { + this.raiseOverwrite( startPos, this.hasPlugin("topLevelAwait") ? Errors.AwaitNotInAsyncContext : Errors.AwaitNotInAsyncFunction, ); + return this.parseAwait(startPos, startLoc); } - return this.parseAwait(startPos, startLoc); } return expr; @@ -2348,17 +2348,7 @@ export default class ExpressionParser extends LValParser { : isStrictReservedWord; if (reservedTest(word, this.inModule)) { - if (!this.prodParam.hasAwait && word === "await") { - this.raise( - startLoc, - this.hasPlugin("topLevelAwait") - ? Errors.AwaitNotInAsyncContext - : Errors.AwaitNotInAsyncFunction, - ); - this.state.invalidAwaitErrors.add(startLoc); - } else { - this.raise(startLoc, Errors.UnexpectedReservedWord, word); - } + this.raise(startLoc, Errors.UnexpectedReservedWord, word); } } diff --git a/packages/babel-parser/src/tokenizer/state.js b/packages/babel-parser/src/tokenizer/state.js index e10cbd5295c6..8a4ca1aede31 100644 --- a/packages/babel-parser/src/tokenizer/state.js +++ b/packages/babel-parser/src/tokenizer/state.js @@ -155,9 +155,6 @@ export default class State { // Tokens length in token store tokensLength: number = 0; - // Positions of invalid await errors - invalidAwaitErrors: Set = new Set(); - curPosition(): Position { return new Position(this.curLine, this.pos - this.lineStart); } diff --git a/packages/babel-parser/test/fixtures/es2015/uncategorised/357/output.json b/packages/babel-parser/test/fixtures/es2015/uncategorised/357/output.json index 5d343bd79c44..ba52d9ef6dd4 100644 --- a/packages/babel-parser/test/fixtures/es2015/uncategorised/357/output.json +++ b/packages/babel-parser/test/fixtures/es2015/uncategorised/357/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}}, "errors": [ - "SyntaxError: 'await' is only allowed within async functions (1:0)" + "SyntaxError: Unexpected reserved word 'await' (1:0)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/es2015/uncategorised/359/output.json b/packages/babel-parser/test/fixtures/es2015/uncategorised/359/output.json index a73b09d33fe5..a178730ecfdb 100644 --- a/packages/babel-parser/test/fixtures/es2015/uncategorised/359/output.json +++ b/packages/babel-parser/test/fixtures/es2015/uncategorised/359/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}}, "errors": [ - "SyntaxError: 'await' is only allowed within async functions (1:6)" + "SyntaxError: Unexpected reserved word 'await' (1:6)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/es2015/uncategorised/361/output.json b/packages/babel-parser/test/fixtures/es2015/uncategorised/361/output.json index b578671bc45f..f36b09894784 100644 --- a/packages/babel-parser/test/fixtures/es2015/uncategorised/361/output.json +++ b/packages/babel-parser/test/fixtures/es2015/uncategorised/361/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":24,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":24}}, "errors": [ - "SyntaxError: 'await' is only allowed within async functions (1:8)" + "SyntaxError: Unexpected reserved word 'await' (1:8)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/es2015/uncategorised/363/output.json b/packages/babel-parser/test/fixtures/es2015/uncategorised/363/output.json index b2ccd978a6d8..0c3cf49c0481 100644 --- a/packages/babel-parser/test/fixtures/es2015/uncategorised/363/output.json +++ b/packages/babel-parser/test/fixtures/es2015/uncategorised/363/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":26}}, "errors": [ - "SyntaxError: 'await' is only allowed within async functions (1:15)" + "SyntaxError: Unexpected reserved word 'await' (1:15)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/es2015/uncategorised/365/output.json b/packages/babel-parser/test/fixtures/es2015/uncategorised/365/output.json index 1b0e35a9e54f..23b41eb517f9 100644 --- a/packages/babel-parser/test/fixtures/es2015/uncategorised/365/output.json +++ b/packages/babel-parser/test/fixtures/es2015/uncategorised/365/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, "errors": [ - "SyntaxError: 'await' is only allowed within async functions (1:9)" + "SyntaxError: Unexpected reserved word 'await' (1:9)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/es2015/uncategorised/367/output.json b/packages/babel-parser/test/fixtures/es2015/uncategorised/367/output.json index 8700e3ede62b..5473a1cc88a4 100644 --- a/packages/babel-parser/test/fixtures/es2015/uncategorised/367/output.json +++ b/packages/babel-parser/test/fixtures/es2015/uncategorised/367/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}}, "errors": [ - "SyntaxError: 'await' is only allowed within async functions (1:6)" + "SyntaxError: Unexpected reserved word 'await' (1:6)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-arrow/options.json b/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-arrow/options.json index 9ca042773617..a2c33141ed15 100644 --- a/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-arrow/options.json +++ b/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-arrow/options.json @@ -2,7 +2,5 @@ "plugins": [ "topLevelAwait" ], - "sourceType": "module", - "errorRecovery": false, - "throws": "'await' is only allowed within async functions and at the top levels of modules (1:6)" -} \ No newline at end of file + "sourceType": "module" +} diff --git a/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-arrow/output.json b/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-arrow/output.json new file mode 100644 index 000000000000..47dbaed6089b --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-arrow/output.json @@ -0,0 +1,41 @@ +{ + "type": "File", + "start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}}, + "errors": [ + "SyntaxError: 'await' is only allowed within async functions and at the top levels of modules (1:6)" + ], + "program": { + "type": "Program", + "start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}}, + "expression": { + "type": "ArrowFunctionExpression", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}}, + "id": null, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "AwaitExpression", + "start":6,"end":13,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":13}}, + "argument": { + "type": "NumericLiteral", + "start":12,"end":13,"loc":{"start":{"line":1,"column":12},"end":{"line":1,"column":13}}, + "extra": { + "rawValue": 0, + "raw": "0" + }, + "value": 0 + } + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-function/options.json b/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-function/options.json index c551469975d6..ee3e94739c6f 100644 --- a/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-function/options.json +++ b/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-function/options.json @@ -2,7 +2,5 @@ "plugins": [ "topLevelAwait" ], - "sourceType": "module", - "errorRecovery": false, - "throws": "'await' is only allowed within async functions and at the top levels of modules (2:2)" -} \ No newline at end of file + "sourceType": "module" + } diff --git a/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-function/output.json b/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-function/output.json new file mode 100644 index 000000000000..27236952ad5a --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/top-level-await/inside-function/output.json @@ -0,0 +1,52 @@ +{ + "type": "File", + "start":0,"end":28,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "errors": [ + "SyntaxError: 'await' is only allowed within async functions and at the top levels of modules (2:2)" + ], + "program": { + "type": "Program", + "start":0,"end":28,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "FunctionDeclaration", + "start":0,"end":28,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "id": { + "type": "Identifier", + "start":9,"end":11,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":11},"identifierName":"fn"}, + "name": "fn" + }, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start":14,"end":28,"loc":{"start":{"line":1,"column":14},"end":{"line":3,"column":1}}, + "body": [ + { + "type": "ExpressionStatement", + "start":18,"end":26,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":10}}, + "expression": { + "type": "AwaitExpression", + "start":18,"end":25,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":9}}, + "argument": { + "type": "NumericLiteral", + "start":24,"end":25,"loc":{"start":{"line":2,"column":8},"end":{"line":2,"column":9}}, + "extra": { + "rawValue": 0, + "raw": "0" + }, + "value": 0 + } + } + } + ], + "directives": [] + } + } + ], + "directives": [] + } +} \ No newline at end of file From 752c4899db879d80a320a3fcb16f5eadd54b6311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 29 Jan 2021 16:14:53 -0500 Subject: [PATCH 2/6] tweak logic --- packages/babel-parser/src/parser/error.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/babel-parser/src/parser/error.js b/packages/babel-parser/src/parser/error.js index 580ec1ce0a67..f016b71149ce 100644 --- a/packages/babel-parser/src/parser/error.js +++ b/packages/babel-parser/src/parser/error.js @@ -56,21 +56,20 @@ export default class ParserError extends CommentsParser { errorTemplate: string, ...params: any ): Error | empty { + const loc = this.getLocationForPosition(pos); + const message = + errorTemplate.replace(/%(\d+)/g, (_, i: number) => params[i]) + + ` (${loc.line}:${loc.column})`; if (this.options.errorRecovery) { - const loc = this.getLocationForPosition(pos); - const message = - errorTemplate.replace(/%(\d+)/g, (_, i: number) => params[i]) + - ` (${loc.line}:${loc.column})`; const errors = this.state.errors; for (let i = errors.length - 1; i >= 0; i--) { const error = errors[i]; if (error.pos === pos) { - Object.assign(error, { message }); - return; + return Object.assign(error, { message }); } } } - return this.raiseWithData(pos, undefined, errorTemplate, ...params); + return this._raise({ loc, pos }, message); } raiseWithData( From 1b4403deda3d99818472e5a563db288523263f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 29 Jan 2021 16:17:12 -0500 Subject: [PATCH 3/6] early exit when errors are before thrown error position --- packages/babel-parser/src/parser/error.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/babel-parser/src/parser/error.js b/packages/babel-parser/src/parser/error.js index f016b71149ce..5302f21b1741 100644 --- a/packages/babel-parser/src/parser/error.js +++ b/packages/babel-parser/src/parser/error.js @@ -66,6 +66,8 @@ export default class ParserError extends CommentsParser { const error = errors[i]; if (error.pos === pos) { return Object.assign(error, { message }); + } else if (error.pos < pos) { + break; } } } From cc741ba264fc8d3275844535fa260a9c9a96fd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 29 Jan 2021 17:24:10 -0500 Subject: [PATCH 4/6] fix: always return true in assert.throws callback See https://nodejs.org/api/assert.html#assert_assert_throws_fn_error_message --- .../src/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/babel-helper-transform-fixture-test-runner/src/index.js b/packages/babel-helper-transform-fixture-test-runner/src/index.js index 1b5116349537..31e1350e03df 100644 --- a/packages/babel-helper-transform-fixture-test-runner/src/index.js +++ b/packages/babel-helper-transform-fixture-test-runner/src/index.js @@ -420,7 +420,13 @@ export default function ( delete task.options.throws; assert.throws(runTask, function (err) { - return throwMsg === true || err.message.indexOf(throwMsg) >= 0; + assert.ok( + throwMsg === true || err.message.includes(throwMsg), + ` +Expected Error: ${throwMsg} +Actual Error: ${err.message}`, + ); + return true; }); } else { if (task.exec.code) { From 38a7717a2dcdbbdb9695275eebac65f4b5dcc02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 29 Jan 2021 17:24:54 -0500 Subject: [PATCH 5/6] update test fixtures --- .../test/fixtures/top-level-await/unsupported/options.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/babel-preset-env/test/fixtures/top-level-await/unsupported/options.json b/packages/babel-preset-env/test/fixtures/top-level-await/unsupported/options.json index 6201eeb28699..e79b0ad1b184 100644 --- a/packages/babel-preset-env/test/fixtures/top-level-await/unsupported/options.json +++ b/packages/babel-preset-env/test/fixtures/top-level-await/unsupported/options.json @@ -6,5 +6,5 @@ "supportsTopLevelAwait": false }, "presets": ["env"], - "throws": "'await' is only allowed within async functions (1:0)" + "throws": "Unexpected reserved word 'await' (1:0)" } From afbcc3205f1cf3f4ddacdc56bd3be3f98ea7a2ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 29 Jan 2021 18:21:29 -0500 Subject: [PATCH 6/6] fix flow error --- packages/babel-parser/src/parser/error.js | 2 ++ packages/babel-parser/src/tokenizer/state.js | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/babel-parser/src/parser/error.js b/packages/babel-parser/src/parser/error.js index 5302f21b1741..eea9610307ba 100644 --- a/packages/babel-parser/src/parser/error.js +++ b/packages/babel-parser/src/parser/error.js @@ -16,6 +16,8 @@ type ErrorContext = { code?: string, }; +export type ParsingError = SyntaxError & ErrorContext; + export { ErrorMessages as Errors } from "./error-message.js"; export default class ParserError extends CommentsParser { diff --git a/packages/babel-parser/src/tokenizer/state.js b/packages/babel-parser/src/tokenizer/state.js index 8a4ca1aede31..b3a45637bbe9 100644 --- a/packages/babel-parser/src/tokenizer/state.js +++ b/packages/babel-parser/src/tokenizer/state.js @@ -6,6 +6,7 @@ import { Position } from "../util/location"; import { types as ct, type TokContext } from "./context"; import { types as tt, type TokenType } from "./types"; +import type { ParsingError } from "../parser/error"; type TopicContextState = { // When a topic binding has been currently established, @@ -37,7 +38,7 @@ export default class State { this.startLoc = this.endLoc = this.curPosition(); } - errors: SyntaxError[] = []; + errors: ParsingError[] = []; // Used to signify the start of a potential arrow function potentialArrowAt: number = -1;