@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:
Nicolò Ribaudo
2019-11-05 10:15:00 +01:00
committed by GitHub
parent d25262ec4b
commit 87feda7c2a
2224 changed files with 155996 additions and 3353 deletions

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;