packages/babel-parser/src/plugins/flow.js
// @flow
/*:: declare var invariant; */
import type Parser from "../parser";
import { types as tt, type TokenType } from "../tokenizer/types";
import * as N from "../types";
import type { Options } from "../options";
import type { Pos, Position } from "../util/location";
import type State from "../tokenizer/state";
import { types as tc } from "../tokenizer/context";
import * as charCodes from "charcodes";
import { isIteratorStart } from "../util/identifier";
import {
type BindingTypes,
BIND_NONE,
BIND_LEXICAL,
BIND_VAR,
BIND_FUNCTION,
SCOPE_ARROW,
SCOPE_FUNCTION,
SCOPE_OTHER,
} from "../util/scopeflags";
import type { ExpressionErrors } from "../parser/util";
import { Errors } from "../parser/error";
const reservedTypes = new Set([
"_",
"any",
"bool",
"boolean",
"empty",
"extends",
"false",
"interface",
"mixed",
"null",
"number",
"static",
"string",
"true",
"typeof",
"void",
]);
/* eslint sort-keys: "error" */
// The Errors key follows https://github.com/facebook/flow/blob/master/src/parser/parse_error.ml unless it does not exist
const FlowErrors = Object.freeze({
AmbiguousConditionalArrow:
"Ambiguous expression: wrap the arrow functions in parentheses to disambiguate.",
AmbiguousDeclareModuleKind:
"Found both `declare module.exports` and `declare export` in the same module. Modules can only have 1 since they are either an ES module or they are a CommonJS module",
AssignReservedType: "Cannot overwrite reserved type %0",
DeclareClassElement:
"The `declare` modifier can only appear on class fields.",
DeclareClassFieldInitializer:
"Initializers are not allowed in fields with the `declare` modifier.",
DuplicateDeclareModuleExports: "Duplicate `declare module.exports` statement",
EnumBooleanMemberNotInitialized:
"Boolean enum members need to be initialized. Use either `%0 = true,` or `%0 = false,` in enum `%1`.",
EnumDuplicateMemberName:
"Enum member names need to be unique, but the name `%0` has already been used before in enum `%1`.",
EnumInconsistentMemberValues:
"Enum `%0` has inconsistent member initializers. Either use no initializers, or consistently use literals (either booleans, numbers, or strings) for all member initializers.",
EnumInvalidExplicitType:
"Enum type `%1` is not valid. Use one of `boolean`, `number`, `string`, or `symbol` in enum `%0`.",
EnumInvalidExplicitTypeUnknownSupplied:
"Supplied enum type is not valid. Use one of `boolean`, `number`, `string`, or `symbol` in enum `%0`.",
EnumInvalidMemberInitializerPrimaryType:
"Enum `%0` has type `%2`, so the initializer of `%1` needs to be a %2 literal.",
EnumInvalidMemberInitializerSymbolType:
"Symbol enum members cannot be initialized. Use `%1,` in enum `%0`.",
EnumInvalidMemberInitializerUnknownType:
"The enum member initializer for `%1` needs to be a literal (either a boolean, number, or string) in enum `%0`.",
EnumInvalidMemberName:
"Enum member names cannot start with lowercase 'a' through 'z'. Instead of using `%0`, consider using `%1`, in enum `%2`.",
EnumNumberMemberNotInitialized:
"Number enum members need to be initialized, e.g. `%1 = 1` in enum `%0`.",
EnumStringMemberInconsistentlyInitailized:
"String enum members need to consistently either all use initializers, or use no initializers, in enum `%0`.",
ImportTypeShorthandOnlyInPureImport:
"The `type` and `typeof` keywords on named imports can only be used on regular `import` statements. It cannot be used with `import type` or `import typeof` statements",
InexactInsideExact:
"Explicit inexact syntax cannot appear inside an explicit exact object type",
InexactInsideNonObject:
"Explicit inexact syntax cannot appear in class or interface definitions",
InexactVariance: "Explicit inexact syntax cannot have variance",
InvalidNonTypeImportInDeclareModule:
"Imports within a `declare module` body must always be `import type` or `import typeof`",
MissingTypeParamDefault:
"Type parameter declaration needs a default, since a preceding type parameter declaration has a default.",
NestedDeclareModule:
"`declare module` cannot be used inside another `declare module`",
NestedFlowComment: "Cannot have a flow comment inside another flow comment",
OptionalBindingPattern:
"A binding pattern parameter cannot be optional in an implementation signature.",
SpreadVariance: "Spread properties cannot have variance",
TypeBeforeInitializer:
"Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`",
TypeCastInPattern:
"The type cast expression is expected to be wrapped with parenthesis",
UnexpectedExplicitInexactInObject:
"Explicit inexact syntax must appear at the end of an inexact object",
UnexpectedReservedType: "Unexpected reserved type %0",
UnexpectedReservedUnderscore:
"`_` is only allowed as a type argument to call or new",
UnexpectedSpaceBetweenModuloChecks:
"Spaces between `%` and `checks` are not allowed here.",
UnexpectedSpreadType:
"Spread operator cannot appear in class or interface definitions",
UnexpectedSubtractionOperand:
'Unexpected token, expected "number" or "bigint"',
UnexpectedTokenAfterTypeParameter:
"Expected an arrow function after this type parameter declaration",
UnsupportedDeclareExportKind:
"`declare export %0` is not supported. Use `%1` instead",
UnsupportedStatementInDeclareModule:
"Only declares and type imports are allowed inside declare module",
UnterminatedFlowComment: "Unterminated flow-comment",
});
/* eslint-disable sort-keys */
function isEsModuleType(bodyElement: N.Node): boolean {
return (
bodyElement.type === "DeclareExportAllDeclaration" ||
(bodyElement.type === "DeclareExportDeclaration" &&
(!bodyElement.declaration ||
(bodyElement.declaration.type !== "TypeAlias" &&
bodyElement.declaration.type !== "InterfaceDeclaration")))
);
}
function hasTypeImportKind(node: N.Node): boolean {
return node.importKind === "type" || node.importKind === "typeof";
}
function isMaybeDefaultImport(state: State): boolean {
return (
(state.type === tt.name || !!state.type.keyword) && state.value !== "from"
);
}
const exportSuggestions = {
const: "declare export var",
let: "declare export var",
type: "export type",
interface: "export interface",
};
// Like Array#filter, but returns a tuple [ acceptedElements, discardedElements ]
function partition<T>(
list: T[],
test: (T, number, T[]) => ?boolean,
): [T[], T[]] {
const list1 = [];
const list2 = [];
for (let i = 0; i < list.length; i++) {
(test(list[i], i, list) ? list1 : list2).push(list[i]);
}
return [list1, list2];
}
const FLOW_PRAGMA_REGEX = /\*?\s*@((?:no)?flow)\b/;
// Flow enums types
type EnumExplicitType = null | "boolean" | "number" | "string" | "symbol";
type EnumContext = {|
enumName: string,
explicitType: EnumExplicitType,
memberName: string,
|};
type EnumMemberInit =
| {| type: "number", pos: number, value: N.Node |}
| {| type: "string", pos: number, value: N.Node |}
| {| type: "boolean", pos: number, value: N.Node |}
| {| type: "invalid", pos: number |}
| {| type: "none", pos: number |};
export default (superClass: Class<Parser>): Class<Parser> =>
class extends superClass {
// The value of the @flow/@noflow pragma. Initially undefined, transitions
// to "@flow" or "@noflow" if we see a pragma. Transitions to null if we are
// past the initial comment.
flowPragma: void | null | "flow" | "noflow";
constructor(options: ?Options, input: string) {
super(options, input);
this.flowPragma = undefined;
}
shouldParseTypes(): boolean {
return this.getPluginOption("flow", "all") || this.flowPragma === "flow";
}
shouldParseEnums(): boolean {
return !!this.getPluginOption("flow", "enums");
}
finishToken(type: TokenType, val: any): void {
if (
type !== tt.string &&
type !== tt.semi &&
type !== tt.interpreterDirective
) {
if (this.flowPragma === undefined) {
this.flowPragma = null;
}
}
return super.finishToken(type, val);
}
addComment(comment: N.Comment): void {
if (this.flowPragma === undefined) {
// Try to parse a flow pragma.
const matches = FLOW_PRAGMA_REGEX.exec(comment.value);
if (!matches) {
// do nothing
} else if (matches[1] === "flow") {
this.flowPragma = "flow";
} else if (matches[1] === "noflow") {
this.flowPragma = "noflow";
} else {
throw new Error("Unexpected flow pragma");
}
}
return super.addComment(comment);
}
flowParseTypeInitialiser(tok?: TokenType): N.FlowType {
const oldInType = this.state.inType;
this.state.inType = true;
this.expect(tok || tt.colon);
const type = this.flowParseType();
this.state.inType = oldInType;
return type;
}
flowParsePredicate(): N.FlowType {
const node = this.startNode();
const moduloLoc = this.state.startLoc;
const moduloPos = this.state.start;
this.expect(tt.modulo);
const checksLoc = this.state.startLoc;
this.expectContextual("checks");
// Force '%' and 'checks' to be adjacent
if (
moduloLoc.line !== checksLoc.line ||
moduloLoc.column !== checksLoc.column - 1
) {
this.raise(moduloPos, FlowErrors.UnexpectedSpaceBetweenModuloChecks);
}
if (this.eat(tt.parenL)) {
node.value = this.parseExpression();
this.expect(tt.parenR);
return this.finishNode(node, "DeclaredPredicate");
} else {
return this.finishNode(node, "InferredPredicate");
}
}
flowParseTypeAndPredicateInitialiser(): [?N.FlowType, ?N.FlowPredicate] {
const oldInType = this.state.inType;
this.state.inType = true;
this.expect(tt.colon);
let type = null;
let predicate = null;
if (this.match(tt.modulo)) {
this.state.inType = oldInType;
predicate = this.flowParsePredicate();
} else {
type = this.flowParseType();
this.state.inType = oldInType;
if (this.match(tt.modulo)) {
predicate = this.flowParsePredicate();
}
}
return [type, predicate];
}
flowParseDeclareClass(node: N.FlowDeclareClass): N.FlowDeclareClass {
this.next();
this.flowParseInterfaceish(node, /*isClass*/ true);
return this.finishNode(node, "DeclareClass");
}
flowParseDeclareFunction(
node: N.FlowDeclareFunction,
): N.FlowDeclareFunction {
this.next();
const id = (node.id = this.parseIdentifier());
const typeNode = this.startNode();
const typeContainer = this.startNode();
if (this.isRelational("<")) {
typeNode.typeParameters = this.flowParseTypeParameterDeclaration();
} else {
typeNode.typeParameters = null;
}
this.expect(tt.parenL);
const tmp = this.flowParseFunctionTypeParams();
typeNode.params = tmp.params;
typeNode.rest = tmp.rest;
this.expect(tt.parenR);
[
// $FlowFixMe (destructuring not supported yet)
typeNode.returnType,
// $FlowFixMe (destructuring not supported yet)
node.predicate,
] = this.flowParseTypeAndPredicateInitialiser();
typeContainer.typeAnnotation = this.finishNode(
typeNode,
"FunctionTypeAnnotation",
);
id.typeAnnotation = this.finishNode(typeContainer, "TypeAnnotation");
this.resetEndLocation(id);
this.semicolon();
return this.finishNode(node, "DeclareFunction");
}
flowParseDeclare(
node: N.FlowDeclare,
insideModule?: boolean,
): N.FlowDeclare {
if (this.match(tt._class)) {
return this.flowParseDeclareClass(node);
} else if (this.match(tt._function)) {
return this.flowParseDeclareFunction(node);
} else if (this.match(tt._var)) {
return this.flowParseDeclareVariable(node);
} else if (this.eatContextual("module")) {
if (this.match(tt.dot)) {
return this.flowParseDeclareModuleExports(node);
} else {
if (insideModule) {
this.raise(this.state.lastTokStart, FlowErrors.NestedDeclareModule);
}
return this.flowParseDeclareModule(node);
}
} else if (this.isContextual("type")) {
return this.flowParseDeclareTypeAlias(node);
} else if (this.isContextual("opaque")) {
return this.flowParseDeclareOpaqueType(node);
} else if (this.isContextual("interface")) {
return this.flowParseDeclareInterface(node);
} else if (this.match(tt._export)) {
return this.flowParseDeclareExportDeclaration(node, insideModule);
} else {
throw this.unexpected();
}
}
flowParseDeclareVariable(
node: N.FlowDeclareVariable,
): N.FlowDeclareVariable {
this.next();
node.id = this.flowParseTypeAnnotatableIdentifier(
/*allowPrimitiveOverride*/ true,
);
this.scope.declareName(node.id.name, BIND_VAR, node.id.start);
this.semicolon();
return this.finishNode(node, "DeclareVariable");
}
flowParseDeclareModule(node: N.FlowDeclareModule): N.FlowDeclareModule {
this.scope.enter(SCOPE_OTHER);
if (this.match(tt.string)) {
node.id = this.parseExprAtom();
} else {
node.id = this.parseIdentifier();
}
const bodyNode = (node.body = this.startNode());
const body = (bodyNode.body = []);
this.expect(tt.braceL);
while (!this.match(tt.braceR)) {
let bodyNode = this.startNode();
if (this.match(tt._import)) {
this.next();
if (!this.isContextual("type") && !this.match(tt._typeof)) {
this.raise(
this.state.lastTokStart,
FlowErrors.InvalidNonTypeImportInDeclareModule,
);
}
this.parseImport(bodyNode);
} else {
this.expectContextual(
"declare",
FlowErrors.UnsupportedStatementInDeclareModule,
);
bodyNode = this.flowParseDeclare(bodyNode, true);
}
body.push(bodyNode);
}
this.scope.exit();
this.expect(tt.braceR);
this.finishNode(bodyNode, "BlockStatement");
let kind = null;
let hasModuleExport = false;
body.forEach(bodyElement => {
if (isEsModuleType(bodyElement)) {
if (kind === "CommonJS") {
this.raise(
bodyElement.start,
FlowErrors.AmbiguousDeclareModuleKind,
);
}
kind = "ES";
} else if (bodyElement.type === "DeclareModuleExports") {
if (hasModuleExport) {
this.raise(
bodyElement.start,
FlowErrors.DuplicateDeclareModuleExports,
);
}
if (kind === "ES") {
this.raise(
bodyElement.start,
FlowErrors.AmbiguousDeclareModuleKind,
);
}
kind = "CommonJS";
hasModuleExport = true;
}
});
node.kind = kind || "CommonJS";
return this.finishNode(node, "DeclareModule");
}
flowParseDeclareExportDeclaration(
node: N.FlowDeclareExportDeclaration,
insideModule: ?boolean,
): N.FlowDeclareExportDeclaration {
this.expect(tt._export);
if (this.eat(tt._default)) {
if (this.match(tt._function) || this.match(tt._class)) {
// declare export default class ...
// declare export default function ...
node.declaration = this.flowParseDeclare(this.startNode());
} else {
// declare export default [type];
node.declaration = this.flowParseType();
this.semicolon();
}
node.default = true;
return this.finishNode(node, "DeclareExportDeclaration");
} else {
if (
this.match(tt._const) ||
this.isLet() ||
((this.isContextual("type") || this.isContextual("interface")) &&
!insideModule)
) {
const label = this.state.value;
const suggestion = exportSuggestions[label];
throw this.raise(
this.state.start,
FlowErrors.UnsupportedDeclareExportKind,
label,
suggestion,
);
}
if (
this.match(tt._var) || // declare export var ...
this.match(tt._function) || // declare export function ...
this.match(tt._class) || // declare export class ...
this.isContextual("opaque") // declare export opaque ..
) {
node.declaration = this.flowParseDeclare(this.startNode());
node.default = false;
return this.finishNode(node, "DeclareExportDeclaration");
} else if (
this.match(tt.star) || // declare export * from ''
this.match(tt.braceL) || // declare export {} ...
this.isContextual("interface") || // declare export interface ...
this.isContextual("type") || // declare export type ...
this.isContextual("opaque") // declare export opaque type ...
) {
node = this.parseExport(node);
if (node.type === "ExportNamedDeclaration") {
// flow does not support the ExportNamedDeclaration
// $FlowIgnore
node.type = "ExportDeclaration";
// $FlowFixMe
node.default = false;
delete node.exportKind;
}
// $FlowIgnore
node.type = "Declare" + node.type;
return node;
}
}
throw this.unexpected();
}
flowParseDeclareModuleExports(
node: N.FlowDeclareModuleExports,
): N.FlowDeclareModuleExports {
this.next();
this.expectContextual("exports");
node.typeAnnotation = this.flowParseTypeAnnotation();
this.semicolon();
return this.finishNode(node, "DeclareModuleExports");
}
flowParseDeclareTypeAlias(
node: N.FlowDeclareTypeAlias,
): N.FlowDeclareTypeAlias {
this.next();
this.flowParseTypeAlias(node);
// Don't do finishNode as we don't want to process comments twice
node.type = "DeclareTypeAlias";
return node;
}
flowParseDeclareOpaqueType(
node: N.FlowDeclareOpaqueType,
): N.FlowDeclareOpaqueType {
this.next();
this.flowParseOpaqueType(node, true);
// Don't do finishNode as we don't want to process comments twice
node.type = "DeclareOpaqueType";
return node;
}
flowParseDeclareInterface(
node: N.FlowDeclareInterface,
): N.FlowDeclareInterface {
this.next();
this.flowParseInterfaceish(node);
return this.finishNode(node, "DeclareInterface");
}
// Interfaces
flowParseInterfaceish(
node: N.FlowDeclare,
isClass?: boolean = false,
): void {
node.id = this.flowParseRestrictedIdentifier(
/* liberal */ !isClass,
/* declaration */ true,
);
this.scope.declareName(
node.id.name,
isClass ? BIND_FUNCTION : BIND_LEXICAL,
node.id.start,
);
if (this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterDeclaration();
} else {
node.typeParameters = null;
}
node.extends = [];
node.implements = [];
node.mixins = [];
if (this.eat(tt._extends)) {
do {
node.extends.push(this.flowParseInterfaceExtends());
} while (!isClass && this.eat(tt.comma));
}
if (this.isContextual("mixins")) {
this.next();
do {
node.mixins.push(this.flowParseInterfaceExtends());
} while (this.eat(tt.comma));
}
if (this.isContextual("implements")) {
this.next();
do {
node.implements.push(this.flowParseInterfaceExtends());
} while (this.eat(tt.comma));
}
node.body = this.flowParseObjectType({
allowStatic: isClass,
allowExact: false,
allowSpread: false,
allowProto: isClass,
allowInexact: false,
});
}
flowParseInterfaceExtends(): N.FlowInterfaceExtends {
const node = this.startNode();
node.id = this.flowParseQualifiedTypeIdentifier();
if (this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterInstantiation();
} else {
node.typeParameters = null;
}
return this.finishNode(node, "InterfaceExtends");
}
flowParseInterface(node: N.FlowInterface): N.FlowInterface {
this.flowParseInterfaceish(node);
return this.finishNode(node, "InterfaceDeclaration");
}
checkNotUnderscore(word: string) {
if (word === "_") {
this.raise(this.state.start, FlowErrors.UnexpectedReservedUnderscore);
}
}
checkReservedType(word: string, startLoc: number, declaration?: boolean) {
if (!reservedTypes.has(word)) return;
this.raise(
startLoc,
declaration
? FlowErrors.AssignReservedType
: FlowErrors.UnexpectedReservedType,
word,
);
}
flowParseRestrictedIdentifier(
liberal?: boolean,
declaration?: boolean,
): N.Identifier {
this.checkReservedType(this.state.value, this.state.start, declaration);
return this.parseIdentifier(liberal);
}
// Type aliases
flowParseTypeAlias(node: N.FlowTypeAlias): N.FlowTypeAlias {
node.id = this.flowParseRestrictedIdentifier(
/* liberal */ false,
/* declaration */ true,
);
this.scope.declareName(node.id.name, BIND_LEXICAL, node.id.start);
if (this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterDeclaration();
} else {
node.typeParameters = null;
}
node.right = this.flowParseTypeInitialiser(tt.eq);
this.semicolon();
return this.finishNode(node, "TypeAlias");
}
flowParseOpaqueType(
node: N.FlowOpaqueType,
declare: boolean,
): N.FlowOpaqueType {
this.expectContextual("type");
node.id = this.flowParseRestrictedIdentifier(
/* liberal */ true,
/* declaration */ true,
);
this.scope.declareName(node.id.name, BIND_LEXICAL, node.id.start);
if (this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterDeclaration();
} else {
node.typeParameters = null;
}
// Parse the supertype
node.supertype = null;
if (this.match(tt.colon)) {
node.supertype = this.flowParseTypeInitialiser(tt.colon);
}
node.impltype = null;
if (!declare) {
node.impltype = this.flowParseTypeInitialiser(tt.eq);
}
this.semicolon();
return this.finishNode(node, "OpaqueType");
}
// Type annotations
flowParseTypeParameter(requireDefault?: boolean = false): N.TypeParameter {
const nodeStart = this.state.start;
const node = this.startNode();
const variance = this.flowParseVariance();
const ident = this.flowParseTypeAnnotatableIdentifier();
node.name = ident.name;
node.variance = variance;
node.bound = ident.typeAnnotation;
if (this.match(tt.eq)) {
this.eat(tt.eq);
node.default = this.flowParseType();
} else {
if (requireDefault) {
this.raise(nodeStart, FlowErrors.MissingTypeParamDefault);
}
}
return this.finishNode(node, "TypeParameter");
}
flowParseTypeParameterDeclaration(): N.TypeParameterDeclaration {
const oldInType = this.state.inType;
const node = this.startNode();
node.params = [];
this.state.inType = true;
// istanbul ignore else: this condition is already checked at all call sites
if (this.isRelational("<") || this.match(tt.jsxTagStart)) {
this.next();
} else {
this.unexpected();
}
let defaultRequired = false;
do {
const typeParameter = this.flowParseTypeParameter(defaultRequired);
node.params.push(typeParameter);
if (typeParameter.default) {
defaultRequired = true;
}
if (!this.isRelational(">")) {
this.expect(tt.comma);
}
} while (!this.isRelational(">"));
this.expectRelational(">");
this.state.inType = oldInType;
return this.finishNode(node, "TypeParameterDeclaration");
}
flowParseTypeParameterInstantiation(): N.TypeParameterInstantiation {
const node = this.startNode();
const oldInType = this.state.inType;
node.params = [];
this.state.inType = true;
this.expectRelational("<");
const oldNoAnonFunctionType = this.state.noAnonFunctionType;
this.state.noAnonFunctionType = false;
while (!this.isRelational(">")) {
node.params.push(this.flowParseType());
if (!this.isRelational(">")) {
this.expect(tt.comma);
}
}
this.state.noAnonFunctionType = oldNoAnonFunctionType;
this.expectRelational(">");
this.state.inType = oldInType;
return this.finishNode(node, "TypeParameterInstantiation");
}
flowParseTypeParameterInstantiationCallOrNew(): N.TypeParameterInstantiation {
const node = this.startNode();
const oldInType = this.state.inType;
node.params = [];
this.state.inType = true;
this.expectRelational("<");
while (!this.isRelational(">")) {
node.params.push(this.flowParseTypeOrImplicitInstantiation());
if (!this.isRelational(">")) {
this.expect(tt.comma);
}
}
this.expectRelational(">");
this.state.inType = oldInType;
return this.finishNode(node, "TypeParameterInstantiation");
}
flowParseInterfaceType(): N.FlowInterfaceType {
const node = this.startNode();
this.expectContextual("interface");
node.extends = [];
if (this.eat(tt._extends)) {
do {
node.extends.push(this.flowParseInterfaceExtends());
} while (this.eat(tt.comma));
}
node.body = this.flowParseObjectType({
allowStatic: false,
allowExact: false,
allowSpread: false,
allowProto: false,
allowInexact: false,
});
return this.finishNode(node, "InterfaceTypeAnnotation");
}
flowParseObjectPropertyKey(): N.Expression {
return this.match(tt.num) || this.match(tt.string)
? this.parseExprAtom()
: this.parseIdentifier(true);
}
flowParseObjectTypeIndexer(
node: N.FlowObjectTypeIndexer,
isStatic: boolean,
variance: ?N.FlowVariance,
): N.FlowObjectTypeIndexer {
node.static = isStatic;
// Note: bracketL has already been consumed
if (this.lookahead().type === tt.colon) {
node.id = this.flowParseObjectPropertyKey();
node.key = this.flowParseTypeInitialiser();
} else {
node.id = null;
node.key = this.flowParseType();
}
this.expect(tt.bracketR);
node.value = this.flowParseTypeInitialiser();
node.variance = variance;
return this.finishNode(node, "ObjectTypeIndexer");
}
flowParseObjectTypeInternalSlot(
node: N.FlowObjectTypeInternalSlot,
isStatic: boolean,
): N.FlowObjectTypeInternalSlot {
node.static = isStatic;
// Note: both bracketL have already been consumed
node.id = this.flowParseObjectPropertyKey();
this.expect(tt.bracketR);
this.expect(tt.bracketR);
if (this.isRelational("<") || this.match(tt.parenL)) {
node.method = true;
node.optional = false;
node.value = this.flowParseObjectTypeMethodish(
this.startNodeAt(node.start, node.loc.start),
);
} else {
node.method = false;
if (this.eat(tt.question)) {
node.optional = true;
}
node.value = this.flowParseTypeInitialiser();
}
return this.finishNode(node, "ObjectTypeInternalSlot");
}
flowParseObjectTypeMethodish(
node: N.FlowFunctionTypeAnnotation,
): N.FlowFunctionTypeAnnotation {
node.params = [];
node.rest = null;
node.typeParameters = null;
if (this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterDeclaration();
}
this.expect(tt.parenL);
while (!this.match(tt.parenR) && !this.match(tt.ellipsis)) {
node.params.push(this.flowParseFunctionTypeParam());
if (!this.match(tt.parenR)) {
this.expect(tt.comma);
}
}
if (this.eat(tt.ellipsis)) {
node.rest = this.flowParseFunctionTypeParam();
}
this.expect(tt.parenR);
node.returnType = this.flowParseTypeInitialiser();
return this.finishNode(node, "FunctionTypeAnnotation");
}
flowParseObjectTypeCallProperty(
node: N.FlowObjectTypeCallProperty,
isStatic: boolean,
): N.FlowObjectTypeCallProperty {
const valueNode = this.startNode();
node.static = isStatic;
node.value = this.flowParseObjectTypeMethodish(valueNode);
return this.finishNode(node, "ObjectTypeCallProperty");
}
flowParseObjectType({
allowStatic,
allowExact,
allowSpread,
allowProto,
allowInexact,
}: {
allowStatic: boolean,
allowExact: boolean,
allowSpread: boolean,
allowProto: boolean,
allowInexact: boolean,
}): N.FlowObjectTypeAnnotation {
const oldInType = this.state.inType;
this.state.inType = true;
const nodeStart = this.startNode();
nodeStart.callProperties = [];
nodeStart.properties = [];
nodeStart.indexers = [];
nodeStart.internalSlots = [];
let endDelim;
let exact;
let inexact = false;
if (allowExact && this.match(tt.braceBarL)) {
this.expect(tt.braceBarL);
endDelim = tt.braceBarR;
exact = true;
} else {
this.expect(tt.braceL);
endDelim = tt.braceR;
exact = false;
}
nodeStart.exact = exact;
while (!this.match(endDelim)) {
let isStatic = false;
let protoStart: ?number = null;
let inexactStart: ?number = null;
const node = this.startNode();
if (allowProto && this.isContextual("proto")) {
const lookahead = this.lookahead();
if (lookahead.type !== tt.colon && lookahead.type !== tt.question) {
this.next();
protoStart = this.state.start;
allowStatic = false;
}
}
if (allowStatic && this.isContextual("static")) {
const lookahead = this.lookahead();
// static is a valid identifier name
if (lookahead.type !== tt.colon && lookahead.type !== tt.question) {
this.next();
isStatic = true;
}
}
const variance = this.flowParseVariance();
if (this.eat(tt.bracketL)) {
if (protoStart != null) {
this.unexpected(protoStart);
}
if (this.eat(tt.bracketL)) {
if (variance) {
this.unexpected(variance.start);
}
nodeStart.internalSlots.push(
this.flowParseObjectTypeInternalSlot(node, isStatic),
);
} else {
nodeStart.indexers.push(
this.flowParseObjectTypeIndexer(node, isStatic, variance),
);
}
} else if (this.match(tt.parenL) || this.isRelational("<")) {
if (protoStart != null) {
this.unexpected(protoStart);
}
if (variance) {
this.unexpected(variance.start);
}
nodeStart.callProperties.push(
this.flowParseObjectTypeCallProperty(node, isStatic),
);
} else {
let kind = "init";
if (this.isContextual("get") || this.isContextual("set")) {
const lookahead = this.lookahead();
if (
lookahead.type === tt.name ||
lookahead.type === tt.string ||
lookahead.type === tt.num
) {
kind = this.state.value;
this.next();
}
}
const propOrInexact = this.flowParseObjectTypeProperty(
node,
isStatic,
protoStart,
variance,
kind,
allowSpread,
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,
FlowErrors.UnexpectedExplicitInexactInObject,
);
}
}
this.expect(endDelim);
/* The inexact flag should only be added on ObjectTypeAnnotations that
* are not the body of an interface, declare interface, or declare class.
* Since spreads are only allowed in objec types, checking that is
* sufficient here.
*/
if (allowSpread) {
nodeStart.inexact = inexact;
}
const out = this.finishNode(nodeStart, "ObjectTypeAnnotation");
this.state.inType = oldInType;
return out;
}
flowParseObjectTypeProperty(
node: N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty,
isStatic: boolean,
protoStart: ?number,
variance: ?N.FlowVariance,
kind: string,
allowSpread: boolean,
allowInexact: boolean,
): (N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty) | null {
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,
FlowErrors.InexactInsideNonObject,
);
} else if (!allowInexact) {
this.raise(this.state.lastTokStart, FlowErrors.InexactInsideExact);
}
if (variance) {
this.raise(variance.start, FlowErrors.InexactVariance);
}
return null;
}
if (!allowSpread) {
this.raise(this.state.lastTokStart, FlowErrors.UnexpectedSpreadType);
}
if (protoStart != null) {
this.unexpected(protoStart);
}
if (variance) {
this.raise(variance.start, FlowErrors.SpreadVariance);
}
node.argument = this.flowParseType();
return this.finishNode(node, "ObjectTypeSpreadProperty");
} else {
node.key = this.flowParseObjectPropertyKey();
node.static = isStatic;
node.proto = protoStart != null;
node.kind = kind;
let optional = false;
if (this.isRelational("<") || this.match(tt.parenL)) {
// This is a method property
node.method = true;
if (protoStart != null) {
this.unexpected(protoStart);
}
if (variance) {
this.unexpected(variance.start);
}
node.value = this.flowParseObjectTypeMethodish(
this.startNodeAt(node.start, node.loc.start),
);
if (kind === "get" || kind === "set") {
this.flowCheckGetterSetterParams(node);
}
} else {
if (kind !== "init") this.unexpected();
node.method = false;
if (this.eat(tt.question)) {
optional = true;
}
node.value = this.flowParseTypeInitialiser();
node.variance = variance;
}
node.optional = optional;
return this.finishNode(node, "ObjectTypeProperty");
}
}
// This is similar to checkGetterSetterParams, but as
// @babel/parser uses non estree properties we cannot reuse it here
flowCheckGetterSetterParams(
property: N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty,
): void {
const paramCount = property.kind === "get" ? 0 : 1;
const start = property.start;
const length =
property.value.params.length + (property.value.rest ? 1 : 0);
if (length !== paramCount) {
if (property.kind === "get") {
this.raise(start, Errors.BadGetterArity);
} else {
this.raise(start, Errors.BadSetterArity);
}
}
if (property.kind === "set" && property.value.rest) {
this.raise(start, Errors.BadSetterRestParameter);
}
}
flowObjectTypeSemicolon(): void {
if (
!this.eat(tt.semi) &&
!this.eat(tt.comma) &&
!this.match(tt.braceR) &&
!this.match(tt.braceBarR)
) {
this.unexpected();
}
}
flowParseQualifiedTypeIdentifier(
startPos?: number,
startLoc?: Position,
id?: N.Identifier,
): N.FlowQualifiedTypeIdentifier {
startPos = startPos || this.state.start;
startLoc = startLoc || this.state.startLoc;
let node = id || this.flowParseRestrictedIdentifier(true);
while (this.eat(tt.dot)) {
const node2 = this.startNodeAt(startPos, startLoc);
node2.qualification = node;
node2.id = this.flowParseRestrictedIdentifier(true);
node = this.finishNode(node2, "QualifiedTypeIdentifier");
}
return node;
}
flowParseGenericType(
startPos: number,
startLoc: Position,
id: N.Identifier,
): N.FlowGenericTypeAnnotation {
const node = this.startNodeAt(startPos, startLoc);
node.typeParameters = null;
node.id = this.flowParseQualifiedTypeIdentifier(startPos, startLoc, id);
if (this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterInstantiation();
}
return this.finishNode(node, "GenericTypeAnnotation");
}
flowParseTypeofType(): N.FlowTypeofTypeAnnotation {
const node = this.startNode();
this.expect(tt._typeof);
node.argument = this.flowParsePrimaryType();
return this.finishNode(node, "TypeofTypeAnnotation");
}
flowParseTupleType(): N.FlowTupleTypeAnnotation {
const node = this.startNode();
node.types = [];
this.expect(tt.bracketL);
// We allow trailing commas
while (this.state.pos < this.length && !this.match(tt.bracketR)) {
node.types.push(this.flowParseType());
if (this.match(tt.bracketR)) break;
this.expect(tt.comma);
}
this.expect(tt.bracketR);
return this.finishNode(node, "TupleTypeAnnotation");
}
flowParseFunctionTypeParam(): N.FlowFunctionTypeParam {
let name = null;
let optional = false;
let typeAnnotation = null;
const node = this.startNode();
const lh = this.lookahead();
if (lh.type === tt.colon || lh.type === tt.question) {
name = this.parseIdentifier();
if (this.eat(tt.question)) {
optional = true;
}
typeAnnotation = this.flowParseTypeInitialiser();
} else {
typeAnnotation = this.flowParseType();
}
node.name = name;
node.optional = optional;
node.typeAnnotation = typeAnnotation;
return this.finishNode(node, "FunctionTypeParam");
}
reinterpretTypeAsFunctionTypeParam(
type: N.FlowType,
): N.FlowFunctionTypeParam {
const node = this.startNodeAt(type.start, type.loc.start);
node.name = null;
node.optional = false;
node.typeAnnotation = type;
return this.finishNode(node, "FunctionTypeParam");
}
flowParseFunctionTypeParams(
params: N.FlowFunctionTypeParam[] = [],
): { params: N.FlowFunctionTypeParam[], rest: ?N.FlowFunctionTypeParam } {
let rest: ?N.FlowFunctionTypeParam = null;
while (!this.match(tt.parenR) && !this.match(tt.ellipsis)) {
params.push(this.flowParseFunctionTypeParam());
if (!this.match(tt.parenR)) {
this.expect(tt.comma);
}
}
if (this.eat(tt.ellipsis)) {
rest = this.flowParseFunctionTypeParam();
}
return { params, rest };
}
flowIdentToTypeAnnotation(
startPos: number,
startLoc: Position,
node: N.FlowTypeAnnotation,
id: N.Identifier,
): N.FlowTypeAnnotation {
switch (id.name) {
case "any":
return this.finishNode(node, "AnyTypeAnnotation");
case "bool":
case "boolean":
return this.finishNode(node, "BooleanTypeAnnotation");
case "mixed":
return this.finishNode(node, "MixedTypeAnnotation");
case "empty":
return this.finishNode(node, "EmptyTypeAnnotation");
case "number":
return this.finishNode(node, "NumberTypeAnnotation");
case "string":
return this.finishNode(node, "StringTypeAnnotation");
case "symbol":
return this.finishNode(node, "SymbolTypeAnnotation");
default:
this.checkNotUnderscore(id.name);
return this.flowParseGenericType(startPos, startLoc, id);
}
}
// The parsing of types roughly parallels the parsing of expressions, and
// primary types are kind of like primary expressions...they're the
// primitives with which other types are constructed.
flowParsePrimaryType(): N.FlowTypeAnnotation {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
const node = this.startNode();
let tmp;
let type;
let isGroupedType = false;
const oldNoAnonFunctionType = this.state.noAnonFunctionType;
switch (this.state.type) {
case tt.name:
if (this.isContextual("interface")) {
return this.flowParseInterfaceType();
}
return this.flowIdentToTypeAnnotation(
startPos,
startLoc,
node,
this.parseIdentifier(),
);
case tt.braceL:
return this.flowParseObjectType({
allowStatic: false,
allowExact: false,
allowSpread: true,
allowProto: false,
allowInexact: true,
});
case tt.braceBarL:
return this.flowParseObjectType({
allowStatic: false,
allowExact: true,
allowSpread: true,
allowProto: false,
allowInexact: false,
});
case tt.bracketL:
this.state.noAnonFunctionType = false;
type = this.flowParseTupleType();
this.state.noAnonFunctionType = oldNoAnonFunctionType;
return type;
case tt.relational:
if (this.state.value === "<") {
node.typeParameters = this.flowParseTypeParameterDeclaration();
this.expect(tt.parenL);
tmp = this.flowParseFunctionTypeParams();
node.params = tmp.params;
node.rest = tmp.rest;
this.expect(tt.parenR);
this.expect(tt.arrow);
node.returnType = this.flowParseType();
return this.finishNode(node, "FunctionTypeAnnotation");
}
break;
case tt.parenL:
this.next();
// Check to see if this is actually a grouped type
if (!this.match(tt.parenR) && !this.match(tt.ellipsis)) {
if (this.match(tt.name)) {
const token = this.lookahead().type;
isGroupedType = token !== tt.question && token !== tt.colon;
} else {
isGroupedType = true;
}
}
if (isGroupedType) {
this.state.noAnonFunctionType = false;
type = this.flowParseType();
this.state.noAnonFunctionType = oldNoAnonFunctionType;
// A `,` or a `) =>` means this is an anonymous function type
if (
this.state.noAnonFunctionType ||
!(
this.match(tt.comma) ||
(this.match(tt.parenR) && this.lookahead().type === tt.arrow)
)
) {
this.expect(tt.parenR);
return type;
} else {
// Eat a comma if there is one
this.eat(tt.comma);
}
}
if (type) {
tmp = this.flowParseFunctionTypeParams([
this.reinterpretTypeAsFunctionTypeParam(type),
]);
} else {
tmp = this.flowParseFunctionTypeParams();
}
node.params = tmp.params;
node.rest = tmp.rest;
this.expect(tt.parenR);
this.expect(tt.arrow);
node.returnType = this.flowParseType();
node.typeParameters = null;
return this.finishNode(node, "FunctionTypeAnnotation");
case tt.string:
return this.parseLiteral(
this.state.value,
"StringLiteralTypeAnnotation",
);
case tt._true:
case tt._false:
node.value = this.match(tt._true);
this.next();
return this.finishNode(node, "BooleanLiteralTypeAnnotation");
case tt.plusMin:
if (this.state.value === "-") {
this.next();
if (this.match(tt.num)) {
return this.parseLiteral(
-this.state.value,
"NumberLiteralTypeAnnotation",
node.start,
node.loc.start,
);
}
if (this.match(tt.bigint)) {
return this.parseLiteral(
-this.state.value,
"BigIntLiteralTypeAnnotation",
node.start,
node.loc.start,
);
}
throw this.raise(
this.state.start,
FlowErrors.UnexpectedSubtractionOperand,
);
}
throw this.unexpected();
case tt.num:
return this.parseLiteral(
this.state.value,
"NumberLiteralTypeAnnotation",
);
case tt.bigint:
return this.parseLiteral(
this.state.value,
"BigIntLiteralTypeAnnotation",
);
case tt._void:
this.next();
return this.finishNode(node, "VoidTypeAnnotation");
case tt._null:
this.next();
return this.finishNode(node, "NullLiteralTypeAnnotation");
case tt._this:
this.next();
return this.finishNode(node, "ThisTypeAnnotation");
case tt.star:
this.next();
return this.finishNode(node, "ExistsTypeAnnotation");
default:
if (this.state.type.keyword === "typeof") {
return this.flowParseTypeofType();
} else if (this.state.type.keyword) {
const label = this.state.type.label;
this.next();
return super.createIdentifier(node, label);
}
}
throw this.unexpected();
}
flowParsePostfixType(): N.FlowTypeAnnotation {
const startPos = this.state.start,
startLoc = this.state.startLoc;
let type = this.flowParsePrimaryType();
while (this.match(tt.bracketL) && !this.canInsertSemicolon()) {
const node = this.startNodeAt(startPos, startLoc);
node.elementType = type;
this.expect(tt.bracketL);
this.expect(tt.bracketR);
type = this.finishNode(node, "ArrayTypeAnnotation");
}
return type;
}
flowParsePrefixType(): N.FlowTypeAnnotation {
const node = this.startNode();
if (this.eat(tt.question)) {
node.typeAnnotation = this.flowParsePrefixType();
return this.finishNode(node, "NullableTypeAnnotation");
} else {
return this.flowParsePostfixType();
}
}
flowParseAnonFunctionWithoutParens(): N.FlowTypeAnnotation {
const param = this.flowParsePrefixType();
if (!this.state.noAnonFunctionType && this.eat(tt.arrow)) {
// TODO: This should be a type error. Passing in a SourceLocation, and it expects a Position.
const node = this.startNodeAt(param.start, param.loc.start);
node.params = [this.reinterpretTypeAsFunctionTypeParam(param)];
node.rest = null;
node.returnType = this.flowParseType();
node.typeParameters = null;
return this.finishNode(node, "FunctionTypeAnnotation");
}
return param;
}
flowParseIntersectionType(): N.FlowTypeAnnotation {
const node = this.startNode();
this.eat(tt.bitwiseAND);
const type = this.flowParseAnonFunctionWithoutParens();
node.types = [type];
while (this.eat(tt.bitwiseAND)) {
node.types.push(this.flowParseAnonFunctionWithoutParens());
}
return node.types.length === 1
? type
: this.finishNode(node, "IntersectionTypeAnnotation");
}
flowParseUnionType(): N.FlowTypeAnnotation {
const node = this.startNode();
this.eat(tt.bitwiseOR);
const type = this.flowParseIntersectionType();
node.types = [type];
while (this.eat(tt.bitwiseOR)) {
node.types.push(this.flowParseIntersectionType());
}
return node.types.length === 1
? type
: this.finishNode(node, "UnionTypeAnnotation");
}
flowParseType(): N.FlowTypeAnnotation {
const oldInType = this.state.inType;
this.state.inType = true;
const type = this.flowParseUnionType();
this.state.inType = oldInType;
// Ensure that a brace after a function generic type annotation is a
// statement, except in arrow functions (noAnonFunctionType)
this.state.exprAllowed =
this.state.exprAllowed || this.state.noAnonFunctionType;
return type;
}
flowParseTypeOrImplicitInstantiation(): N.FlowTypeAnnotation {
if (this.state.type === tt.name && this.state.value === "_") {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
const node = this.parseIdentifier();
return this.flowParseGenericType(startPos, startLoc, node);
} else {
return this.flowParseType();
}
}
flowParseTypeAnnotation(): N.FlowTypeAnnotation {
const node = this.startNode();
node.typeAnnotation = this.flowParseTypeInitialiser();
return this.finishNode(node, "TypeAnnotation");
}
flowParseTypeAnnotatableIdentifier(
allowPrimitiveOverride?: boolean,
): N.Identifier {
const ident = allowPrimitiveOverride
? this.parseIdentifier()
: this.flowParseRestrictedIdentifier();
if (this.match(tt.colon)) {
ident.typeAnnotation = this.flowParseTypeAnnotation();
this.resetEndLocation(ident);
}
return ident;
}
typeCastToParameter(node: N.Node): N.Node {
node.expression.typeAnnotation = node.typeAnnotation;
this.resetEndLocation(
node.expression,
node.typeAnnotation.end,
node.typeAnnotation.loc.end,
);
return node.expression;
}
flowParseVariance(): ?N.FlowVariance {
let variance = null;
if (this.match(tt.plusMin)) {
variance = this.startNode();
if (this.state.value === "+") {
variance.kind = "plus";
} else {
variance.kind = "minus";
}
this.next();
this.finishNode(variance, "Variance");
}
return variance;
}
// ==================================
// Overrides
// ==================================
parseFunctionBody(
node: N.Function,
allowExpressionBody: ?boolean,
isMethod?: boolean = false,
): void {
if (allowExpressionBody) {
return this.forwardNoArrowParamsConversionAt(node, () =>
super.parseFunctionBody(node, true, isMethod),
);
}
return super.parseFunctionBody(node, false, isMethod);
}
parseFunctionBodyAndFinish(
node: N.BodilessFunctionOrMethodBase,
type: string,
isMethod?: boolean = false,
): void {
if (this.match(tt.colon)) {
const typeNode = this.startNode();
[
// $FlowFixMe (destructuring not supported yet)
typeNode.typeAnnotation,
// $FlowFixMe (destructuring not supported yet)
node.predicate,
] = this.flowParseTypeAndPredicateInitialiser();
node.returnType = typeNode.typeAnnotation
? this.finishNode(typeNode, "TypeAnnotation")
: null;
}
super.parseFunctionBodyAndFinish(node, type, isMethod);
}
// interfaces and enums
parseStatement(context: ?string, topLevel?: boolean): N.Statement {
// strict mode handling of `interface` since it's a reserved word
if (
this.state.strict &&
this.match(tt.name) &&
this.state.value === "interface"
) {
const node = this.startNode();
this.next();
return this.flowParseInterface(node);
} else if (this.shouldParseEnums() && this.isContextual("enum")) {
const node = this.startNode();
this.next();
return this.flowParseEnumDeclaration(node);
} else {
const stmt = super.parseStatement(context, topLevel);
// We will parse a flow pragma in any comment before the first statement.
if (this.flowPragma === undefined && !this.isValidDirective(stmt)) {
this.flowPragma = null;
}
return stmt;
}
}
// declares, interfaces and type aliases
parseExpressionStatement(
node: N.ExpressionStatement,
expr: N.Expression,
): N.ExpressionStatement {
if (expr.type === "Identifier") {
if (expr.name === "declare") {
if (
this.match(tt._class) ||
this.match(tt.name) ||
this.match(tt._function) ||
this.match(tt._var) ||
this.match(tt._export)
) {
return this.flowParseDeclare(node);
}
} else if (this.match(tt.name)) {
if (expr.name === "interface") {
return this.flowParseInterface(node);
} else if (expr.name === "type") {
return this.flowParseTypeAlias(node);
} else if (expr.name === "opaque") {
return this.flowParseOpaqueType(node, false);
}
}
}
return super.parseExpressionStatement(node, expr);
}
// export type
shouldParseExportDeclaration(): boolean {
return (
this.isContextual("type") ||
this.isContextual("interface") ||
this.isContextual("opaque") ||
(this.shouldParseEnums() && this.isContextual("enum")) ||
super.shouldParseExportDeclaration()
);
}
isExportDefaultSpecifier(): boolean {
if (
this.match(tt.name) &&
(this.state.value === "type" ||
this.state.value === "interface" ||
this.state.value === "opaque" ||
(this.shouldParseEnums() && this.state.value === "enum"))
) {
return false;
}
return super.isExportDefaultSpecifier();
}
parseExportDefaultExpression(): N.Expression | N.Declaration {
if (this.shouldParseEnums() && this.isContextual("enum")) {
const node = this.startNode();
this.next();
return this.flowParseEnumDeclaration(node);
}
return super.parseExportDefaultExpression();
}
parseConditional(
expr: N.Expression,
noIn: ?boolean,
startPos: number,
startLoc: Position,
refNeedsArrowPos?: ?Pos,
): N.Expression {
if (!this.match(tt.question)) return expr;
// only use the expensive "tryParse" method if there is a question mark
// and if we come from inside parens
if (refNeedsArrowPos) {
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;
const node = this.startNodeAt(startPos, startLoc);
let { consequent, failed } = this.tryParseConditionalConsequent();
let [valid, invalid] = this.getArrowLikeExpressions(consequent);
if (failed || invalid.length > 0) {
const noArrowAt = [...originalNoArrowAt];
if (invalid.length > 0) {
this.state = state;
this.state.noArrowAt = noArrowAt;
for (let i = 0; i < invalid.length; i++) {
noArrowAt.push(invalid[i].start);
}
({ consequent, failed } = this.tryParseConditionalConsequent());
[valid, invalid] = this.getArrowLikeExpressions(consequent);
}
if (failed && valid.length > 1) {
// if there are two or more possible correct ways of parsing, throw an
// error.
// e.g. Source: a ? (b): c => (d): e => f
// Result 1: a ? b : (c => ((d): e => f))
// Result 2: a ? ((b): c => d) : (e => f)
this.raise(state.start, FlowErrors.AmbiguousConditionalArrow);
}
if (failed && valid.length === 1) {
this.state = state;
this.state.noArrowAt = noArrowAt.concat(valid[0].start);
({ consequent, failed } = this.tryParseConditionalConsequent());
}
}
this.getArrowLikeExpressions(consequent, true);
this.state.noArrowAt = originalNoArrowAt;
this.expect(tt.colon);
node.test = expr;
node.consequent = consequent;
node.alternate = this.forwardNoArrowParamsConversionAt(node, () =>
this.parseMaybeAssign(noIn, undefined, undefined, undefined),
);
return this.finishNode(node, "ConditionalExpression");
}
tryParseConditionalConsequent(): {
consequent: N.Expression,
failed: boolean,
} {
this.state.noArrowParamsConversionAt.push(this.state.start);
const consequent = this.parseMaybeAssign();
const failed = !this.match(tt.colon);
this.state.noArrowParamsConversionAt.pop();
return { consequent, failed };
}
// Given an expression, walks through out its arrow functions whose body is
// an expression and through out conditional expressions. It returns every
// function which has been parsed with a return type but could have been
// parenthesized expressions.
// These functions are separated into two arrays: one containing the ones
// whose parameters can be converted to assignable lists, one containing the
// others.
getArrowLikeExpressions(
node: N.Expression,
disallowInvalid?: boolean,
): [N.ArrowFunctionExpression[], N.ArrowFunctionExpression[]] {
const stack = [node];
const arrows: N.ArrowFunctionExpression[] = [];
while (stack.length !== 0) {
const node = stack.pop();
if (node.type === "ArrowFunctionExpression") {
if (node.typeParameters || !node.returnType) {
// This is an arrow expression without ambiguity, so check its parameters
this.finishArrowValidation(node);
} else {
arrows.push(node);
}
stack.push(node.body);
} else if (node.type === "ConditionalExpression") {
stack.push(node.consequent);
stack.push(node.alternate);
}
}
if (disallowInvalid) {
arrows.forEach(node => this.finishArrowValidation(node));
return [arrows, []];
}
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[]),
node.extra?.trailingComma,
);
// Enter scope, as checkParams defines bindings
this.scope.enter(SCOPE_FUNCTION | 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 {
let result: T;
if (this.state.noArrowParamsConversionAt.indexOf(node.start) !== -1) {
this.state.noArrowParamsConversionAt.push(this.state.start);
result = parse();
this.state.noArrowParamsConversionAt.pop();
} else {
result = parse();
}
return result;
}
parseParenItem(
node: N.Expression,
startPos: number,
startLoc: Position,
): N.Expression {
node = super.parseParenItem(node, startPos, startLoc);
if (this.eat(tt.question)) {
node.optional = true;
// Include questionmark in location of node
// Don't use this.finishNode() as otherwise we might process comments twice and
// include already consumed parens
this.resetEndLocation(node);
}
if (this.match(tt.colon)) {
const typeCastNode = this.startNodeAt(startPos, startLoc);
typeCastNode.expression = node;
typeCastNode.typeAnnotation = this.flowParseTypeAnnotation();
return this.finishNode(typeCastNode, "TypeCastExpression");
}
return node;
}
assertModuleNodeAllowed(node: N.Node) {
if (
(node.type === "ImportDeclaration" &&
(node.importKind === "type" || node.importKind === "typeof")) ||
(node.type === "ExportNamedDeclaration" &&
node.exportKind === "type") ||
(node.type === "ExportAllDeclaration" && node.exportKind === "type")
) {
// Allow Flowtype imports and exports in all conditions because
// Flow itself does not care about 'sourceType'.
return;
}
super.assertModuleNodeAllowed(node);
}
parseExport(node: N.Node): N.AnyExport {
const decl = super.parseExport(node);
if (
decl.type === "ExportNamedDeclaration" ||
decl.type === "ExportAllDeclaration"
) {
decl.exportKind = decl.exportKind || "value";
}
return decl;
}
parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration {
if (this.isContextual("type")) {
node.exportKind = "type";
const declarationNode = this.startNode();
this.next();
if (this.match(tt.braceL)) {
// export type { foo, bar };
node.specifiers = this.parseExportSpecifiers();
this.parseExportFrom(node);
return null;
} else {
// export type Foo = Bar;
return this.flowParseTypeAlias(declarationNode);
}
} else if (this.isContextual("opaque")) {
node.exportKind = "type";
const declarationNode = this.startNode();
this.next();
// export opaque type Foo = Bar;
return this.flowParseOpaqueType(declarationNode, false);
} else if (this.isContextual("interface")) {
node.exportKind = "type";
const declarationNode = this.startNode();
this.next();
return this.flowParseInterface(declarationNode);
} else if (this.shouldParseEnums() && this.isContextual("enum")) {
node.exportKind = "value";
const declarationNode = this.startNode();
this.next();
return this.flowParseEnumDeclaration(declarationNode);
} else {
return super.parseExportDeclaration(node);
}
}
eatExportStar(node: N.Node): boolean {
if (super.eatExportStar(...arguments)) return true;
if (this.isContextual("type") && this.lookahead().type === tt.star) {
node.exportKind = "type";
this.next();
this.next();
return true;
}
return false;
}
maybeParseExportNamespaceSpecifier(node: N.Node): boolean {
const pos = this.state.start;
const hasNamespace = super.maybeParseExportNamespaceSpecifier(node);
if (hasNamespace && node.exportKind === "type") {
this.unexpected(pos);
}
return hasNamespace;
}
parseClassId(node: N.Class, isStatement: boolean, optionalId: ?boolean) {
super.parseClassId(node, isStatement, optionalId);
if (this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterDeclaration();
}
}
parseClassMember(
classBody: N.ClassBody,
member: any,
state: { hadConstructor: boolean },
constructorAllowsSuper: boolean,
): void {
const pos = this.state.start;
if (this.isContextual("declare")) {
if (this.parseClassMemberFromModifier(classBody, member)) {
// 'declare' is a class element name
return;
}
member.declare = true;
}
super.parseClassMember(classBody, member, state, constructorAllowsSuper);
if (member.declare) {
if (
member.type !== "ClassProperty" &&
member.type !== "ClassPrivateProperty"
) {
this.raise(pos, FlowErrors.DeclareClassElement);
} else if (member.value) {
this.raise(
member.value.start,
FlowErrors.DeclareClassFieldInitializer,
);
}
}
}
// ensure that inside flow types, we bypass the jsx parser plugin
getTokenFromCode(code: number): void {
const next = this.input.charCodeAt(this.state.pos + 1);
if (code === charCodes.leftCurlyBrace && next === charCodes.verticalBar) {
return this.finishOp(tt.braceBarL, 2);
} else if (
this.state.inType &&
(code === charCodes.greaterThan || code === charCodes.lessThan)
) {
return this.finishOp(tt.relational, 1);
} else if (isIteratorStart(code, next)) {
this.state.isIterator = true;
return super.readWord();
} else {
return super.getTokenFromCode(code);
}
}
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":
case "TypeCastExpression":
return this.isAssignable(node.expression);
case "MemberExpression":
case "OptionalMemberExpression":
return !isBinding;
default:
return false;
}
}
toAssignable(node: N.Node): N.Node {
if (node.type === "TypeCastExpression") {
return super.toAssignable(this.typeCastToParameter(node));
} else {
return super.toAssignable(node);
}
}
// turn type casts that we found in function parameter head into type annotated params
toAssignableList(
exprList: N.Expression[],
trailingCommaPos?: ?number,
): $ReadOnlyArray<N.Pattern> {
for (let i = 0; i < exprList.length; i++) {
const expr = exprList[i];
if (expr?.type === "TypeCastExpression") {
exprList[i] = this.typeCastToParameter(expr);
}
}
return super.toAssignableList(exprList, trailingCommaPos);
}
// this is a list of nodes, from something like a call expression, we need to filter the
// type casts that we've found that are illegal in this context
toReferencedList(
exprList: $ReadOnlyArray<?N.Expression>,
isParenthesizedExpr?: boolean,
): $ReadOnlyArray<?N.Expression> {
for (let i = 0; i < exprList.length; i++) {
const expr = exprList[i];
if (
expr &&
expr.type === "TypeCastExpression" &&
!expr.extra?.parenthesized &&
(exprList.length > 1 || !isParenthesizedExpr)
) {
this.raise(expr.typeAnnotation.start, FlowErrors.TypeCastInPattern);
}
}
return exprList;
}
checkLVal(
expr: N.Expression,
bindingType: BindingTypes = BIND_NONE,
checkClashes: ?{ [key: string]: boolean },
contextDescription: string,
): void {
if (expr.type !== "TypeCastExpression") {
return super.checkLVal(
expr,
bindingType,
checkClashes,
contextDescription,
);
}
}
// parse class property type annotations
parseClassProperty(node: N.ClassProperty): N.ClassProperty {
if (this.match(tt.colon)) {
node.typeAnnotation = this.flowParseTypeAnnotation();
}
return super.parseClassProperty(node);
}
parseClassPrivateProperty(
node: N.ClassPrivateProperty,
): N.ClassPrivateProperty {
if (this.match(tt.colon)) {
node.typeAnnotation = this.flowParseTypeAnnotation();
}
return super.parseClassPrivateProperty(node);
}
// determine whether or not we're currently in the position where a class method would appear
isClassMethod(): boolean {
return this.isRelational("<") || super.isClassMethod();
}
// determine whether or not we're currently in the position where a class property would appear
isClassProperty(): boolean {
return this.match(tt.colon) || super.isClassProperty();
}
isNonstaticConstructor(method: N.ClassMethod | N.ClassProperty): boolean {
return !this.match(tt.colon) && super.isNonstaticConstructor(method);
}
// parse type parameters for class methods
pushClassMethod(
classBody: N.ClassBody,
method: N.ClassMethod,
isGenerator: boolean,
isAsync: boolean,
isConstructor: boolean,
allowsDirectSuper: boolean,
): void {
if ((method: $FlowFixMe).variance) {
this.unexpected((method: $FlowFixMe).variance.start);
}
delete (method: $FlowFixMe).variance;
if (this.isRelational("<")) {
method.typeParameters = this.flowParseTypeParameterDeclaration();
}
super.pushClassMethod(
classBody,
method,
isGenerator,
isAsync,
isConstructor,
allowsDirectSuper,
);
}
pushClassPrivateMethod(
classBody: N.ClassBody,
method: N.ClassPrivateMethod,
isGenerator: boolean,
isAsync: boolean,
): void {
if ((method: $FlowFixMe).variance) {
this.unexpected((method: $FlowFixMe).variance.start);
}
delete (method: $FlowFixMe).variance;
if (this.isRelational("<")) {
method.typeParameters = this.flowParseTypeParameterDeclaration();
}
super.pushClassPrivateMethod(classBody, method, isGenerator, isAsync);
}
// parse a the super class type parameters and implements
parseClassSuper(node: N.Class): void {
super.parseClassSuper(node);
if (node.superClass && this.isRelational("<")) {
node.superTypeParameters = this.flowParseTypeParameterInstantiation();
}
if (this.isContextual("implements")) {
this.next();
const implemented: N.FlowClassImplements[] = (node.implements = []);
do {
const node = this.startNode();
node.id = this.flowParseRestrictedIdentifier(/*liberal*/ true);
if (this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterInstantiation();
} else {
node.typeParameters = null;
}
implemented.push(this.finishNode(node, "ClassImplements"));
} while (this.eat(tt.comma));
}
}
parsePropertyName(
node: N.ObjectOrClassMember | N.ClassMember | N.TsNamedTypeElementBase,
isPrivateNameAllowed: boolean,
): N.Identifier {
const variance = this.flowParseVariance();
const key = super.parsePropertyName(node, isPrivateNameAllowed);
// $FlowIgnore ("variance" not defined on TsNamedTypeElementBase)
node.variance = variance;
return key;
}
// parse type parameters for object method shorthand
parseObjPropValue(
prop: N.ObjectMember,
startPos: ?number,
startLoc: ?Position,
isGenerator: boolean,
isAsync: boolean,
isPattern: boolean,
refExpressionErrors: ?ExpressionErrors,
containsEsc: boolean,
): void {
if ((prop: $FlowFixMe).variance) {
this.unexpected((prop: $FlowFixMe).variance.start);
}
delete (prop: $FlowFixMe).variance;
let typeParameters;
// method shorthand
if (this.isRelational("<")) {
typeParameters = this.flowParseTypeParameterDeclaration();
if (!this.match(tt.parenL)) this.unexpected();
}
super.parseObjPropValue(
prop,
startPos,
startLoc,
isGenerator,
isAsync,
isPattern,
refExpressionErrors,
containsEsc,
);
// add typeParameters if we found them
if (typeParameters) {
(prop.value || prop).typeParameters = typeParameters;
}
}
parseAssignableListItemTypes(param: N.Pattern): N.Pattern {
if (this.eat(tt.question)) {
if (param.type !== "Identifier") {
this.raise(param.start, FlowErrors.OptionalBindingPattern);
}
((param: any): N.Identifier).optional = true;
}
if (this.match(tt.colon)) {
param.typeAnnotation = this.flowParseTypeAnnotation();
}
this.resetEndLocation(param);
return param;
}
parseMaybeDefault(
startPos?: ?number,
startLoc?: ?Position,
left?: ?N.Pattern,
): N.Pattern {
const node = super.parseMaybeDefault(startPos, startLoc, left);
if (
node.type === "AssignmentPattern" &&
node.typeAnnotation &&
node.right.start < node.typeAnnotation.start
) {
this.raise(node.typeAnnotation.start, FlowErrors.TypeBeforeInitializer);
}
return node;
}
shouldParseDefaultImport(node: N.ImportDeclaration): boolean {
if (!hasTypeImportKind(node)) {
return super.shouldParseDefaultImport(node);
}
return isMaybeDefaultImport(this.state);
}
parseImportSpecifierLocal(
node: N.ImportDeclaration,
specifier: N.Node,
type: string,
contextDescription: string,
): void {
specifier.local = hasTypeImportKind(node)
? this.flowParseRestrictedIdentifier(
/* liberal */ true,
/* declaration */ true,
)
: this.parseIdentifier();
this.checkLVal(
specifier.local,
BIND_LEXICAL,
undefined,
contextDescription,
);
node.specifiers.push(this.finishNode(specifier, type));
}
// parse typeof and type imports
maybeParseDefaultImportSpecifier(node: N.ImportDeclaration): boolean {
node.importKind = "value";
let kind = null;
if (this.match(tt._typeof)) {
kind = "typeof";
} else if (this.isContextual("type")) {
kind = "type";
}
if (kind) {
const lh = this.lookahead();
// import type * is not allowed
if (kind === "type" && lh.type === tt.star) {
this.unexpected(lh.start);
}
if (
isMaybeDefaultImport(lh) ||
lh.type === tt.braceL ||
lh.type === tt.star
) {
this.next();
node.importKind = kind;
}
}
return super.maybeParseDefaultImportSpecifier(node);
}
// parse import-type/typeof shorthand
parseImportSpecifier(node: N.ImportDeclaration): void {
const specifier = this.startNode();
const firstIdentLoc = this.state.start;
const firstIdent = this.parseIdentifier(true);
let specifierTypeKind = null;
if (firstIdent.name === "type") {
specifierTypeKind = "type";
} else if (firstIdent.name === "typeof") {
specifierTypeKind = "typeof";
}
let isBinding = false;
if (this.isContextual("as") && !this.isLookaheadContextual("as")) {
const as_ident = this.parseIdentifier(true);
if (
specifierTypeKind !== null &&
!this.match(tt.name) &&
!this.state.type.keyword
) {
// `import {type as ,` or `import {type as }`
specifier.imported = as_ident;
specifier.importKind = specifierTypeKind;
specifier.local = as_ident.__clone();
} else {
// `import {type as foo`
specifier.imported = firstIdent;
specifier.importKind = null;
specifier.local = this.parseIdentifier();
}
} else if (
specifierTypeKind !== null &&
(this.match(tt.name) || this.state.type.keyword)
) {
// `import {type foo`
specifier.imported = this.parseIdentifier(true);
specifier.importKind = specifierTypeKind;
if (this.eatContextual("as")) {
specifier.local = this.parseIdentifier();
} else {
isBinding = true;
specifier.local = specifier.imported.__clone();
}
} else {
isBinding = true;
specifier.imported = firstIdent;
specifier.importKind = null;
specifier.local = specifier.imported.__clone();
}
const nodeIsTypeImport = hasTypeImportKind(node);
const specifierIsTypeImport = hasTypeImportKind(specifier);
if (nodeIsTypeImport && specifierIsTypeImport) {
this.raise(
firstIdentLoc,
FlowErrors.ImportTypeShorthandOnlyInPureImport,
);
}
if (nodeIsTypeImport || specifierIsTypeImport) {
this.checkReservedType(
specifier.local.name,
specifier.local.start,
/* declaration */ true,
);
}
if (isBinding && !nodeIsTypeImport && !specifierIsTypeImport) {
this.checkReservedWord(
specifier.local.name,
specifier.start,
true,
true,
);
}
this.checkLVal(
specifier.local,
BIND_LEXICAL,
undefined,
"import specifier",
);
node.specifiers.push(this.finishNode(specifier, "ImportSpecifier"));
}
// parse function type parameters - function foo<T>() {}
parseFunctionParams(node: N.Function, allowModifiers?: boolean): void {
// $FlowFixMe
const kind = node.kind;
if (kind !== "get" && kind !== "set" && this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterDeclaration();
}
super.parseFunctionParams(node, allowModifiers);
}
// parse flow type annotations on variable declarator heads - let foo: string = bar
parseVarId(
decl: N.VariableDeclarator,
kind: "var" | "let" | "const",
): void {
super.parseVarId(decl, kind);
if (this.match(tt.colon)) {
decl.id.typeAnnotation = this.flowParseTypeAnnotation();
this.resetEndLocation(decl.id); // set end position to end of type
}
}
// parse the return type of an async arrow function - let foo = (async (): number => {});
parseAsyncArrowFromCallExpression(
node: N.ArrowFunctionExpression,
call: N.CallExpression,
): N.ArrowFunctionExpression {
if (this.match(tt.colon)) {
const oldNoAnonFunctionType = this.state.noAnonFunctionType;
this.state.noAnonFunctionType = true;
node.returnType = this.flowParseTypeAnnotation();
this.state.noAnonFunctionType = oldNoAnonFunctionType;
}
return super.parseAsyncArrowFromCallExpression(node, call);
}
// todo description
shouldParseAsyncArrow(): boolean {
return this.match(tt.colon) || super.shouldParseAsyncArrow();
}
// We need to support type parameter declarations for arrow functions. This
// is tricky. There are three situations we need to handle
//
// 1. This is either JSX or an arrow function. We'll try JSX first. If that
// fails, we'll try an arrow function. If that fails, we'll throw the JSX
// error.
// 2. This is an arrow function. We'll parse the type parameter declaration,
// parse the rest, make sure the rest is an arrow function, and go from
// there
// 3. This is neither. Just call the super method
parseMaybeAssign(
noIn?: ?boolean,
refExpressionErrors?: ?ExpressionErrors,
afterLeftParse?: Function,
refNeedsArrowPos?: ?Pos,
): N.Expression {
let state = null;
let jsx;
if (
this.hasPlugin("jsx") &&
(this.match(tt.jsxTagStart) || this.isRelational("<"))
) {
state = this.state.clone();
jsx = this.tryParse(
() =>
super.parseMaybeAssign(
noIn,
refExpressionErrors,
afterLeftParse,
refNeedsArrowPos,
),
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] === tc.j_oTag) {
context.length -= 2;
} else if (context[context.length - 1] === tc.j_expr) {
context.length -= 1;
}
}
if (jsx?.error || this.isRelational("<")) {
state = state || this.state.clone();
let typeParameters;
const arrow = this.tryParse(() => {
typeParameters = this.flowParseTypeParameterDeclaration();
const arrowExpression = this.forwardNoArrowParamsConversionAt(
typeParameters,
() =>
super.parseMaybeAssign(
noIn,
refExpressionErrors,
afterLeftParse,
refNeedsArrowPos,
),
);
arrowExpression.typeParameters = typeParameters;
this.resetStartLocationFromNode(arrowExpression, typeParameters);
return arrowExpression;
}, state);
const arrowExpression: ?N.ArrowFunctionExpression =
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?.node) {
/*:: invariant(jsx.failState) */
this.state = jsx.failState;
return jsx.node;
}
if (arrowExpression) {
/*:: invariant(arrow.failState) */
this.state = arrow.failState;
return arrowExpression;
}
if (jsx?.thrown) throw jsx.error;
if (arrow.thrown) throw arrow.error;
/*:: invariant(typeParameters) */
throw this.raise(
typeParameters.start,
FlowErrors.UnexpectedTokenAfterTypeParameter,
);
}
return super.parseMaybeAssign(
noIn,
refExpressionErrors,
afterLeftParse,
refNeedsArrowPos,
);
}
// handle return types for arrow functions
parseArrow(node: N.ArrowFunctionExpression): ?N.ArrowFunctionExpression {
if (this.match(tt.colon)) {
const result = this.tryParse(() => {
const oldNoAnonFunctionType = this.state.noAnonFunctionType;
this.state.noAnonFunctionType = true;
const typeNode = this.startNode();
[
// $FlowFixMe (destructuring not supported yet)
typeNode.typeAnnotation,
// $FlowFixMe (destructuring not supported yet)
node.predicate,
] = this.flowParseTypeAndPredicateInitialiser();
this.state.noAnonFunctionType = oldNoAnonFunctionType;
if (this.canInsertSemicolon()) this.unexpected();
if (!this.match(tt.arrow)) this.unexpected();
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);
}
shouldParseArrow(): boolean {
return this.match(tt.colon) || super.shouldParseArrow();
}
setArrowFunctionParameters(
node: N.ArrowFunctionExpression,
params: N.Expression[],
): void {
if (this.state.noArrowParamsConversionAt.indexOf(node.start) !== -1) {
node.params = params;
} else {
super.setArrowFunctionParameters(node, params);
}
}
checkParams(
node: N.Function,
allowDuplicates: boolean,
isArrowFunction: ?boolean,
): void {
if (
isArrowFunction &&
this.state.noArrowParamsConversionAt.indexOf(node.start) !== -1
) {
return;
}
return super.checkParams(...arguments);
}
parseParenAndDistinguishExpression(canBeArrow: boolean): N.Expression {
return super.parseParenAndDistinguishExpression(
canBeArrow && this.state.noArrowAt.indexOf(this.state.start) === -1,
);
}
parseSubscripts(
base: N.Expression,
startPos: number,
startLoc: Position,
noCalls?: ?boolean,
): N.Expression {
if (
base.type === "Identifier" &&
base.name === "async" &&
this.state.noArrowAt.indexOf(startPos) !== -1
) {
this.next();
const node = this.startNodeAt(startPos, startLoc);
node.callee = base;
node.arguments = this.parseCallExpressionArguments(tt.parenR, false);
base = this.finishNode(node, "CallExpression");
} else if (
base.type === "Identifier" &&
base.name === "async" &&
this.isRelational("<")
) {
const state = this.state.clone();
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;
}
if (result.node) {
this.state = result.failState;
return result.node;
}
throw arrow.error || result.error;
}
return super.parseSubscripts(base, startPos, startLoc, noCalls);
}
parseSubscript(
base: N.Expression,
startPos: number,
startLoc: Position,
noCalls: ?boolean,
subscriptState: N.ParseSubscriptState,
): N.Expression {
if (this.match(tt.questionDot) && this.isLookaheadRelational("<")) {
subscriptState.optionalChainMember = true;
if (noCalls) {
subscriptState.stop = true;
return base;
}
this.next();
const node: N.OptionalCallExpression = this.startNodeAt(
startPos,
startLoc,
);
node.callee = base;
node.typeArguments = this.flowParseTypeParameterInstantiation();
this.expect(tt.parenL);
// $FlowFixMe
node.arguments = this.parseCallExpressionArguments(tt.parenR, false);
node.optional = true;
return this.finishCallExpression(node, /* optional */ true);
} else if (
!noCalls &&
this.shouldParseTypes() &&
this.isRelational("<")
) {
const node = this.startNodeAt(startPos, startLoc);
node.callee = base;
const result = this.tryParse(() => {
node.typeArguments = this.flowParseTypeParameterInstantiationCallOrNew();
this.expect(tt.parenL);
node.arguments = this.parseCallExpressionArguments(tt.parenR, false);
if (subscriptState.optionalChainMember) node.optional = false;
return this.finishCallExpression(
node,
subscriptState.optionalChainMember,
);
});
if (result.node) {
if (result.error) this.state = result.failState;
return result.node;
}
}
return super.parseSubscript(
base,
startPos,
startLoc,
noCalls,
subscriptState,
);
}
parseNewArguments(node: N.NewExpression): void {
let targs = null;
if (this.shouldParseTypes() && this.isRelational("<")) {
targs = this.tryParse(() =>
this.flowParseTypeParameterInstantiationCallOrNew(),
).node;
}
node.typeArguments = targs;
super.parseNewArguments(node);
}
parseAsyncArrowWithTypeParameters(
startPos: number,
startLoc: Position,
): ?N.ArrowFunctionExpression {
const node = this.startNodeAt(startPos, startLoc);
this.parseFunctionParams(node);
if (!this.parseArrow(node)) return;
return this.parseArrowExpression(
node,
/* params */ undefined,
/* isAsync */ true,
);
}
readToken_mult_modulo(code: number): void {
const next = this.input.charCodeAt(this.state.pos + 1);
if (
code === charCodes.asterisk &&
next === charCodes.slash &&
this.state.hasFlowComment
) {
this.state.hasFlowComment = false;
this.state.pos += 2;
this.nextToken();
return;
}
super.readToken_mult_modulo(code);
}
readToken_pipe_amp(code: number): void {
const next = this.input.charCodeAt(this.state.pos + 1);
if (
code === charCodes.verticalBar &&
next === charCodes.rightCurlyBrace
) {
// '|}'
this.finishOp(tt.braceBarR, 2);
return;
}
super.readToken_pipe_amp(code);
}
parseTopLevel(file: N.File, program: N.Program): N.File {
const fileNode = super.parseTopLevel(file, program);
if (this.state.hasFlowComment) {
this.raise(this.state.pos, FlowErrors.UnterminatedFlowComment);
}
return fileNode;
}
skipBlockComment(): void {
if (this.hasPlugin("flowComments") && this.skipFlowComment()) {
if (this.state.hasFlowComment) {
this.unexpected(null, FlowErrors.NestedFlowComment);
}
this.hasFlowCommentCompletion();
this.state.pos += this.skipFlowComment();
this.state.hasFlowComment = true;
return;
}
if (this.state.hasFlowComment) {
const end = this.input.indexOf("*-/", (this.state.pos += 2));
if (end === -1) {
throw this.raise(this.state.pos - 2, Errors.UnterminatedComment);
}
this.state.pos = end + 3;
return;
}
super.skipBlockComment();
}
skipFlowComment(): number | boolean {
const { pos } = this.state;
let shiftToFirstNonWhiteSpace = 2;
while (
[charCodes.space, charCodes.tab].includes(
this.input.charCodeAt(pos + shiftToFirstNonWhiteSpace),
)
) {
shiftToFirstNonWhiteSpace++;
}
const ch2 = this.input.charCodeAt(shiftToFirstNonWhiteSpace + pos);
const ch3 = this.input.charCodeAt(shiftToFirstNonWhiteSpace + pos + 1);
if (ch2 === charCodes.colon && ch3 === charCodes.colon) {
return shiftToFirstNonWhiteSpace + 2; // check for /*::
}
if (
this.input.slice(
shiftToFirstNonWhiteSpace + pos,
shiftToFirstNonWhiteSpace + pos + 12,
) === "flow-include"
) {
return shiftToFirstNonWhiteSpace + 12; // check for /*flow-include
}
if (ch2 === charCodes.colon && ch3 !== charCodes.colon) {
return shiftToFirstNonWhiteSpace; // check for /*:, advance up to :
}
return false;
}
hasFlowCommentCompletion(): void {
const end = this.input.indexOf("*/", this.state.pos);
if (end === -1) {
throw this.raise(this.state.pos, Errors.UnterminatedComment);
}
}
// Flow enum parsing
flowEnumErrorBooleanMemberNotInitialized(
pos: number,
{ enumName, memberName }: { enumName: string, memberName: string },
): void {
this.raise(
pos,
FlowErrors.EnumBooleanMemberNotInitialized,
memberName,
enumName,
);
}
flowEnumErrorInvalidMemberName(
pos: number,
{ enumName, memberName }: { enumName: string, memberName: string },
): void {
const suggestion = memberName[0].toUpperCase() + memberName.slice(1);
this.raise(
pos,
FlowErrors.EnumInvalidMemberName,
memberName,
suggestion,
enumName,
);
}
flowEnumErrorDuplicateMemberName(
pos: number,
{ enumName, memberName }: { enumName: string, memberName: string },
): void {
this.raise(pos, FlowErrors.EnumDuplicateMemberName, memberName, enumName);
}
flowEnumErrorInconsistentMemberValues(
pos: number,
{ enumName }: { enumName: string },
): void {
this.raise(pos, FlowErrors.EnumInconsistentMemberValues, enumName);
}
flowEnumErrorInvalidExplicitType(
pos: number,
{
enumName,
suppliedType,
}: { enumName: string, suppliedType: null | string },
) {
return this.raise(
pos,
suppliedType === null
? FlowErrors.EnumInvalidExplicitTypeUnknownSupplied
: FlowErrors.EnumInvalidExplicitType,
enumName,
suppliedType,
);
}
flowEnumErrorInvalidMemberInitializer(
pos: number,
{ enumName, explicitType, memberName }: EnumContext,
) {
let message = null;
switch (explicitType) {
case "boolean":
case "number":
case "string":
message = FlowErrors.EnumInvalidMemberInitializerPrimaryType;
break;
case "symbol":
message = FlowErrors.EnumInvalidMemberInitializerSymbolType;
break;
default:
// null
message = FlowErrors.EnumInvalidMemberInitializerUnknownType;
}
return this.raise(pos, message, enumName, memberName, explicitType);
}
flowEnumErrorNumberMemberNotInitialized(
pos: number,
{ enumName, memberName }: { enumName: string, memberName: string },
): void {
this.raise(
pos,
FlowErrors.EnumNumberMemberNotInitialized,
enumName,
memberName,
);
}
flowEnumErrorStringMemberInconsistentlyInitailized(
pos: number,
{ enumName }: { enumName: string },
): void {
this.raise(
pos,
FlowErrors.EnumStringMemberInconsistentlyInitailized,
enumName,
);
}
flowEnumMemberInit(): EnumMemberInit {
const startPos = this.state.start;
const endOfInit = () => this.match(tt.comma) || this.match(tt.braceR);
switch (this.state.type) {
case tt.num: {
const literal = this.parseLiteral(this.state.value, "NumericLiteral");
if (endOfInit()) {
return { type: "number", pos: literal.start, value: literal };
}
return { type: "invalid", pos: startPos };
}
case tt.string: {
const literal = this.parseLiteral(this.state.value, "StringLiteral");
if (endOfInit()) {
return { type: "string", pos: literal.start, value: literal };
}
return { type: "invalid", pos: startPos };
}
case tt._true:
case tt._false: {
const literal = this.parseBooleanLiteral();
if (endOfInit()) {
return {
type: "boolean",
pos: literal.start,
value: literal,
};
}
return { type: "invalid", pos: startPos };
}
default:
return { type: "invalid", pos: startPos };
}
}
flowEnumMemberRaw(): { id: N.Node, init: EnumMemberInit } {
const pos = this.state.start;
const id = this.parseIdentifier(true);
const init = this.eat(tt.eq)
? this.flowEnumMemberInit()
: { type: "none", pos };
return { id, init };
}
flowEnumCheckExplicitTypeMismatch(
pos: number,
context: EnumContext,
expectedType: EnumExplicitType,
): void {
const { explicitType } = context;
if (explicitType === null) {
return;
}
if (explicitType !== expectedType) {
this.flowEnumErrorInvalidMemberInitializer(pos, context);
}
}
flowEnumMembers({
enumName,
explicitType,
}: {
enumName: string,
explicitType: EnumExplicitType,
}): {|
booleanMembers: Array<N.Node>,
numberMembers: Array<N.Node>,
stringMembers: Array<N.Node>,
defaultedMembers: Array<N.Node>,
|} {
const seenNames = new Set();
const members = {
booleanMembers: [],
numberMembers: [],
stringMembers: [],
defaultedMembers: [],
};
while (!this.match(tt.braceR)) {
const memberNode = this.startNode();
const { id, init } = this.flowEnumMemberRaw();
const memberName = id.name;
if (memberName === "") {
continue;
}
if (/^[a-z]/.test(memberName)) {
this.flowEnumErrorInvalidMemberName(id.start, {
enumName,
memberName,
});
}
if (seenNames.has(memberName)) {
this.flowEnumErrorDuplicateMemberName(id.start, {
enumName,
memberName,
});
}
seenNames.add(memberName);
const context = { enumName, explicitType, memberName };
memberNode.id = id;
switch (init.type) {
case "boolean": {
this.flowEnumCheckExplicitTypeMismatch(
init.pos,
context,
"boolean",
);
memberNode.init = init.value;
members.booleanMembers.push(
this.finishNode(memberNode, "EnumBooleanMember"),
);
break;
}
case "number": {
this.flowEnumCheckExplicitTypeMismatch(init.pos, context, "number");
memberNode.init = init.value;
members.numberMembers.push(
this.finishNode(memberNode, "EnumNumberMember"),
);
break;
}
case "string": {
this.flowEnumCheckExplicitTypeMismatch(init.pos, context, "string");
memberNode.init = init.value;
members.stringMembers.push(
this.finishNode(memberNode, "EnumStringMember"),
);
break;
}
case "invalid": {
throw this.flowEnumErrorInvalidMemberInitializer(init.pos, context);
}
case "none": {
switch (explicitType) {
case "boolean":
this.flowEnumErrorBooleanMemberNotInitialized(
init.pos,
context,
);
break;
case "number":
this.flowEnumErrorNumberMemberNotInitialized(init.pos, context);
break;
default:
members.defaultedMembers.push(
this.finishNode(memberNode, "EnumDefaultedMember"),
);
}
}
}
if (!this.match(tt.braceR)) {
this.expect(tt.comma);
}
}
return members;
}
flowEnumStringMembers(
initializedMembers: Array<N.Node>,
defaultedMembers: Array<N.Node>,
{ enumName }: { enumName: string },
): Array<N.Node> {
if (initializedMembers.length === 0) {
return defaultedMembers;
} else if (defaultedMembers.length === 0) {
return initializedMembers;
} else if (defaultedMembers.length > initializedMembers.length) {
for (const member of initializedMembers) {
this.flowEnumErrorStringMemberInconsistentlyInitailized(
member.start,
{ enumName },
);
}
return defaultedMembers;
} else {
for (const member of defaultedMembers) {
this.flowEnumErrorStringMemberInconsistentlyInitailized(
member.start,
{ enumName },
);
}
return initializedMembers;
}
}
flowEnumParseExplicitType({
enumName,
}: {
enumName: string,
}): EnumExplicitType {
if (this.eatContextual("of")) {
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;
}
flowEnumBody(node: N.Node, { enumName, nameLoc }): N.Node {
const explicitType = this.flowEnumParseExplicitType({ enumName });
this.expect(tt.braceL);
const members = this.flowEnumMembers({ enumName, explicitType });
switch (explicitType) {
case "boolean":
node.explicitType = true;
node.members = members.booleanMembers;
this.expect(tt.braceR);
return this.finishNode(node, "EnumBooleanBody");
case "number":
node.explicitType = true;
node.members = members.numberMembers;
this.expect(tt.braceR);
return this.finishNode(node, "EnumNumberBody");
case "string":
node.explicitType = true;
node.members = this.flowEnumStringMembers(
members.stringMembers,
members.defaultedMembers,
{ enumName },
);
this.expect(tt.braceR);
return this.finishNode(node, "EnumStringBody");
case "symbol":
node.members = members.defaultedMembers;
this.expect(tt.braceR);
return this.finishNode(node, "EnumSymbolBody");
default: {
// `explicitType` is `null`
const empty = () => {
node.members = [];
this.expect(tt.braceR);
return this.finishNode(node, "EnumStringBody");
};
node.explicitType = false;
const boolsLen = members.booleanMembers.length;
const numsLen = members.numberMembers.length;
const strsLen = members.stringMembers.length;
const defaultedLen = members.defaultedMembers.length;
if (!boolsLen && !numsLen && !strsLen && !defaultedLen) {
return empty();
} else if (!boolsLen && !numsLen) {
node.members = this.flowEnumStringMembers(
members.stringMembers,
members.defaultedMembers,
{ enumName },
);
this.expect(tt.braceR);
return this.finishNode(node, "EnumStringBody");
} else if (!numsLen && !strsLen && boolsLen >= defaultedLen) {
for (const member of members.defaultedMembers) {
this.flowEnumErrorBooleanMemberNotInitialized(member.start, {
enumName,
memberName: member.id.name,
});
}
node.members = members.booleanMembers;
this.expect(tt.braceR);
return this.finishNode(node, "EnumBooleanBody");
} else if (!boolsLen && !strsLen && numsLen >= defaultedLen) {
for (const member of members.defaultedMembers) {
this.flowEnumErrorNumberMemberNotInitialized(member.start, {
enumName,
memberName: member.id.name,
});
}
node.members = members.numberMembers;
this.expect(tt.braceR);
return this.finishNode(node, "EnumNumberBody");
} else {
this.flowEnumErrorInconsistentMemberValues(nameLoc, { enumName });
return empty();
}
}
}
}
flowParseEnumDeclaration(node: N.Node): N.Node {
const id = this.parseIdentifier();
node.id = id;
node.body = this.flowEnumBody(this.startNode(), {
enumName: id.name,
nameLoc: id.start,
});
return this.finishNode(node, "EnumDeclaration");
}
updateContext(prevType: TokenType): void {
if (
this.match(tt.name) &&
this.state.value === "of" &&
prevType === tt.name &&
this.input.slice(this.state.lastTokStart, this.state.lastTokEnd) ===
"interface"
) {
this.state.exprAllowed = false;
} else {
super.updateContext(prevType);
}
}
};