@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 ??
This commit is contained in:
@@ -108,6 +108,7 @@ export default class ExpressionParser extends LValParser {
|
||||
this.unexpected();
|
||||
}
|
||||
expr.comments = this.state.comments;
|
||||
expr.errors = this.state.errors;
|
||||
return expr;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
@@ -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 (
|
||||
@@ -922,6 +916,14 @@ 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 (
|
||||
@@ -929,7 +931,11 @@ export default class ExpressionParser extends LValParser {
|
||||
!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");
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1199,6 +1206,15 @@ 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,
|
||||
@@ -1206,15 +1222,6 @@ export default class ExpressionParser extends LValParser {
|
||||
);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -2003,6 +2022,8 @@ export default class ExpressionParser extends LValParser {
|
||||
BIND_VAR,
|
||||
allowDuplicates ? null : nameHash,
|
||||
"function parameter list",
|
||||
undefined,
|
||||
strictModeChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
@@ -2153,6 +2175,7 @@ export default class ExpressionParser extends LValParser {
|
||||
startLoc,
|
||||
"Can not use 'yield' as identifier inside a generator",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (word === "await") {
|
||||
@@ -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())
|
||||
) {
|
||||
@@ -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
|
||||
@@ -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}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -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.",
|
||||
@@ -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();
|
||||
@@ -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`,
|
||||
);
|
||||
@@ -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`,
|
||||
);
|
||||
@@ -2362,7 +2388,7 @@ 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`,
|
||||
);
|
||||
@@ -2370,7 +2396,9 @@ export default class ExpressionParser extends LValParser {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
this.parseTopLevel(file, program);
|
||||
file.errors = this.state.errors;
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import CommentsParser from "./comments";
|
||||
// message.
|
||||
|
||||
export default class LocationParser extends CommentsParser {
|
||||
+isLookahead: boolean;
|
||||
|
||||
getLocationForPosition(pos: number): Position {
|
||||
let loc;
|
||||
if (pos === this.state.start) loc = this.state.startLoc;
|
||||
@@ -31,7 +33,7 @@ export default class LocationParser extends CommentsParser {
|
||||
missingPluginNames?: Array<string>,
|
||||
code?: string,
|
||||
} = {},
|
||||
): empty {
|
||||
): Error | empty {
|
||||
const loc = this.getLocationForPosition(pos);
|
||||
|
||||
message += ` (${loc.line}:${loc.column})`;
|
||||
@@ -47,6 +49,12 @@ export default class LocationParser extends CommentsParser {
|
||||
if (code !== undefined) {
|
||||
err.code = code;
|
||||
}
|
||||
throw err;
|
||||
|
||||
if (this.options.errorRecovery) {
|
||||
if (!this.isLookahead) this.state.errors.push(err);
|
||||
return err;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@ import type {
|
||||
SpreadElement,
|
||||
} from "../types";
|
||||
import type { Pos, Position } from "../util/location";
|
||||
import { isStrictBindReservedWord } from "../util/identifier";
|
||||
import {
|
||||
isStrictBindOnlyReservedWord,
|
||||
isStrictBindReservedWord,
|
||||
} from "../util/identifier";
|
||||
import { NodeUtils } from "./node";
|
||||
import { type BindingTypes, BIND_NONE } from "../util/scopeflags";
|
||||
|
||||
@@ -37,6 +40,8 @@ export default class LValParser extends NodeUtils {
|
||||
|
||||
// Convert existing expression atom to assignable pattern
|
||||
// if possible.
|
||||
// NOTE: There is a corresponding "isAssignable" method in flow.js.
|
||||
// When this one is updated, please check if also that one needs to be updated.
|
||||
|
||||
toAssignable(
|
||||
node: Node,
|
||||
@@ -96,15 +101,16 @@ export default class LValParser extends NodeUtils {
|
||||
break;
|
||||
|
||||
case "AssignmentExpression":
|
||||
if (node.operator === "=") {
|
||||
node.type = "AssignmentPattern";
|
||||
delete node.operator;
|
||||
} else {
|
||||
if (node.operator !== "=") {
|
||||
this.raise(
|
||||
node.left.end,
|
||||
"Only '=' operator can be used for specifying default value.",
|
||||
);
|
||||
}
|
||||
|
||||
node.type = "AssignmentPattern";
|
||||
delete node.operator;
|
||||
this.toAssignable(node.left, isBinding, contextDescription);
|
||||
break;
|
||||
|
||||
case "ParenthesizedExpression":
|
||||
@@ -118,14 +124,9 @@ export default class LValParser extends NodeUtils {
|
||||
case "MemberExpression":
|
||||
if (!isBinding) break;
|
||||
|
||||
default: {
|
||||
const message =
|
||||
"Invalid left-hand side" +
|
||||
(contextDescription
|
||||
? " in " + contextDescription
|
||||
: /* istanbul ignore next */ "expression");
|
||||
this.raise(node.start, message);
|
||||
}
|
||||
default:
|
||||
// We don't know how to deal with this node. It will
|
||||
// be reported by a later call to checkLVal
|
||||
}
|
||||
}
|
||||
return node;
|
||||
@@ -349,12 +350,18 @@ export default class LValParser extends NodeUtils {
|
||||
checkClashes: ?{ [key: string]: boolean },
|
||||
contextDescription: string,
|
||||
disallowLetBinding?: boolean,
|
||||
strictModeChanged?: boolean = false,
|
||||
): void {
|
||||
switch (expr.type) {
|
||||
case "Identifier":
|
||||
if (
|
||||
this.state.strict &&
|
||||
isStrictBindReservedWord(expr.name, this.inModule)
|
||||
// "Global" reserved words have already been checked by parseIdentifier,
|
||||
// unless they have been found in the id or parameters of a strict-mode
|
||||
// function in a sloppy context.
|
||||
(strictModeChanged
|
||||
? isStrictBindReservedWord(expr.name, this.inModule)
|
||||
: isStrictBindOnlyReservedWord(expr.name))
|
||||
) {
|
||||
this.raise(
|
||||
expr.start,
|
||||
@@ -404,6 +411,11 @@ export default class LValParser extends NodeUtils {
|
||||
case "ObjectPattern":
|
||||
for (let prop of expr.properties) {
|
||||
if (prop.type === "ObjectProperty") prop = prop.value;
|
||||
// If we find here an ObjectMethod, it's because this was originally
|
||||
// an ObjectExpression which has then been converted.
|
||||
// toAssignable already reported this error with a nicer message.
|
||||
else if (prop.type === "ObjectMethod") continue;
|
||||
|
||||
this.checkLVal(
|
||||
prop,
|
||||
bindingType,
|
||||
@@ -489,7 +501,7 @@ export default class LValParser extends NodeUtils {
|
||||
}
|
||||
|
||||
raiseRestNotLast(pos: number) {
|
||||
this.raise(pos, `Rest element must be last element`);
|
||||
throw this.raise(pos, `Rest element must be last element`);
|
||||
}
|
||||
|
||||
raiseTrailingCommaAfterRest(pos: number) {
|
||||
|
||||
@@ -203,7 +203,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
case tt._var:
|
||||
kind = kind || this.state.value;
|
||||
if (context && kind !== "var") {
|
||||
this.unexpected(
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"Lexical declaration cannot appear in a single-statement context",
|
||||
);
|
||||
@@ -269,8 +269,8 @@ export default class StatementParser extends ExpressionParser {
|
||||
default: {
|
||||
if (this.isAsyncFunction()) {
|
||||
if (context) {
|
||||
this.unexpected(
|
||||
null,
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"Async functions can only be declared at the top level or inside a block",
|
||||
);
|
||||
}
|
||||
@@ -351,7 +351,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
);
|
||||
}
|
||||
} else if (!this.canHaveLeadingDecorator()) {
|
||||
this.raise(
|
||||
throw this.raise(
|
||||
this.state.start,
|
||||
"Leading decorators must be attached to a class declaration",
|
||||
);
|
||||
@@ -1036,7 +1036,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
this.initFunction(node, isAsync);
|
||||
|
||||
if (this.match(tt.star) && isHangingStatement) {
|
||||
this.unexpected(
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"Generators can only be declared at the top level or inside a block",
|
||||
);
|
||||
@@ -1077,10 +1077,10 @@ export default class StatementParser extends ExpressionParser {
|
||||
this.scope.exit();
|
||||
|
||||
if (isStatement && !isHangingStatement) {
|
||||
// We need to validate this _after_ parsing the function body
|
||||
// We need to register this _after_ parsing the function body
|
||||
// because of TypeScript body-less function declarations,
|
||||
// which shouldn't be added to the scope.
|
||||
this.checkFunctionStatementId(node);
|
||||
this.registerFunctionStatementId(node);
|
||||
}
|
||||
|
||||
this.state.maybeInArrowParameters = oldMaybeInArrowParameters;
|
||||
@@ -1111,22 +1111,21 @@ export default class StatementParser extends ExpressionParser {
|
||||
this.checkYieldAwaitInDefaultParams();
|
||||
}
|
||||
|
||||
checkFunctionStatementId(node: N.Function): void {
|
||||
registerFunctionStatementId(node: N.Function): void {
|
||||
if (!node.id) return;
|
||||
|
||||
// If it is a regular function declaration in sloppy mode, then it is
|
||||
// subject to Annex B semantics (BIND_FUNCTION). Otherwise, the binding
|
||||
// mode depends on properties of the current scope (see
|
||||
// treatFunctionsAsVar).
|
||||
this.checkLVal(
|
||||
node.id,
|
||||
this.scope.declareName(
|
||||
node.id.name,
|
||||
this.state.strict || node.generator || node.async
|
||||
? this.scope.treatFunctionsAsVar
|
||||
? BIND_VAR
|
||||
: BIND_LEXICAL
|
||||
: BIND_FUNCTION,
|
||||
null,
|
||||
"function name",
|
||||
node.id.start,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1191,7 +1190,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
while (!this.eat(tt.braceR)) {
|
||||
if (this.eat(tt.semi)) {
|
||||
if (decorators.length > 0) {
|
||||
this.raise(
|
||||
throw this.raise(
|
||||
this.state.lastTokEnd,
|
||||
"Decorators must not be followed by a semicolon",
|
||||
);
|
||||
@@ -1229,7 +1228,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
});
|
||||
|
||||
if (decorators.length) {
|
||||
this.raise(
|
||||
throw this.raise(
|
||||
this.state.start,
|
||||
"You have trailing decorators with no method",
|
||||
);
|
||||
@@ -1362,13 +1361,6 @@ export default class StatementParser extends ExpressionParser {
|
||||
if (isConstructor) {
|
||||
publicMethod.kind = "constructor";
|
||||
|
||||
if (publicMethod.decorators) {
|
||||
this.raise(
|
||||
publicMethod.start,
|
||||
"You can't attach decorators to a class constructor",
|
||||
);
|
||||
}
|
||||
|
||||
// TypeScript allows multiple overloaded constructor declarations.
|
||||
if (state.hadConstructor && !this.hasPlugin("typescript")) {
|
||||
this.raise(key.start, "Duplicate constructor in the same class");
|
||||
@@ -1797,7 +1789,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
this.hasPlugin("decorators") &&
|
||||
this.getPluginOption("decorators", "decoratorsBeforeExport")
|
||||
) {
|
||||
this.unexpected(
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"Decorators must be placed *before* the 'export' keyword." +
|
||||
" You can set the 'decoratorsBeforeExport' option to false to use" +
|
||||
@@ -1807,7 +1799,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
this.parseDecorators(false);
|
||||
return this.parseClass(expr, true, true);
|
||||
} else if (this.match(tt._const) || this.match(tt._var) || this.isLet()) {
|
||||
return this.raise(
|
||||
throw this.raise(
|
||||
this.state.start,
|
||||
"Only expressions, functions or classes are allowed as the `default` export.",
|
||||
);
|
||||
@@ -1977,7 +1969,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
name: string,
|
||||
): void {
|
||||
if (this.state.exportedIdentifiers.indexOf(name) > -1) {
|
||||
throw this.raise(
|
||||
this.raise(
|
||||
node.start,
|
||||
name === "default"
|
||||
? "Only one default export allowed per module."
|
||||
@@ -2098,8 +2090,8 @@ export default class StatementParser extends ExpressionParser {
|
||||
} else {
|
||||
// Detect an attempt to deep destructure
|
||||
if (this.eat(tt.colon)) {
|
||||
this.unexpected(
|
||||
null,
|
||||
throw this.raise(
|
||||
this.state.start,
|
||||
"ES2015 named imports do not destructure. " +
|
||||
"Use another statement for destructuring after the import.",
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { types as tt, type TokenType } from "../tokenizer/types";
|
||||
import Tokenizer from "../tokenizer";
|
||||
import State from "../tokenizer/state";
|
||||
import type { Node } from "../types";
|
||||
import { lineBreak, skipWhiteSpace } from "../util/whitespace";
|
||||
import { isIdentifierChar } from "../util/identifier";
|
||||
@@ -9,6 +10,14 @@ import * as charCodes from "charcodes";
|
||||
|
||||
const literal = /^('|")((?:\\?.)*?)\1/;
|
||||
|
||||
type TryParse<Node, Error, Thrown, Aborted, FailState> = {
|
||||
node: Node,
|
||||
error: Error,
|
||||
thrown: Thrown,
|
||||
aborted: Aborted,
|
||||
failState: FailState,
|
||||
};
|
||||
|
||||
// ## Parser utilities
|
||||
|
||||
export default class UtilParser extends Tokenizer {
|
||||
@@ -215,4 +224,58 @@ export default class UtilParser extends Tokenizer {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// tryParse will clone parser state.
|
||||
// It is expensive and should be used with cautions
|
||||
tryParse<T: Node | $ReadOnlyArray<Node>>(
|
||||
fn: (abort: (node?: T) => empty) => T,
|
||||
oldState: State = this.state.clone(),
|
||||
):
|
||||
| TryParse<T, null, false, false, null>
|
||||
| TryParse<T | null, SyntaxError, boolean, false, State>
|
||||
| TryParse<T | null, null, false, true, State> {
|
||||
const abortSignal: { node: T | null } = { node: null };
|
||||
try {
|
||||
const node = fn((node = null) => {
|
||||
abortSignal.node = node;
|
||||
throw abortSignal;
|
||||
});
|
||||
if (this.state.errors.length > oldState.errors.length) {
|
||||
const failState = this.state;
|
||||
this.state = oldState;
|
||||
return {
|
||||
node,
|
||||
error: (failState.errors[oldState.errors.length]: SyntaxError),
|
||||
thrown: false,
|
||||
aborted: false,
|
||||
failState,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
node,
|
||||
error: null,
|
||||
thrown: false,
|
||||
aborted: false,
|
||||
failState: null,
|
||||
};
|
||||
} catch (error) {
|
||||
const failState = this.state;
|
||||
this.state = oldState;
|
||||
if (error instanceof SyntaxError) {
|
||||
return { node: null, error, thrown: true, aborted: false, failState };
|
||||
}
|
||||
if (error === abortSignal) {
|
||||
return {
|
||||
node: abortSignal.node,
|
||||
error: null,
|
||||
thrown: false,
|
||||
aborted: true,
|
||||
failState,
|
||||
};
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user