Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@babel/parser error recovery #10363

Merged
merged 29 commits into from Nov 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c0230cf
Add error recovery support to @babel/parser
nicolo-ribaudo Aug 20, 2019
031a337
Update @babel/parser tests to always recover from errors
nicolo-ribaudo Aug 20, 2019
ff376e2
Update this.raise usage in @babel/parser:
nicolo-ribaudo Aug 20, 2019
b422ffa
Update @babel/parser fixtures with recovered errors
nicolo-ribaudo Aug 20, 2019
43cfec4
Fix tests out of @babel/parser
nicolo-ribaudo Aug 31, 2019
ea3c4e4
Do not use try/catch for control flow
nicolo-ribaudo Aug 22, 2019
44be497
Update invalid fixtures
nicolo-ribaudo Aug 23, 2019
8546a6d
Do not report invalid lhs in toAssignable
nicolo-ribaudo Aug 24, 2019
e2f8b45
Do not validate function id multiple times
nicolo-ribaudo Aug 24, 2019
51d55b5
Dedupe reserved await errors
nicolo-ribaudo Aug 24, 2019
774d669
Remove duplicate errors about strict reserved bindings
nicolo-ribaudo Aug 24, 2019
1ae5ada
Remove duplicated error about yield/await inside params
nicolo-ribaudo Aug 24, 2019
f9a869d
Don't error twice for methods in object patterns
nicolo-ribaudo Aug 24, 2019
86b6275
Don't report invalid super() twice
nicolo-ribaudo Aug 24, 2019
3b44131
Remove dup error about reserved param for expr arrows
nicolo-ribaudo Aug 24, 2019
3497179
Remove double escapes in migrated tests
nicolo-ribaudo Aug 24, 2019
cfc1363
Dedupe errors about invalid escapes in identifiers
nicolo-ribaudo Aug 24, 2019
8316a6a
Remove duplicated error about decorated constructor
nicolo-ribaudo Aug 24, 2019
1eabd28
Remove duplicated error about spread in flow class
nicolo-ribaudo Aug 24, 2019
050ceb4
Don't throw for invalid super usage
nicolo-ribaudo Aug 24, 2019
78e4946
Don't fail for object decorators with stage 2
nicolo-ribaudo Aug 24, 2019
d8c0436
Fix flow inexact type errors
nicolo-ribaudo Aug 24, 2019
a5c1c5c
Fix flow
nicolo-ribaudo Sep 12, 2019
d9a3f2b
Fix errors about escapes in keywords (ref: #10455)
nicolo-ribaudo Oct 22, 2019
9fd7069
Update after rebase
nicolo-ribaudo Nov 5, 2019
04e64e6
Fix todo
nicolo-ribaudo Nov 5, 2019
c3f64a7
Remove duplicated error when using += for defaults
nicolo-ribaudo Nov 5, 2019
7740a45
Remove unnecessary throw
nicolo-ribaudo Nov 5, 2019
ebc2ce0
Nit: use ??
nicolo-ribaudo Nov 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions packages/babel-core/test/fixtures/parse/output.json
Expand Up @@ -2,6 +2,7 @@
"type": "File",
"start": 0,
"end": 91,
"errors": [],
"loc": {
"start": {
"line": 1,
Expand Down
4 changes: 4 additions & 0 deletions packages/babel-parser/src/options.js
Expand Up @@ -21,6 +21,7 @@ export type Options = {
ranges: boolean,
tokens: boolean,
createParenthesizedExpressions: boolean,
errorRecovery: boolean,
};

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

// Interpret and default an options object
Expand Down
138 changes: 83 additions & 55 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -108,6 +108,7 @@ export default class ExpressionParser extends LValParser {
this.unexpected();
}
expr.comments = this.state.comments;
expr.errors = this.state.errors;
return expr;
}

Expand Down Expand Up @@ -786,11 +787,11 @@ export default class ExpressionParser extends LValParser {
if (node.callee.type === "Import") {
if (node.arguments.length !== 1) {
this.raise(node.start, "import() requires exactly one argument");
}

const importArg = node.arguments[0];
if (importArg && importArg.type === "SpreadElement") {
this.raise(importArg.start, "... is not allowed in import()");
} else {
const importArg = node.arguments[0];
if (importArg && importArg.type === "SpreadElement") {
this.raise(importArg.start, "... is not allowed in import()");
}
}
}
return this.finishNode(
Expand Down Expand Up @@ -903,13 +904,6 @@ export default class ExpressionParser extends LValParser {

switch (this.state.type) {
case tt._super:
if (!this.scope.allowSuper && !this.options.allowSuperOutsideMethod) {
this.raise(
this.state.start,
"super is only allowed in object methods and classes",
);
}

node = this.startNode();
this.next();
if (
Expand All @@ -922,14 +916,26 @@ export default class ExpressionParser extends LValParser {
"super() is only valid inside a class constructor of a subclass. " +
"Maybe a typo in the method name ('constructor') or not extending another class?",
);
} else if (
!this.scope.allowSuper &&
!this.options.allowSuperOutsideMethod
) {
this.raise(
node.start,
"super is only allowed in object methods and classes",
);
}

if (
!this.match(tt.parenL) &&
!this.match(tt.bracketL) &&
!this.match(tt.dot)
) {
this.unexpected();
this.raise(
node.start,
"super can only be used with function calls (i.e. super()) or " +
"in property accesses (i.e. super.prop or super[prop])",
);
}

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

this.next();
if (this.primaryTopicReferenceIsAllowedInCurrentTopicContext()) {
this.registerTopicReference();
return this.finishNode(node, "PipelinePrimaryTopicReference");
} else {
throw this.raise(

if (!this.primaryTopicReferenceIsAllowedInCurrentTopicContext()) {
this.raise(
node.start,
`Topic reference was used in a lexical context without topic binding`,
);
}

this.registerTopicReference();
return this.finishNode(node, "PipelinePrimaryTopicReference");
}
}

Expand Down Expand Up @@ -1199,22 +1206,22 @@ export default class ExpressionParser extends LValParser {

if (this.isContextual("meta")) {
this.expectPlugin("importMeta");

if (!this.inModule) {
this.raise(
id.start,
`import.meta may appear only with 'sourceType: "module"'`,
{ code: "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED" },
);
}
this.sawUnambiguousESM = true;
} else if (!this.hasPlugin("importMeta")) {
this.raise(
id.start,
`Dynamic imports require a parameter: import('a.js')`,
);
}

if (!this.inModule) {
this.raise(
id.start,
`import.meta may appear only with 'sourceType: "module"'`,
{ code: "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED" },
);
}
this.sawUnambiguousESM = true;

return this.parseMetaProperty(node, id, "meta");
}

Expand Down Expand Up @@ -1386,7 +1393,10 @@ export default class ExpressionParser extends LValParser {

parseNew(): N.NewExpression | N.MetaProperty {
const node = this.startNode();
const meta = this.parseIdentifier(true);

let meta = this.startNode();
this.next();
meta = this.createIdentifier(meta, "new");

if (this.eat(tt.dot)) {
const metaProp = this.parseMetaProperty(node, meta, "target");
Expand Down Expand Up @@ -1553,12 +1563,12 @@ export default class ExpressionParser extends LValParser {
this.state.start,
"Stage 2 decorators disallow object literal property decorators",
);
} else {
// we needn't check if decorators (stage 0) plugin is enabled since it's checked by
// the call to this.parseDecorator
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
}
}

// we needn't check if decorators (stage 0) plugin is enabled since it's checked by
// the call to this.parseDecorator
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
}
}

Expand Down Expand Up @@ -1933,7 +1943,7 @@ export default class ExpressionParser extends LValParser {

if (isExpression) {
node.body = this.parseMaybeAssign();
this.checkParams(node, false, allowExpression);
this.checkParams(node, false, allowExpression, false);
} else {
const nonSimple = !this.isSimpleParamList(node.params);
if (!oldStrict || nonSimple) {
Expand Down Expand Up @@ -1967,6 +1977,7 @@ export default class ExpressionParser extends LValParser {
node,
!oldStrict && !useStrict && !allowExpression && !isMethod && !nonSimple,
allowExpression,
!oldStrict && useStrict,
);
node.body = this.parseBlock(true, false);
this.state.labels = oldLabels;
Expand All @@ -1975,7 +1986,14 @@ export default class ExpressionParser extends LValParser {
this.state.inParameters = oldInParameters;
// Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval'
if (this.state.strict && node.id) {
this.checkLVal(node.id, BIND_OUTSIDE, undefined, "function name");
this.checkLVal(
node.id,
BIND_OUTSIDE,
undefined,
"function name",
undefined,
!oldStrict && useStrict,
);
}
this.state.strict = oldStrict;
}
Expand All @@ -1994,6 +2012,7 @@ export default class ExpressionParser extends LValParser {
allowDuplicates: boolean,
// eslint-disable-next-line no-unused-vars
isArrowFunction: ?boolean,
strictModeChanged?: boolean = true,
): void {
// $FlowIssue
const nameHash: {} = Object.create(null);
Expand All @@ -2003,6 +2022,8 @@ export default class ExpressionParser extends LValParser {
BIND_VAR,
allowDuplicates ? null : nameHash,
"function parameter list",
undefined,
strictModeChanged,
);
}
}
Expand Down Expand Up @@ -2084,6 +2105,8 @@ export default class ExpressionParser extends LValParser {
// Parse the next token as an identifier. If `liberal` is true (used
// when parsing properties), it will also convert keywords into
// identifiers.
// This shouldn't be used to parse the keywords of meta properties, since they
// are not identifiers and cannot contain escape sequences.

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

if (this.match(tt.name)) {
name = this.state.value;

// An escaped identifier whose value is the same as a keyword
if (!liberal && this.state.containsEsc && isKeyword(name)) {
this.raise(this.state.pos, `Escape sequence in keyword ${name}`);
}
} else if (this.state.type.keyword) {
name = this.state.type.keyword;

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

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

if (word === "await") {
Expand All @@ -2161,7 +2184,9 @@ export default class ExpressionParser extends LValParser {
startLoc,
"Can not use 'await' as identifier inside an async function",
);
} else if (
return;
}
if (
this.state.awaitPos === -1 &&
(this.state.maybeInArrowParameters || this.isAwaitAllowed())
) {
Expand All @@ -2174,9 +2199,11 @@ export default class ExpressionParser extends LValParser {
startLoc,
"'arguments' is not allowed in class field initializer",
);
return;
}
if (checkKeywords && isKeyword(word)) {
this.raise(startLoc, `Unexpected keyword '${word}'`);
return;
}

const reservedTest = !this.state.strict
Expand All @@ -2191,8 +2218,9 @@ export default class ExpressionParser extends LValParser {
startLoc,
"Can not use keyword 'await' outside an async function",
);
} else {
this.raise(startLoc, `Unexpected reserved word '${word}'`);
}
this.raise(startLoc, `Unexpected reserved word '${word}'`);
}
}

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

parseAwait(): N.AwaitExpression {
if (this.state.awaitPos === -1) {
this.state.awaitPos = this.state.start;
}
const node = this.startNode();

this.next();
Expand All @@ -2218,8 +2243,10 @@ export default class ExpressionParser extends LValParser {
node.start,
"await is not allowed in async function parameters",
);
} else if (this.state.awaitPos === -1) {
this.state.awaitPos = node.start;
}
if (this.match(tt.star)) {
if (this.eat(tt.star)) {
this.raise(
node.start,
"await* has been removed from the async functions proposal. Use Promise.all() instead.",
Expand Down Expand Up @@ -2259,13 +2286,12 @@ export default class ExpressionParser extends LValParser {
// Parses yield expression inside generator.

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

if (this.state.inParameters) {
this.raise(node.start, "yield is not allowed in generator parameters");
} else if (this.state.yieldPos === -1) {
this.state.yieldPos = node.start;
}

this.next();
Expand All @@ -2291,7 +2317,7 @@ export default class ExpressionParser extends LValParser {
if (left.type === "SequenceExpression") {
// Ensure that the pipeline head is not a comma-delimited
// sequence expression.
throw this.raise(
this.raise(
leftStartPos,
`Pipeline head should not be a comma-separated sequence expression`,
);
Expand Down Expand Up @@ -2336,7 +2362,7 @@ export default class ExpressionParser extends LValParser {
pipelineStyle === "PipelineTopicExpression" &&
childExpression.type === "SequenceExpression"
) {
throw this.raise(
this.raise(
startPos,
`Pipeline body may not be a comma-separated sequence expression`,
);
Expand All @@ -2362,15 +2388,17 @@ export default class ExpressionParser extends LValParser {
break;
case "PipelineTopicExpression":
if (!this.topicReferenceWasUsedInCurrentTopicContext()) {
throw this.raise(
this.raise(
startPos,
`Pipeline is in topic style but does not use topic reference`,
);
}
bodyNode.expression = childExpression;
break;
default:
throw this.raise(startPos, `Unknown pipeline style ${pipelineStyle}`);
throw new Error(
`Internal @babel/parser error: Unknown pipeline style (${pipelineStyle})`,
);
}
return this.finishNode(bodyNode, pipelineStyle);
}
Expand Down
5 changes: 4 additions & 1 deletion packages/babel-parser/src/parser/index.js
Expand Up @@ -39,7 +39,10 @@ export default class Parser extends StatementParser {
const file = this.startNode();
const program = this.startNode();
this.nextToken();
return this.parseTopLevel(file, program);
file.errors = null;
JLHwung marked this conversation as resolved.
Show resolved Hide resolved
this.parseTopLevel(file, program);
file.errors = this.state.errors;
return file;
}
}

Expand Down