@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:
@@ -93,9 +93,10 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
} else {
|
||||
this.raise(start, "setter must have exactly one formal parameter");
|
||||
}
|
||||
}
|
||||
|
||||
if (prop.kind === "set" && prop.value.params[0].type === "RestElement") {
|
||||
} else if (
|
||||
prop.kind === "set" &&
|
||||
prop.value.params[0].type === "RestElement"
|
||||
) {
|
||||
this.raise(
|
||||
start,
|
||||
"setter function argument must not be a rest parameter",
|
||||
@@ -382,12 +383,15 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
isLast: boolean,
|
||||
) {
|
||||
if (prop.kind === "get" || prop.kind === "set") {
|
||||
this.raise(
|
||||
throw this.raise(
|
||||
prop.key.start,
|
||||
"Object pattern can't contain getter or setter",
|
||||
);
|
||||
} else if (prop.method) {
|
||||
this.raise(prop.key.start, "Object pattern can't contain methods");
|
||||
throw this.raise(
|
||||
prop.key.start,
|
||||
"Object pattern can't contain methods",
|
||||
);
|
||||
} else {
|
||||
super.toAssignableObjectExpressionProp(prop, isBinding, isLast);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
/*:: declare var invariant; */
|
||||
|
||||
import type Parser from "../parser";
|
||||
import { types as tt, type TokenType } from "../tokenizer/types";
|
||||
import * as N from "../types";
|
||||
@@ -263,7 +265,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return this.flowParseDeclareModuleExports(node);
|
||||
} else {
|
||||
if (insideModule) {
|
||||
this.unexpected(
|
||||
this.raise(
|
||||
this.state.lastTokStart,
|
||||
"`declare module` cannot be used inside another `declare module`",
|
||||
);
|
||||
@@ -313,7 +315,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
if (this.match(tt._import)) {
|
||||
this.next();
|
||||
if (!this.isContextual("type") && !this.match(tt._typeof)) {
|
||||
this.unexpected(
|
||||
this.raise(
|
||||
this.state.lastTokStart,
|
||||
"Imports within a `declare module` body must always be `import type` or `import typeof`",
|
||||
);
|
||||
@@ -345,17 +347,17 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
body.forEach(bodyElement => {
|
||||
if (isEsModuleType(bodyElement)) {
|
||||
if (kind === "CommonJS") {
|
||||
this.unexpected(bodyElement.start, errorMessage);
|
||||
this.raise(bodyElement.start, errorMessage);
|
||||
}
|
||||
kind = "ES";
|
||||
} else if (bodyElement.type === "DeclareModuleExports") {
|
||||
if (hasModuleExport) {
|
||||
this.unexpected(
|
||||
this.raise(
|
||||
bodyElement.start,
|
||||
"Duplicate `declare module.exports` statement",
|
||||
);
|
||||
}
|
||||
if (kind === "ES") this.unexpected(bodyElement.start, errorMessage);
|
||||
if (kind === "ES") this.raise(bodyElement.start, errorMessage);
|
||||
kind = "CommonJS";
|
||||
hasModuleExport = true;
|
||||
}
|
||||
@@ -548,8 +550,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
checkNotUnderscore(word: string) {
|
||||
if (word === "_") {
|
||||
throw this.unexpected(
|
||||
null,
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"`_` is only allowed as a type argument to call or new",
|
||||
);
|
||||
}
|
||||
@@ -632,7 +634,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
node.default = this.flowParseType();
|
||||
} else {
|
||||
if (requireDefault) {
|
||||
this.unexpected(
|
||||
this.raise(
|
||||
nodeStart,
|
||||
// eslint-disable-next-line max-len
|
||||
"Type parameter declaration needs a default, since a preceding type parameter declaration has a default.",
|
||||
@@ -878,6 +880,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
while (!this.match(endDelim)) {
|
||||
let isStatic = false;
|
||||
let protoStart: ?number = null;
|
||||
let inexactStart: ?number = null;
|
||||
const node = this.startNode();
|
||||
|
||||
if (allowProto && this.isContextual("proto")) {
|
||||
@@ -950,17 +953,29 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
variance,
|
||||
kind,
|
||||
allowSpread,
|
||||
allowInexact,
|
||||
allowInexact ?? !exact,
|
||||
);
|
||||
|
||||
if (propOrInexact === null) {
|
||||
inexact = true;
|
||||
inexactStart = this.state.lastTokStart;
|
||||
} else {
|
||||
nodeStart.properties.push(propOrInexact);
|
||||
}
|
||||
}
|
||||
|
||||
this.flowObjectTypeSemicolon();
|
||||
|
||||
if (
|
||||
inexactStart &&
|
||||
!this.match(tt.braceR) &&
|
||||
!this.match(tt.braceBarR)
|
||||
) {
|
||||
this.raise(
|
||||
inexactStart,
|
||||
"Explicit inexact syntax must appear at the end of an inexact object",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.expect(endDelim);
|
||||
@@ -990,10 +1005,38 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
allowSpread: boolean,
|
||||
allowInexact: boolean,
|
||||
): (N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty) | null {
|
||||
if (this.match(tt.ellipsis)) {
|
||||
if (this.eat(tt.ellipsis)) {
|
||||
const isInexactToken =
|
||||
this.match(tt.comma) ||
|
||||
this.match(tt.semi) ||
|
||||
this.match(tt.braceR) ||
|
||||
this.match(tt.braceBarR);
|
||||
|
||||
if (isInexactToken) {
|
||||
if (!allowSpread) {
|
||||
this.raise(
|
||||
this.state.lastTokStart,
|
||||
"Explicit inexact syntax cannot appear in class or interface definitions",
|
||||
);
|
||||
} else if (!allowInexact) {
|
||||
this.raise(
|
||||
this.state.lastTokStart,
|
||||
"Explicit inexact syntax cannot appear inside an explicit exact object type",
|
||||
);
|
||||
}
|
||||
if (variance) {
|
||||
this.raise(
|
||||
variance.start,
|
||||
"Explicit inexact syntax cannot have variance",
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!allowSpread) {
|
||||
this.unexpected(
|
||||
null,
|
||||
this.raise(
|
||||
this.state.lastTokStart,
|
||||
"Spread operator cannot appear in class or interface definitions",
|
||||
);
|
||||
}
|
||||
@@ -1001,35 +1044,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
this.unexpected(protoStart);
|
||||
}
|
||||
if (variance) {
|
||||
this.unexpected(
|
||||
variance.start,
|
||||
"Spread properties cannot have variance",
|
||||
);
|
||||
}
|
||||
this.expect(tt.ellipsis);
|
||||
const isInexactToken = this.eat(tt.comma) || this.eat(tt.semi);
|
||||
|
||||
if (this.match(tt.braceR)) {
|
||||
if (allowInexact) return null;
|
||||
this.unexpected(
|
||||
null,
|
||||
"Explicit inexact syntax is only allowed inside inexact objects",
|
||||
);
|
||||
this.raise(variance.start, "Spread properties cannot have variance");
|
||||
}
|
||||
|
||||
if (this.match(tt.braceBarR)) {
|
||||
this.unexpected(
|
||||
null,
|
||||
"Explicit inexact syntax cannot appear inside an explicit exact object type",
|
||||
);
|
||||
}
|
||||
|
||||
if (isInexactToken) {
|
||||
this.unexpected(
|
||||
null,
|
||||
"Explicit inexact syntax must appear at the end of an inexact object",
|
||||
);
|
||||
}
|
||||
node.argument = this.flowParseType();
|
||||
return this.finishNode(node, "ObjectTypeSpreadProperty");
|
||||
} else {
|
||||
@@ -1400,8 +1417,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
);
|
||||
}
|
||||
|
||||
this.unexpected(
|
||||
null,
|
||||
throw this.raise(
|
||||
this.state.start,
|
||||
`Unexpected token, expected "number" or "bigint"`,
|
||||
);
|
||||
}
|
||||
@@ -1720,23 +1737,23 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
): N.Expression {
|
||||
if (!this.match(tt.question)) return expr;
|
||||
|
||||
// only do the expensive clone if there is a question mark
|
||||
// only use the expensive "tryParse" method if there is a question mark
|
||||
// and if we come from inside parens
|
||||
if (refNeedsArrowPos) {
|
||||
const state = this.state.clone();
|
||||
try {
|
||||
return super.parseConditional(expr, noIn, startPos, startLoc);
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
this.state = state;
|
||||
refNeedsArrowPos.start = err.pos || this.state.start;
|
||||
return expr;
|
||||
} else {
|
||||
// istanbul ignore next: no such error is expected
|
||||
throw err;
|
||||
}
|
||||
const result = this.tryParse(() =>
|
||||
super.parseConditional(expr, noIn, startPos, startLoc),
|
||||
);
|
||||
|
||||
if (!result.node) {
|
||||
// $FlowIgnore
|
||||
refNeedsArrowPos.start = result.error.pos || this.state.start;
|
||||
return expr;
|
||||
}
|
||||
|
||||
if (result.error) this.state = result.failState;
|
||||
return result.node;
|
||||
}
|
||||
|
||||
this.expect(tt.question);
|
||||
const state = this.state.clone();
|
||||
const originalNoArrowAt = this.state.noArrowAt;
|
||||
@@ -1776,10 +1793,10 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
this.state.noArrowAt = noArrowAt.concat(valid[0].start);
|
||||
({ consequent, failed } = this.tryParseConditionalConsequent());
|
||||
}
|
||||
|
||||
this.getArrowLikeExpressions(consequent, true);
|
||||
}
|
||||
|
||||
this.getArrowLikeExpressions(consequent, true);
|
||||
|
||||
this.state.noArrowAt = originalNoArrowAt;
|
||||
this.expect(tt.colon);
|
||||
|
||||
@@ -1825,19 +1842,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
if (node.type === "ArrowFunctionExpression") {
|
||||
if (node.typeParameters || !node.returnType) {
|
||||
// This is an arrow expression without ambiguity, so check its parameters
|
||||
this.toAssignableList(
|
||||
// node.params is Expression[] instead of $ReadOnlyArray<Pattern> because it
|
||||
// has not been converted yet.
|
||||
((node.params: any): N.Expression[]),
|
||||
true,
|
||||
"arrow function parameters",
|
||||
node.extra?.trailingComma,
|
||||
);
|
||||
// Enter scope, as checkParams defines bindings
|
||||
this.scope.enter(functionFlags(false, false) | SCOPE_ARROW);
|
||||
// Use super's method to force the parameters to be checked
|
||||
super.checkParams(node, false, true);
|
||||
this.scope.exit();
|
||||
this.finishArrowValidation(node);
|
||||
} else {
|
||||
arrows.push(node);
|
||||
}
|
||||
@@ -1849,30 +1854,29 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
}
|
||||
|
||||
if (disallowInvalid) {
|
||||
for (let i = 0; i < arrows.length; i++) {
|
||||
this.toAssignableList(
|
||||
((node.params: any): N.Expression[]),
|
||||
true,
|
||||
"arrow function parameters",
|
||||
node.extra?.trailingComma,
|
||||
);
|
||||
}
|
||||
arrows.forEach(node => this.finishArrowValidation(node));
|
||||
return [arrows, []];
|
||||
}
|
||||
|
||||
return partition(arrows, node => {
|
||||
try {
|
||||
this.toAssignableList(
|
||||
((node.params: any): N.Expression[]),
|
||||
true,
|
||||
"arrow function parameters",
|
||||
node.extra?.trailingComma,
|
||||
);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return partition(arrows, node =>
|
||||
node.params.every(param => this.isAssignable(param, true)),
|
||||
);
|
||||
}
|
||||
|
||||
finishArrowValidation(node: N.ArrowFunctionExpression) {
|
||||
this.toAssignableList(
|
||||
// node.params is Expression[] instead of $ReadOnlyArray<Pattern> because it
|
||||
// has not been converted yet.
|
||||
((node.params: any): N.Expression[]),
|
||||
true,
|
||||
"arrow function parameters",
|
||||
node.extra?.trailingComma,
|
||||
);
|
||||
// Enter scope, as checkParams defines bindings
|
||||
this.scope.enter(functionFlags(false, false) | SCOPE_ARROW);
|
||||
// Use super's method to force the parameters to be checked
|
||||
super.checkParams(node, false, true);
|
||||
this.scope.exit();
|
||||
}
|
||||
|
||||
forwardNoArrowParamsConversionAt<T>(node: N.Node, parse: () => T): T {
|
||||
@@ -2025,6 +2029,49 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
}
|
||||
}
|
||||
|
||||
isAssignable(node: N.Node, isBinding?: boolean): boolean {
|
||||
switch (node.type) {
|
||||
case "Identifier":
|
||||
case "ObjectPattern":
|
||||
case "ArrayPattern":
|
||||
case "AssignmentPattern":
|
||||
return true;
|
||||
|
||||
case "ObjectExpression": {
|
||||
const last = node.properties.length - 1;
|
||||
return node.properties.every((prop, i) => {
|
||||
return (
|
||||
prop.type !== "ObjectMethod" &&
|
||||
(i === last || prop.type === "SpreadElement") &&
|
||||
this.isAssignable(prop)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
case "ObjectProperty":
|
||||
return this.isAssignable(node.value);
|
||||
|
||||
case "SpreadElement":
|
||||
return this.isAssignable(node.argument);
|
||||
|
||||
case "ArrayExpression":
|
||||
return node.elements.every(element => this.isAssignable(element));
|
||||
|
||||
case "AssignmentExpression":
|
||||
return node.operator === "=";
|
||||
|
||||
case "ParenthesizedExpression":
|
||||
return this.isAssignable(node.expression);
|
||||
|
||||
case "MemberExpression":
|
||||
case "OptionalMemberExpression":
|
||||
return !isBinding;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
toAssignable(
|
||||
node: N.Node,
|
||||
isBinding: ?boolean,
|
||||
@@ -2253,13 +2300,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
parseAssignableListItemTypes(param: N.Pattern): N.Pattern {
|
||||
if (this.eat(tt.question)) {
|
||||
if (param.type !== "Identifier") {
|
||||
throw this.raise(
|
||||
this.raise(
|
||||
param.start,
|
||||
"A binding pattern parameter cannot be optional in an implementation signature.",
|
||||
);
|
||||
}
|
||||
|
||||
param.optional = true;
|
||||
((param: any): N.Identifier).optional = true;
|
||||
}
|
||||
if (this.match(tt.colon)) {
|
||||
param.typeAnnotation = this.flowParseTypeAnnotation();
|
||||
@@ -2490,45 +2537,50 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
afterLeftParse?: Function,
|
||||
refNeedsArrowPos?: ?Pos,
|
||||
): N.Expression {
|
||||
let jsxError = null;
|
||||
let state = null;
|
||||
|
||||
let jsx;
|
||||
|
||||
if (
|
||||
this.hasPlugin("jsx") &&
|
||||
(this.match(tt.jsxTagStart) || this.isRelational("<"))
|
||||
) {
|
||||
const state = this.state.clone();
|
||||
try {
|
||||
return super.parseMaybeAssign(
|
||||
noIn,
|
||||
refShorthandDefaultPos,
|
||||
afterLeftParse,
|
||||
refNeedsArrowPos,
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
this.state = state;
|
||||
state = this.state.clone();
|
||||
|
||||
// Remove `tc.j_expr` and `tc.j_oTag` from context added
|
||||
// by parsing `jsxTagStart` to stop the JSX plugin from
|
||||
// messing with the tokens
|
||||
const cLength = this.state.context.length;
|
||||
if (this.state.context[cLength - 1] === tc.j_oTag) {
|
||||
this.state.context.length -= 2;
|
||||
}
|
||||
jsx = this.tryParse(
|
||||
() =>
|
||||
super.parseMaybeAssign(
|
||||
noIn,
|
||||
refShorthandDefaultPos,
|
||||
afterLeftParse,
|
||||
refNeedsArrowPos,
|
||||
),
|
||||
state,
|
||||
);
|
||||
/*:: invariant(!jsx.aborted) */
|
||||
|
||||
jsxError = err;
|
||||
} else {
|
||||
// istanbul ignore next: no such error is expected
|
||||
throw err;
|
||||
}
|
||||
if (!jsx.error) return jsx.node;
|
||||
|
||||
// Remove `tc.j_expr` and `tc.j_oTag` from context added
|
||||
// by parsing `jsxTagStart` to stop the JSX plugin from
|
||||
// messing with the tokens
|
||||
const { context } = this.state;
|
||||
if (context[context.length - 1] === tc.j_oTag) {
|
||||
context.length -= 2;
|
||||
} else if (context[context.length - 1] === tc.j_expr) {
|
||||
context.length -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (jsxError != null || this.isRelational("<")) {
|
||||
let arrowExpression;
|
||||
if ((jsx && jsx.error) || this.isRelational("<")) {
|
||||
state = state || this.state.clone();
|
||||
|
||||
let typeParameters;
|
||||
try {
|
||||
|
||||
const arrow = this.tryParse(() => {
|
||||
typeParameters = this.flowParseTypeParameterDeclaration();
|
||||
arrowExpression = this.forwardNoArrowParamsConversionAt(
|
||||
|
||||
const arrowExpression = this.forwardNoArrowParamsConversionAt(
|
||||
typeParameters,
|
||||
() =>
|
||||
super.parseMaybeAssign(
|
||||
@@ -2540,20 +2592,43 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
);
|
||||
arrowExpression.typeParameters = typeParameters;
|
||||
this.resetStartLocationFromNode(arrowExpression, typeParameters);
|
||||
} catch (err) {
|
||||
throw jsxError || err;
|
||||
|
||||
return arrowExpression;
|
||||
}, state);
|
||||
|
||||
const arrowExpression: ?N.ArrowFunctionExpression =
|
||||
arrow.node && arrow.node.type === "ArrowFunctionExpression"
|
||||
? arrow.node
|
||||
: null;
|
||||
|
||||
if (!arrow.error && arrowExpression) return arrowExpression;
|
||||
|
||||
// If we are here, both JSX and Flow parsing attemps failed.
|
||||
// Give the precedence to the JSX error, except if JSX had an
|
||||
// unrecoverable error while Flow didn't.
|
||||
// If the error is recoverable, we can only re-report it if there is
|
||||
// a node we can return.
|
||||
|
||||
if (jsx && jsx.node) {
|
||||
/*:: invariant(jsx.failState) */
|
||||
this.state = jsx.failState;
|
||||
return jsx.node;
|
||||
}
|
||||
|
||||
if (arrowExpression.type === "ArrowFunctionExpression") {
|
||||
if (arrowExpression) {
|
||||
/*:: invariant(arrow.failState) */
|
||||
this.state = arrow.failState;
|
||||
return arrowExpression;
|
||||
} else if (jsxError != null) {
|
||||
throw jsxError;
|
||||
} else {
|
||||
this.raise(
|
||||
typeParameters.start,
|
||||
"Expected an arrow function after this type parameter declaration",
|
||||
);
|
||||
}
|
||||
|
||||
if (jsx && jsx.thrown) throw jsx.error;
|
||||
if (arrow.thrown) throw arrow.error;
|
||||
|
||||
/*:: invariant(typeParameters) */
|
||||
throw this.raise(
|
||||
typeParameters.start,
|
||||
"Expected an arrow function after this type parameter declaration",
|
||||
);
|
||||
}
|
||||
|
||||
return super.parseMaybeAssign(
|
||||
@@ -2567,8 +2642,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
// handle return types for arrow functions
|
||||
parseArrow(node: N.ArrowFunctionExpression): ?N.ArrowFunctionExpression {
|
||||
if (this.match(tt.colon)) {
|
||||
const state = this.state.clone();
|
||||
try {
|
||||
const result = this.tryParse(() => {
|
||||
const oldNoAnonFunctionType = this.state.noAnonFunctionType;
|
||||
this.state.noAnonFunctionType = true;
|
||||
|
||||
@@ -2586,18 +2660,18 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
if (this.canInsertSemicolon()) this.unexpected();
|
||||
if (!this.match(tt.arrow)) this.unexpected();
|
||||
|
||||
// assign after it is clear it is an arrow
|
||||
node.returnType = typeNode.typeAnnotation
|
||||
? this.finishNode(typeNode, "TypeAnnotation")
|
||||
: null;
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
this.state = state;
|
||||
} else {
|
||||
// istanbul ignore next: no such error is expected
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return typeNode;
|
||||
});
|
||||
|
||||
if (result.thrown) return null;
|
||||
/*:: invariant(result.node) */
|
||||
|
||||
if (result.error) this.state = result.failState;
|
||||
|
||||
// assign after it is clear it is an arrow
|
||||
node.returnType = result.node.typeAnnotation
|
||||
? this.finishNode(result.node, "TypeAnnotation")
|
||||
: null;
|
||||
}
|
||||
|
||||
return super.parseArrow(node);
|
||||
@@ -2630,7 +2704,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return;
|
||||
}
|
||||
|
||||
return super.checkParams(node, allowDuplicates, isArrowFunction);
|
||||
return super.checkParams(...arguments);
|
||||
}
|
||||
|
||||
parseParenAndDistinguishExpression(canBeArrow: boolean): N.Expression {
|
||||
@@ -2662,23 +2736,33 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
this.isRelational("<")
|
||||
) {
|
||||
const state = this.state.clone();
|
||||
let error;
|
||||
try {
|
||||
const node = this.parseAsyncArrowWithTypeParameters(
|
||||
startPos,
|
||||
startLoc,
|
||||
);
|
||||
if (node) return node;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
const arrow = this.tryParse(
|
||||
abort =>
|
||||
this.parseAsyncArrowWithTypeParameters(startPos, startLoc) ||
|
||||
abort(),
|
||||
state,
|
||||
);
|
||||
|
||||
if (!arrow.error && !arrow.aborted) return arrow.node;
|
||||
|
||||
const result = this.tryParse(
|
||||
() => super.parseSubscripts(base, startPos, startLoc, noCalls),
|
||||
state,
|
||||
);
|
||||
|
||||
if (result.node && !result.error) return result.node;
|
||||
|
||||
if (arrow.node) {
|
||||
this.state = arrow.failState;
|
||||
return arrow.node;
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
try {
|
||||
return super.parseSubscripts(base, startPos, startLoc, noCalls);
|
||||
} catch (e) {
|
||||
throw error || e;
|
||||
if (result.node) {
|
||||
this.state = result.failState;
|
||||
return result.node;
|
||||
}
|
||||
|
||||
throw arrow.error || result.error;
|
||||
}
|
||||
|
||||
return super.parseSubscripts(base, startPos, startLoc, noCalls);
|
||||
@@ -2717,8 +2801,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
) {
|
||||
const node = this.startNodeAt(startPos, startLoc);
|
||||
node.callee = base;
|
||||
const state = this.state.clone();
|
||||
try {
|
||||
|
||||
const result = this.tryParse(() => {
|
||||
node.typeArguments = this.flowParseTypeParameterInstantiationCallOrNew();
|
||||
this.expect(tt.parenL);
|
||||
node.arguments = this.parseCallExpressionArguments(tt.parenR, false);
|
||||
@@ -2727,12 +2811,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
node,
|
||||
subscriptState.optionalChainMember,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
this.state = state;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
if (result.node) {
|
||||
if (result.error) this.state = result.failState;
|
||||
return result.node;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2748,16 +2831,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
parseNewArguments(node: N.NewExpression): void {
|
||||
let targs = null;
|
||||
if (this.shouldParseTypes() && this.isRelational("<")) {
|
||||
const state = this.state.clone();
|
||||
try {
|
||||
targs = this.flowParseTypeParameterInstantiationCallOrNew();
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
this.state = state;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
targs = this.tryParse(() =>
|
||||
this.flowParseTypeParameterInstantiationCallOrNew(),
|
||||
).node;
|
||||
}
|
||||
node.typeArguments = targs;
|
||||
|
||||
@@ -2811,7 +2887,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
parseTopLevel(file: N.File, program: N.Program): N.File {
|
||||
const fileNode = super.parseTopLevel(file, program);
|
||||
if (this.state.hasFlowComment) {
|
||||
this.unexpected(null, "Unterminated flow-comment");
|
||||
this.raise(this.state.pos, "Unterminated flow-comment");
|
||||
}
|
||||
return fileNode;
|
||||
}
|
||||
@@ -2832,7 +2908,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
if (this.state.hasFlowComment) {
|
||||
const end = this.input.indexOf("*-/", (this.state.pos += 2));
|
||||
if (end === -1) this.raise(this.state.pos - 2, "Unterminated comment");
|
||||
if (end === -1) {
|
||||
throw this.raise(this.state.pos - 2, "Unterminated comment");
|
||||
}
|
||||
this.state.pos = end + 3;
|
||||
return;
|
||||
}
|
||||
@@ -2874,7 +2952,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
hasFlowCommentCompletion(): void {
|
||||
const end = this.input.indexOf("*/", this.state.pos);
|
||||
if (end === -1) {
|
||||
this.raise(this.state.pos, "Unterminated comment");
|
||||
throw this.raise(this.state.pos, "Unterminated comment");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2931,7 +3009,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
enumName,
|
||||
suppliedType,
|
||||
}: { enumName: string, suppliedType: null | string },
|
||||
): void {
|
||||
) {
|
||||
const suggestion =
|
||||
`Use one of \`boolean\`, \`number\`, \`string\`, or \`symbol\` in ` +
|
||||
`enum \`${enumName}\`.`;
|
||||
@@ -2939,13 +3017,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
suppliedType === null
|
||||
? `Supplied enum type is not valid. ${suggestion}`
|
||||
: `Enum type \`${suppliedType}\` is not valid. ${suggestion}`;
|
||||
this.raise(pos, message);
|
||||
return this.raise(pos, message);
|
||||
}
|
||||
|
||||
flowEnumErrorInvalidMemberInitializer(
|
||||
pos: number,
|
||||
{ enumName, explicitType, memberName }: EnumContext,
|
||||
): void {
|
||||
) {
|
||||
let message = null;
|
||||
switch (explicitType) {
|
||||
case "boolean":
|
||||
@@ -2966,7 +3044,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
`The enum member initializer for \`${memberName}\` needs to be a literal (either ` +
|
||||
`a boolean, number, or string) in enum \`${enumName}\`.`;
|
||||
}
|
||||
this.raise(pos, message);
|
||||
return this.raise(pos, message);
|
||||
}
|
||||
|
||||
flowEnumErrorNumberMemberNotInitialized(
|
||||
@@ -3119,8 +3197,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
break;
|
||||
}
|
||||
case "invalid": {
|
||||
this.flowEnumErrorInvalidMemberInitializer(init.pos, context);
|
||||
break;
|
||||
throw this.flowEnumErrorInvalidMemberInitializer(init.pos, context);
|
||||
}
|
||||
case "none": {
|
||||
switch (explicitType) {
|
||||
@@ -3184,28 +3261,29 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
enumName: string,
|
||||
}): EnumExplicitType {
|
||||
if (this.eatContextual("of")) {
|
||||
if (this.match(tt.name)) {
|
||||
switch (this.state.value) {
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
case "symbol": {
|
||||
const explicitType = this.state.value;
|
||||
this.next();
|
||||
return explicitType;
|
||||
}
|
||||
default:
|
||||
this.flowEnumErrorInvalidExplicitType(this.state.start, {
|
||||
enumName,
|
||||
suppliedType: this.state.value,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.flowEnumErrorInvalidExplicitType(this.state.start, {
|
||||
if (!this.match(tt.name)) {
|
||||
throw this.flowEnumErrorInvalidExplicitType(this.state.start, {
|
||||
enumName,
|
||||
suppliedType: null,
|
||||
});
|
||||
}
|
||||
|
||||
const { value } = this.state;
|
||||
this.next();
|
||||
|
||||
if (
|
||||
value !== "boolean" &&
|
||||
value !== "number" &&
|
||||
value !== "string" &&
|
||||
value !== "symbol"
|
||||
) {
|
||||
this.flowEnumErrorInvalidExplicitType(this.state.start, {
|
||||
enumName,
|
||||
suppliedType: value,
|
||||
});
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
let chunkStart = this.state.pos;
|
||||
for (;;) {
|
||||
if (this.state.pos >= this.length) {
|
||||
this.raise(this.state.start, "Unterminated JSX contents");
|
||||
throw this.raise(this.state.start, "Unterminated JSX contents");
|
||||
}
|
||||
|
||||
const ch = this.input.charCodeAt(this.state.pos);
|
||||
@@ -142,7 +142,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
let chunkStart = ++this.state.pos;
|
||||
for (;;) {
|
||||
if (this.state.pos >= this.length) {
|
||||
this.raise(this.state.start, "Unterminated string constant");
|
||||
throw this.raise(this.state.start, "Unterminated string constant");
|
||||
}
|
||||
|
||||
const ch = this.input.charCodeAt(this.state.pos);
|
||||
@@ -279,13 +279,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
this.next();
|
||||
node = this.jsxParseExpressionContainer(node);
|
||||
if (node.expression.type === "JSXEmptyExpression") {
|
||||
throw this.raise(
|
||||
this.raise(
|
||||
node.start,
|
||||
"JSX attributes must only be assigned a non-empty expression",
|
||||
);
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
return node;
|
||||
|
||||
case tt.jsxTagStart:
|
||||
case tt.string:
|
||||
@@ -485,12 +484,18 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
node.closingElement = closingElement;
|
||||
}
|
||||
node.children = children;
|
||||
if (this.match(tt.relational) && this.state.value === "<") {
|
||||
while (this.isRelational("<")) {
|
||||
// In case we encounter an lt token here it will always be the start of
|
||||
// jsx as the lt sign is not allowed in places that expect an expression
|
||||
this.finishToken(tt.jsxTagStart);
|
||||
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"Adjacent JSX elements must be wrapped in an enclosing tag. " +
|
||||
"Did you want a JSX fragment <>...</>?",
|
||||
);
|
||||
|
||||
this.jsxParseElement();
|
||||
}
|
||||
|
||||
return isFragment(openingElement)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// @flow
|
||||
|
||||
/*:: declare var invariant; */
|
||||
|
||||
import type { TokenType } from "../../tokenizer/types";
|
||||
import type State from "../../tokenizer/state";
|
||||
import { types as tt } from "../../tokenizer/types";
|
||||
import { types as ct } from "../../tokenizer/context";
|
||||
import * as N from "../../types";
|
||||
@@ -234,8 +237,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
this.expect(tt._import);
|
||||
this.expect(tt.parenL);
|
||||
if (!this.match(tt.string)) {
|
||||
throw this.unexpected(
|
||||
null,
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"Argument in a type import must be a string literal",
|
||||
);
|
||||
}
|
||||
@@ -371,13 +374,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
pattern.type !== "ObjectPattern" &&
|
||||
pattern.type !== "ArrayPattern"
|
||||
) {
|
||||
throw this.unexpected(
|
||||
this.raise(
|
||||
pattern.start,
|
||||
"Name in a signature must be an Identifier, ObjectPattern or ArrayPattern," +
|
||||
`instead got ${pattern.type}`,
|
||||
);
|
||||
}
|
||||
return pattern;
|
||||
return (pattern: any);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -642,7 +645,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
const node: N.TsLiteralType = this.startNode();
|
||||
const templateNode = this.parseTemplate(false);
|
||||
if (templateNode.expressions.length > 0) {
|
||||
throw this.raise(
|
||||
this.raise(
|
||||
templateNode.expressions[0].start,
|
||||
"Template literal types cannot have any substitution",
|
||||
);
|
||||
@@ -1276,17 +1279,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return res;
|
||||
}
|
||||
|
||||
tsTryParseAndCatch<T>(f: () => T): ?T {
|
||||
const state = this.state.clone();
|
||||
try {
|
||||
return f();
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
this.state = state;
|
||||
return undefined;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
tsTryParseAndCatch<T: ?N.NodeBase>(f: () => T): ?T {
|
||||
const result = this.tryParse(abort => f() || abort());
|
||||
|
||||
if (result.aborted || !result.node) return undefined;
|
||||
if (result.error) this.state = result.failState;
|
||||
return result.node;
|
||||
}
|
||||
|
||||
tsTryParse<T>(f: () => ?T): ?T {
|
||||
@@ -1558,12 +1556,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
if (accessibility) pp.accessibility = accessibility;
|
||||
if (readonly) pp.readonly = readonly;
|
||||
if (elt.type !== "Identifier" && elt.type !== "AssignmentPattern") {
|
||||
throw this.raise(
|
||||
this.raise(
|
||||
pp.start,
|
||||
"A parameter property may not be declared using a binding pattern.",
|
||||
);
|
||||
}
|
||||
pp.parameter = elt;
|
||||
pp.parameter = ((elt: any): N.Identifier | N.AssignmentPattern);
|
||||
return this.finishNode(pp, "TSParameterProperty");
|
||||
}
|
||||
|
||||
@@ -1597,11 +1595,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
super.parseFunctionBodyAndFinish(node, type, isMethod);
|
||||
}
|
||||
|
||||
checkFunctionStatementId(node: N.Function): void {
|
||||
registerFunctionStatementId(node: N.Function): void {
|
||||
if (!node.body && node.id) {
|
||||
// Function ids are validated after parsing their body.
|
||||
// For bodyless function, we need to do it here.
|
||||
this.checkLVal(node.id, BIND_TS_AMBIENT, null, "function name");
|
||||
} else {
|
||||
super.checkFunctionStatementId(...arguments);
|
||||
super.registerFunctionStatementId(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1946,19 +1946,17 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
);
|
||||
}
|
||||
|
||||
const state = this.state.clone();
|
||||
try {
|
||||
return super.parseConditional(expr, noIn, startPos, startLoc);
|
||||
} catch (err) {
|
||||
if (!(err instanceof SyntaxError)) {
|
||||
// istanbul ignore next: no such error is expected
|
||||
throw err;
|
||||
}
|
||||
const result = this.tryParse(() =>
|
||||
super.parseConditional(expr, noIn, startPos, startLoc),
|
||||
);
|
||||
|
||||
this.state = state;
|
||||
refNeedsArrowPos.start = err.pos || this.state.start;
|
||||
if (!result.node) {
|
||||
// $FlowIgnore
|
||||
refNeedsArrowPos.start = result.error.pos || this.state.start;
|
||||
return expr;
|
||||
}
|
||||
if (result.error) this.state = result.failState;
|
||||
return result.node;
|
||||
}
|
||||
|
||||
// Note: These "type casts" are *not* valid TS expressions.
|
||||
@@ -2161,80 +2159,97 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
parseMaybeAssign(...args): N.Expression {
|
||||
// Note: When the JSX plugin is on, type assertions (`<T> x`) aren't valid syntax.
|
||||
|
||||
let jsxError: ?SyntaxError;
|
||||
let state: ?State;
|
||||
let jsx;
|
||||
let typeCast;
|
||||
|
||||
if (this.match(tt.jsxTagStart)) {
|
||||
const context = this.curContext();
|
||||
assert(context === ct.j_oTag);
|
||||
// Only time j_oTag is pushed is right after j_expr.
|
||||
assert(this.state.context[this.state.context.length - 2] === ct.j_expr);
|
||||
|
||||
// Prefer to parse JSX if possible. But may be an arrow fn.
|
||||
const state = this.state.clone();
|
||||
try {
|
||||
return super.parseMaybeAssign(...args);
|
||||
} catch (err) {
|
||||
if (!(err instanceof SyntaxError)) {
|
||||
// istanbul ignore next: no such error is expected
|
||||
throw err;
|
||||
}
|
||||
state = this.state.clone();
|
||||
|
||||
this.state = state;
|
||||
// Pop the context added by the jsxTagStart.
|
||||
assert(this.curContext() === ct.j_oTag);
|
||||
this.state.context.pop();
|
||||
assert(this.curContext() === ct.j_expr);
|
||||
this.state.context.pop();
|
||||
jsxError = err;
|
||||
jsx = this.tryParse(() => super.parseMaybeAssign(...args), state);
|
||||
/*:: invariant(!jsx.aborted) */
|
||||
|
||||
if (!jsx.error) return jsx.node;
|
||||
|
||||
// Remove `tc.j_expr` and `tc.j_oTag` from context added
|
||||
// by parsing `jsxTagStart` to stop the JSX plugin from
|
||||
// messing with the tokens
|
||||
const { context } = this.state;
|
||||
if (context[context.length - 1] === ct.j_oTag) {
|
||||
context.length -= 2;
|
||||
} else if (context[context.length - 1] === ct.j_expr) {
|
||||
context.length -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (jsxError === undefined && !this.isRelational("<")) {
|
||||
if (!(jsx && jsx.error) && !this.isRelational("<")) {
|
||||
return super.parseMaybeAssign(...args);
|
||||
}
|
||||
|
||||
// Either way, we're looking at a '<': tt.jsxTagStart or relational.
|
||||
|
||||
let arrowExpression;
|
||||
let typeParameters: N.TsTypeParameterDeclaration;
|
||||
const state = this.state.clone();
|
||||
try {
|
||||
state = state || this.state.clone();
|
||||
|
||||
const arrow = this.tryParse(abort => {
|
||||
// This is similar to TypeScript's `tryParseParenthesizedArrowFunctionExpression`.
|
||||
typeParameters = this.tsParseTypeParameters();
|
||||
arrowExpression = super.parseMaybeAssign(...args);
|
||||
const expr = super.parseMaybeAssign(...args);
|
||||
|
||||
if (
|
||||
arrowExpression.type !== "ArrowFunctionExpression" ||
|
||||
(arrowExpression.extra && arrowExpression.extra.parenthesized)
|
||||
expr.type !== "ArrowFunctionExpression" ||
|
||||
(expr.extra && expr.extra.parenthesized)
|
||||
) {
|
||||
this.unexpected(); // Go to the catch block (needs a SyntaxError).
|
||||
}
|
||||
} catch (err) {
|
||||
if (!(err instanceof SyntaxError)) {
|
||||
// istanbul ignore next: no such error is expected
|
||||
throw err;
|
||||
abort();
|
||||
}
|
||||
|
||||
if (jsxError) {
|
||||
throw jsxError;
|
||||
// Correct TypeScript code should have at least 1 type parameter, but don't crash on bad code.
|
||||
if (typeParameters && typeParameters.params.length !== 0) {
|
||||
this.resetStartLocationFromNode(expr, typeParameters);
|
||||
}
|
||||
expr.typeParameters = typeParameters;
|
||||
return expr;
|
||||
}, state);
|
||||
|
||||
if (!arrow.error && !arrow.aborted) return arrow.node;
|
||||
|
||||
if (!jsx) {
|
||||
// Try parsing a type cast instead of an arrow function.
|
||||
// This will never happen outside of JSX.
|
||||
// (Because in JSX the '<' should be a jsxTagStart and not a relational.
|
||||
assert(!this.hasPlugin("jsx"));
|
||||
// Parsing an arrow function failed, so try a type cast.
|
||||
this.state = state;
|
||||
|
||||
// This will start with a type assertion (via parseMaybeUnary).
|
||||
// But don't directly call `this.tsParseTypeAssertion` because we want to handle any binary after it.
|
||||
return super.parseMaybeAssign(...args);
|
||||
typeCast = this.tryParse(() => super.parseMaybeAssign(...args), state);
|
||||
/*:: invariant(!typeCast.aborted) */
|
||||
if (!typeCast.error) return typeCast.node;
|
||||
}
|
||||
|
||||
// Correct TypeScript code should have at least 1 type parameter, but don't crash on bad code.
|
||||
if (typeParameters && typeParameters.params.length !== 0) {
|
||||
this.resetStartLocationFromNode(arrowExpression, typeParameters);
|
||||
if (jsx && jsx.node) {
|
||||
/*:: invariant(jsx.failState) */
|
||||
this.state = jsx.failState;
|
||||
return jsx.node;
|
||||
}
|
||||
arrowExpression.typeParameters = typeParameters;
|
||||
return arrowExpression;
|
||||
|
||||
if (arrow.node) {
|
||||
/*:: invariant(arrow.failState) */
|
||||
this.state = arrow.failState;
|
||||
return arrow.node;
|
||||
}
|
||||
|
||||
if (typeCast && typeCast.node) {
|
||||
/*:: invariant(typeCast.failState) */
|
||||
this.state = typeCast.failState;
|
||||
return typeCast.node;
|
||||
}
|
||||
|
||||
if (jsx && jsx.thrown) throw jsx.error;
|
||||
if (arrow.thrown) throw arrow.error;
|
||||
if (typeCast && typeCast.thrown) throw typeCast.error;
|
||||
|
||||
throw (jsx && jsx.error) || arrow.error || (typeCast && typeCast.error);
|
||||
}
|
||||
|
||||
// Handle type assertions
|
||||
@@ -2250,23 +2265,20 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
if (this.match(tt.colon)) {
|
||||
// This is different from how the TS parser does it.
|
||||
// TS uses lookahead. The Babel Parser parses it as a parenthesized expression and converts.
|
||||
const state = this.state.clone();
|
||||
try {
|
||||
|
||||
const result = this.tryParse(abort => {
|
||||
const returnType = this.tsParseTypeOrTypePredicateAnnotation(
|
||||
tt.colon,
|
||||
);
|
||||
if (this.canInsertSemicolon() || !this.match(tt.arrow)) {
|
||||
this.state = state;
|
||||
return undefined;
|
||||
}
|
||||
node.returnType = returnType;
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
this.state = state;
|
||||
} else {
|
||||
// istanbul ignore next: no such error is expected
|
||||
throw err;
|
||||
}
|
||||
if (this.canInsertSemicolon() || !this.match(tt.arrow)) abort();
|
||||
return returnType;
|
||||
});
|
||||
|
||||
if (result.aborted) return;
|
||||
|
||||
if (!result.thrown) {
|
||||
if (result.error) this.state = result.failState;
|
||||
node.returnType = result.node;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2277,13 +2289,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
parseAssignableListItemTypes(param: N.Pattern) {
|
||||
if (this.eat(tt.question)) {
|
||||
if (param.type !== "Identifier") {
|
||||
throw this.raise(
|
||||
this.raise(
|
||||
param.start,
|
||||
"A binding pattern parameter cannot be optional in an implementation signature.",
|
||||
);
|
||||
}
|
||||
|
||||
param.optional = true;
|
||||
((param: any): N.Identifier).optional = true;
|
||||
}
|
||||
const type = this.tsTryParseTypeAnnotation();
|
||||
if (type) param.typeAnnotation = type;
|
||||
|
||||
Reference in New Issue
Block a user