Skip to content

Commit 87feda7

Browse files
@babel/parser error recovery (#10363)
* Add error recovery support to @babel/parser * Update @babel/parser tests to always recover from errors * Update this.raise usage in @babel/parser: - expression.js - lval.js - statement.js - estree.js - flow.js - jsx/index.js - tokenizer/index.js * Update @babel/parser fixtures with recovered errors * Fix tests out of @babel/parser * Do not use try/catch for control flow * Update invalid fixtures * Do not report invalid lhs in toAssignable * Do not validate function id multiple times * Dedupe reserved await errors * Remove duplicate errors about strict reserved bindings * Remove duplicated error about yield/await inside params * Don't error twice for methods in object patterns * Don't report invalid super() twice * Remove dup error about reserved param for expr arrows * Remove double escapes in migrated tests * Dedupe errors about invalid escapes in identifiers * Remove duplicated error about decorated constructor * Remove duplicated error about spread in flow class * Don't throw for invalid super usage * Don't fail for object decorators with stage 2 * Fix flow inexact type errors * Fix flow * Fix errors about escapes in keywords (ref: #10455) * Update after rebase * Fix todo * Remove duplicated error when using += for defaults * Remove unnecessary throw * Nit: use ??
1 parent d25262e commit 87feda7

File tree

2,224 files changed

+155997
-3354
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

2,224 files changed

+155997
-3354
lines changed

packages/babel-core/test/fixtures/parse/output.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"type": "File",
33
"start": 0,
44
"end": 91,
5+
"errors": [],
56
"loc": {
67
"start": {
78
"line": 1,

packages/babel-parser/src/options.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type Options = {
2121
ranges: boolean,
2222
tokens: boolean,
2323
createParenthesizedExpressions: boolean,
24+
errorRecovery: boolean,
2425
};
2526

2627
export const defaultOptions: Options = {
@@ -62,6 +63,9 @@ export const defaultOptions: Options = {
6263
// Whether to create ParenthesizedExpression AST nodes (if false
6364
// the parser sets extra.parenthesized on the expression nodes instead).
6465
createParenthesizedExpressions: false,
66+
// When enabled, errors are attached to the AST instead of being directly thrown.
67+
// Some errors will still throw, because @babel/parser can't always recover.
68+
errorRecovery: false,
6569
};
6670

6771
// Interpret and default an options object

packages/babel-parser/src/parser/expression.js

Lines changed: 83 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export default class ExpressionParser extends LValParser {
108108
this.unexpected();
109109
}
110110
expr.comments = this.state.comments;
111+
expr.errors = this.state.errors;
111112
return expr;
112113
}
113114

@@ -786,11 +787,11 @@ export default class ExpressionParser extends LValParser {
786787
if (node.callee.type === "Import") {
787788
if (node.arguments.length !== 1) {
788789
this.raise(node.start, "import() requires exactly one argument");
789-
}
790-
791-
const importArg = node.arguments[0];
792-
if (importArg && importArg.type === "SpreadElement") {
793-
this.raise(importArg.start, "... is not allowed in import()");
790+
} else {
791+
const importArg = node.arguments[0];
792+
if (importArg && importArg.type === "SpreadElement") {
793+
this.raise(importArg.start, "... is not allowed in import()");
794+
}
794795
}
795796
}
796797
return this.finishNode(
@@ -903,13 +904,6 @@ export default class ExpressionParser extends LValParser {
903904

904905
switch (this.state.type) {
905906
case tt._super:
906-
if (!this.scope.allowSuper && !this.options.allowSuperOutsideMethod) {
907-
this.raise(
908-
this.state.start,
909-
"super is only allowed in object methods and classes",
910-
);
911-
}
912-
913907
node = this.startNode();
914908
this.next();
915909
if (
@@ -922,14 +916,26 @@ export default class ExpressionParser extends LValParser {
922916
"super() is only valid inside a class constructor of a subclass. " +
923917
"Maybe a typo in the method name ('constructor') or not extending another class?",
924918
);
919+
} else if (
920+
!this.scope.allowSuper &&
921+
!this.options.allowSuperOutsideMethod
922+
) {
923+
this.raise(
924+
node.start,
925+
"super is only allowed in object methods and classes",
926+
);
925927
}
926928

927929
if (
928930
!this.match(tt.parenL) &&
929931
!this.match(tt.bracketL) &&
930932
!this.match(tt.dot)
931933
) {
932-
this.unexpected();
934+
this.raise(
935+
node.start,
936+
"super can only be used with function calls (i.e. super()) or " +
937+
"in property accesses (i.e. super.prop or super[prop])",
938+
);
933939
}
934940

935941
return this.finishNode(node, "Super");
@@ -1106,15 +1112,16 @@ export default class ExpressionParser extends LValParser {
11061112
}
11071113

11081114
this.next();
1109-
if (this.primaryTopicReferenceIsAllowedInCurrentTopicContext()) {
1110-
this.registerTopicReference();
1111-
return this.finishNode(node, "PipelinePrimaryTopicReference");
1112-
} else {
1113-
throw this.raise(
1115+
1116+
if (!this.primaryTopicReferenceIsAllowedInCurrentTopicContext()) {
1117+
this.raise(
11141118
node.start,
11151119
`Topic reference was used in a lexical context without topic binding`,
11161120
);
11171121
}
1122+
1123+
this.registerTopicReference();
1124+
return this.finishNode(node, "PipelinePrimaryTopicReference");
11181125
}
11191126
}
11201127

@@ -1199,22 +1206,22 @@ export default class ExpressionParser extends LValParser {
11991206

12001207
if (this.isContextual("meta")) {
12011208
this.expectPlugin("importMeta");
1209+
1210+
if (!this.inModule) {
1211+
this.raise(
1212+
id.start,
1213+
`import.meta may appear only with 'sourceType: "module"'`,
1214+
{ code: "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED" },
1215+
);
1216+
}
1217+
this.sawUnambiguousESM = true;
12021218
} else if (!this.hasPlugin("importMeta")) {
12031219
this.raise(
12041220
id.start,
12051221
`Dynamic imports require a parameter: import('a.js')`,
12061222
);
12071223
}
12081224

1209-
if (!this.inModule) {
1210-
this.raise(
1211-
id.start,
1212-
`import.meta may appear only with 'sourceType: "module"'`,
1213-
{ code: "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED" },
1214-
);
1215-
}
1216-
this.sawUnambiguousESM = true;
1217-
12181225
return this.parseMetaProperty(node, id, "meta");
12191226
}
12201227

@@ -1386,7 +1393,10 @@ export default class ExpressionParser extends LValParser {
13861393

13871394
parseNew(): N.NewExpression | N.MetaProperty {
13881395
const node = this.startNode();
1389-
const meta = this.parseIdentifier(true);
1396+
1397+
let meta = this.startNode();
1398+
this.next();
1399+
meta = this.createIdentifier(meta, "new");
13901400

13911401
if (this.eat(tt.dot)) {
13921402
const metaProp = this.parseMetaProperty(node, meta, "target");
@@ -1553,12 +1563,12 @@ export default class ExpressionParser extends LValParser {
15531563
this.state.start,
15541564
"Stage 2 decorators disallow object literal property decorators",
15551565
);
1556-
} else {
1557-
// we needn't check if decorators (stage 0) plugin is enabled since it's checked by
1558-
// the call to this.parseDecorator
1559-
while (this.match(tt.at)) {
1560-
decorators.push(this.parseDecorator());
1561-
}
1566+
}
1567+
1568+
// we needn't check if decorators (stage 0) plugin is enabled since it's checked by
1569+
// the call to this.parseDecorator
1570+
while (this.match(tt.at)) {
1571+
decorators.push(this.parseDecorator());
15621572
}
15631573
}
15641574

@@ -1933,7 +1943,7 @@ export default class ExpressionParser extends LValParser {
19331943

19341944
if (isExpression) {
19351945
node.body = this.parseMaybeAssign();
1936-
this.checkParams(node, false, allowExpression);
1946+
this.checkParams(node, false, allowExpression, false);
19371947
} else {
19381948
const nonSimple = !this.isSimpleParamList(node.params);
19391949
if (!oldStrict || nonSimple) {
@@ -1967,6 +1977,7 @@ export default class ExpressionParser extends LValParser {
19671977
node,
19681978
!oldStrict && !useStrict && !allowExpression && !isMethod && !nonSimple,
19691979
allowExpression,
1980+
!oldStrict && useStrict,
19701981
);
19711982
node.body = this.parseBlock(true, false);
19721983
this.state.labels = oldLabels;
@@ -1975,7 +1986,14 @@ export default class ExpressionParser extends LValParser {
19751986
this.state.inParameters = oldInParameters;
19761987
// Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval'
19771988
if (this.state.strict && node.id) {
1978-
this.checkLVal(node.id, BIND_OUTSIDE, undefined, "function name");
1989+
this.checkLVal(
1990+
node.id,
1991+
BIND_OUTSIDE,
1992+
undefined,
1993+
"function name",
1994+
undefined,
1995+
!oldStrict && useStrict,
1996+
);
19791997
}
19801998
this.state.strict = oldStrict;
19811999
}
@@ -1994,6 +2012,7 @@ export default class ExpressionParser extends LValParser {
19942012
allowDuplicates: boolean,
19952013
// eslint-disable-next-line no-unused-vars
19962014
isArrowFunction: ?boolean,
2015+
strictModeChanged?: boolean = true,
19972016
): void {
19982017
// $FlowIssue
19992018
const nameHash: {} = Object.create(null);
@@ -2003,6 +2022,8 @@ export default class ExpressionParser extends LValParser {
20032022
BIND_VAR,
20042023
allowDuplicates ? null : nameHash,
20052024
"function parameter list",
2025+
undefined,
2026+
strictModeChanged,
20062027
);
20072028
}
20082029
}
@@ -2084,6 +2105,8 @@ export default class ExpressionParser extends LValParser {
20842105
// Parse the next token as an identifier. If `liberal` is true (used
20852106
// when parsing properties), it will also convert keywords into
20862107
// identifiers.
2108+
// This shouldn't be used to parse the keywords of meta properties, since they
2109+
// are not identifiers and cannot contain escape sequences.
20872110

20882111
parseIdentifier(liberal?: boolean): N.Identifier {
20892112
const node = this.startNode();
@@ -2104,11 +2127,6 @@ export default class ExpressionParser extends LValParser {
21042127

21052128
if (this.match(tt.name)) {
21062129
name = this.state.value;
2107-
2108-
// An escaped identifier whose value is the same as a keyword
2109-
if (!liberal && this.state.containsEsc && isKeyword(name)) {
2110-
this.raise(this.state.pos, `Escape sequence in keyword ${name}`);
2111-
}
21122130
} else if (this.state.type.keyword) {
21132131
name = this.state.type.keyword;
21142132

@@ -2128,7 +2146,11 @@ export default class ExpressionParser extends LValParser {
21282146
throw this.unexpected();
21292147
}
21302148

2131-
if (!liberal) {
2149+
if (liberal) {
2150+
// If the current token is not used as a keyword, set its type to "tt.name".
2151+
// This will prevent this.next() from throwing about unexpected escapes.
2152+
this.state.type = tt.name;
2153+
} else {
21322154
this.checkReservedWord(
21332155
name,
21342156
this.state.start,
@@ -2153,6 +2175,7 @@ export default class ExpressionParser extends LValParser {
21532175
startLoc,
21542176
"Can not use 'yield' as identifier inside a generator",
21552177
);
2178+
return;
21562179
}
21572180

21582181
if (word === "await") {
@@ -2161,7 +2184,9 @@ export default class ExpressionParser extends LValParser {
21612184
startLoc,
21622185
"Can not use 'await' as identifier inside an async function",
21632186
);
2164-
} else if (
2187+
return;
2188+
}
2189+
if (
21652190
this.state.awaitPos === -1 &&
21662191
(this.state.maybeInArrowParameters || this.isAwaitAllowed())
21672192
) {
@@ -2174,9 +2199,11 @@ export default class ExpressionParser extends LValParser {
21742199
startLoc,
21752200
"'arguments' is not allowed in class field initializer",
21762201
);
2202+
return;
21772203
}
21782204
if (checkKeywords && isKeyword(word)) {
21792205
this.raise(startLoc, `Unexpected keyword '${word}'`);
2206+
return;
21802207
}
21812208

21822209
const reservedTest = !this.state.strict
@@ -2191,8 +2218,9 @@ export default class ExpressionParser extends LValParser {
21912218
startLoc,
21922219
"Can not use keyword 'await' outside an async function",
21932220
);
2221+
} else {
2222+
this.raise(startLoc, `Unexpected reserved word '${word}'`);
21942223
}
2195-
this.raise(startLoc, `Unexpected reserved word '${word}'`);
21962224
}
21972225
}
21982226

@@ -2206,9 +2234,6 @@ export default class ExpressionParser extends LValParser {
22062234
// Parses await expression inside async function.
22072235

22082236
parseAwait(): N.AwaitExpression {
2209-
if (this.state.awaitPos === -1) {
2210-
this.state.awaitPos = this.state.start;
2211-
}
22122237
const node = this.startNode();
22132238

22142239
this.next();
@@ -2218,8 +2243,10 @@ export default class ExpressionParser extends LValParser {
22182243
node.start,
22192244
"await is not allowed in async function parameters",
22202245
);
2246+
} else if (this.state.awaitPos === -1) {
2247+
this.state.awaitPos = node.start;
22212248
}
2222-
if (this.match(tt.star)) {
2249+
if (this.eat(tt.star)) {
22232250
this.raise(
22242251
node.start,
22252252
"await* has been removed from the async functions proposal. Use Promise.all() instead.",
@@ -2259,13 +2286,12 @@ export default class ExpressionParser extends LValParser {
22592286
// Parses yield expression inside generator.
22602287

22612288
parseYield(noIn?: ?boolean): N.YieldExpression {
2262-
if (this.state.yieldPos === -1) {
2263-
this.state.yieldPos = this.state.start;
2264-
}
22652289
const node = this.startNode();
22662290

22672291
if (this.state.inParameters) {
22682292
this.raise(node.start, "yield is not allowed in generator parameters");
2293+
} else if (this.state.yieldPos === -1) {
2294+
this.state.yieldPos = node.start;
22692295
}
22702296

22712297
this.next();
@@ -2291,7 +2317,7 @@ export default class ExpressionParser extends LValParser {
22912317
if (left.type === "SequenceExpression") {
22922318
// Ensure that the pipeline head is not a comma-delimited
22932319
// sequence expression.
2294-
throw this.raise(
2320+
this.raise(
22952321
leftStartPos,
22962322
`Pipeline head should not be a comma-separated sequence expression`,
22972323
);
@@ -2336,7 +2362,7 @@ export default class ExpressionParser extends LValParser {
23362362
pipelineStyle === "PipelineTopicExpression" &&
23372363
childExpression.type === "SequenceExpression"
23382364
) {
2339-
throw this.raise(
2365+
this.raise(
23402366
startPos,
23412367
`Pipeline body may not be a comma-separated sequence expression`,
23422368
);
@@ -2362,15 +2388,17 @@ export default class ExpressionParser extends LValParser {
23622388
break;
23632389
case "PipelineTopicExpression":
23642390
if (!this.topicReferenceWasUsedInCurrentTopicContext()) {
2365-
throw this.raise(
2391+
this.raise(
23662392
startPos,
23672393
`Pipeline is in topic style but does not use topic reference`,
23682394
);
23692395
}
23702396
bodyNode.expression = childExpression;
23712397
break;
23722398
default:
2373-
throw this.raise(startPos, `Unknown pipeline style ${pipelineStyle}`);
2399+
throw new Error(
2400+
`Internal @babel/parser error: Unknown pipeline style (${pipelineStyle})`,
2401+
);
23742402
}
23752403
return this.finishNode(bodyNode, pipelineStyle);
23762404
}

packages/babel-parser/src/parser/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ export default class Parser extends StatementParser {
3939
const file = this.startNode();
4040
const program = this.startNode();
4141
this.nextToken();
42-
return this.parseTopLevel(file, program);
42+
file.errors = null;
43+
this.parseTopLevel(file, program);
44+
file.errors = this.state.errors;
45+
return file;
4346
}
4447
}
4548

0 commit comments

Comments
 (0)