2018-12-15 12:51:24 -08:00

2778 lines
82 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @flow
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";
const reservedTypes = [
"any",
"bool",
"boolean",
"empty",
"false",
"mixed",
"null",
"number",
"static",
"string",
"true",
"typeof",
"void",
"interface",
"extends",
"_",
];
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/;
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";
}
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) {
this.flowPragma = null;
} 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,
"Spaces between ´%´ and ´checks´ are not allowed here.",
);
}
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.finishNode(id, id.type);
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.isContextual("module")) {
if (this.lookahead().type === tt.dot) {
return this.flowParseDeclareModuleExports(node);
} else {
if (insideModule) {
this.unexpected(
null,
"`declare module` cannot be used inside another `declare module`",
);
}
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.semicolon();
return this.finishNode(node, "DeclareVariable");
}
flowParseDeclareModule(node: N.FlowDeclareModule): N.FlowDeclareModule {
this.next();
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)) {
const lookahead = this.lookahead();
if (lookahead.value !== "type" && lookahead.value !== "typeof") {
this.unexpected(
null,
"Imports within a `declare module` body must always be `import type` or `import typeof`",
);
}
this.next();
this.parseImport(bodyNode);
} else {
this.expectContextual(
"declare",
"Only declares and type imports are allowed inside declare module",
);
bodyNode = this.flowParseDeclare(bodyNode, true);
}
body.push(bodyNode);
}
this.expect(tt.braceR);
this.finishNode(bodyNode, "BlockStatement");
let kind = null;
let hasModuleExport = false;
const errorMessage =
"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";
body.forEach(bodyElement => {
if (isEsModuleType(bodyElement)) {
if (kind === "CommonJS") {
this.unexpected(bodyElement.start, errorMessage);
}
kind = "ES";
} else if (bodyElement.type === "DeclareModuleExports") {
if (hasModuleExport) {
this.unexpected(
bodyElement.start,
"Duplicate `declare module.exports` statement",
);
}
if (kind === "ES") this.unexpected(bodyElement.start, errorMessage);
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.match(tt._let) ||
((this.isContextual("type") || this.isContextual("interface")) &&
!insideModule)
) {
const label = this.state.value;
const suggestion = exportSuggestions[label];
this.unexpected(
this.state.start,
`\`declare export ${label}\` is not supported. Use \`${suggestion}\` instead`,
);
}
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.expectContextual("module");
this.expect(tt.dot);
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);
return this.finishNode(node, "DeclareTypeAlias");
}
flowParseDeclareOpaqueType(
node: N.FlowDeclareOpaqueType,
): N.FlowDeclareOpaqueType {
this.next();
this.flowParseOpaqueType(node, true);
return this.finishNode(node, "DeclareOpaqueType");
}
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);
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 === "_") {
throw this.unexpected(
null,
"`_` is only allowed as a type argument to call or new",
);
}
}
checkReservedType(word: string, startLoc: number) {
if (reservedTypes.indexOf(word) > -1) {
this.raise(startLoc, `Cannot overwrite reserved type ${word}`);
}
}
flowParseRestrictedIdentifier(liberal?: boolean): N.Identifier {
this.checkReservedType(this.state.value, this.state.start);
return this.parseIdentifier(liberal);
}
// Type aliases
flowParseTypeAlias(node: N.FlowTypeAlias): N.FlowTypeAlias {
node.id = this.flowParseRestrictedIdentifier();
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);
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(
allowDefault?: boolean = true,
requireDefault?: boolean = false,
): N.TypeParameter {
if (!allowDefault && requireDefault) {
throw new Error(
"Cannot disallow a default value (`allowDefault`) while also requiring it (`requireDefault`).",
);
}
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)) {
if (allowDefault) {
this.eat(tt.eq);
node.default = this.flowParseType();
} else {
this.unexpected();
}
} else {
if (requireDefault) {
this.unexpected(
nodeStart,
// eslint-disable-next-line max-len
"Type parameter declaration needs a default, since a preceding type parameter declaration has a default.",
);
}
}
return this.finishNode(node, "TypeParameter");
}
flowParseTypeParameterDeclaration(
allowDefault?: boolean = true,
): 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(
allowDefault,
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(
/* allowDefault */ false,
);
}
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;
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,
);
if (propOrInexact === null) {
inexact = true;
} else {
nodeStart.properties.push(propOrInexact);
}
}
this.flowObjectTypeSemicolon();
}
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.match(tt.ellipsis)) {
if (!allowSpread) {
this.unexpected(
null,
"Spread operator cannot appear in class or interface definitions",
);
}
if (protoStart != null) {
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",
);
}
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 {
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, "getter must not have any formal parameters");
} else {
this.raise(start, "setter must have exactly one formal parameter");
}
}
if (property.kind === "set" && property.value.rest) {
this.raise(
start,
"setter function argument must not be a rest parameter",
);
}
}
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.parseIdentifier();
while (this.eat(tt.dot)) {
const node2 = this.startNodeAt(startPos, startLoc);
node2.qualification = node;
node2.id = this.parseIdentifier();
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.input.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 "void":
return this.finishNode(node, "VoidTypeAnnotation");
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");
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:
return this.flowParseTupleType();
case tt.relational:
if (this.state.value === "<") {
node.typeParameters = this.flowParseTypeParameterDeclaration(
/* allowDefault */ false,
);
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)) {
this.unexpected(null, `Unexpected token, expected "number"`);
}
return this.parseLiteral(
-this.state.value,
"NumberLiteralTypeAnnotation",
node.start,
node.loc.start,
);
}
this.unexpected();
case tt.num:
return this.parseLiteral(
this.state.value,
"NumberLiteralTypeAnnotation",
);
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.canInsertSemicolon() && this.match(tt.bracketL)) {
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.finishNode(ident, ident.type);
}
return ident;
}
typeCastToParameter(node: N.Node): N.Node {
node.expression.typeAnnotation = node.typeAnnotation;
return this.finishNodeAt(
node.expression,
node.expression.type,
node.typeAnnotation.end,
node.typeAnnotation.loc.end,
);
}
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): void {
if (allowExpressionBody) {
return this.forwardNoArrowParamsConversionAt(node, () =>
super.parseFunctionBody(node, true),
);
}
return super.parseFunctionBody(node, false);
}
parseFunctionBodyAndFinish(
node: N.BodilessFunctionOrMethodBase,
type: string,
allowExpressionBody?: boolean,
): void {
// For arrow functions, `parseArrow` handles the return type itself.
if (!allowExpressionBody && 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, allowExpressionBody);
}
// interfaces
parseStatement(declaration: boolean, 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 {
const stmt = super.parseStatement(declaration, 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") ||
super.shouldParseExportDeclaration()
);
}
isExportDefaultSpecifier(): boolean {
if (
this.match(tt.name) &&
(this.state.value === "type" ||
this.state.value === "interface" ||
this.state.value == "opaque")
) {
return false;
}
return super.isExportDefaultSpecifier();
}
parseConditional(
expr: N.Expression,
noIn: ?boolean,
startPos: number,
startLoc: Position,
refNeedsArrowPos?: ?Pos,
): N.Expression {
if (!this.match(tt.question)) return expr;
// only do the expensive clone 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;
}
}
}
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,
"Ambiguous expression: wrap the arrow functions in parentheses to disambiguate.",
);
}
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.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",
);
// Use super's method to force the parameters to be checked
super.checkFunctionNameAndParams(node, true);
} else {
arrows.push(node);
}
stack.push(node.body);
} else if (node.type === "ConditionalExpression") {
stack.push(node.consequent);
stack.push(node.alternate);
}
}
if (disallowInvalid) {
for (let i = 0; i < arrows.length; i++) {
this.toAssignableList(
((node.params: any): N.Expression[]),
true,
"arrow function parameters",
);
}
return [arrows, []];
}
return partition(arrows, node => {
try {
this.toAssignableList(
((node.params: any): N.Expression[]),
true,
"arrow function parameters",
);
return true;
} catch (err) {
return false;
}
});
}
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;
}
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.ExportNamedDeclaration | N.ExportAllDeclaration,
): N.ExportNamedDeclaration | N.ExportAllDeclaration {
node = super.parseExport(node);
if (
node.type === "ExportNamedDeclaration" ||
node.type === "ExportAllDeclaration"
) {
node.exportKind = node.exportKind || "value";
}
return node;
}
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 {
return super.parseExportDeclaration(node);
}
}
shouldParseExportStar(): boolean {
return (
super.shouldParseExportStar() ||
(this.isContextual("type") && this.lookahead().type === tt.star)
);
}
parseExportStar(node: N.ExportNamedDeclaration): void {
if (this.eatContextual("type")) {
node.exportKind = "type";
}
return super.parseExportStar(node);
}
parseExportNamespace(node: N.ExportNamedDeclaration) {
if (node.exportKind === "type") {
this.unexpected();
}
return super.parseExportNamespace(node);
}
parseClassId(node: N.Class, isStatement: boolean, optionalId: ?boolean) {
super.parseClassId(node, isStatement, optionalId);
if (this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterDeclaration();
}
}
// don't consider `void` to be a keyword as then it'll use the void token type
// and set startExpr
isKeyword(name: string): boolean {
if (this.state.inType && name === "void") {
return false;
} else {
return super.isKeyword(name);
}
}
// ensure that inside flow types, we bypass the jsx parser plugin
readToken(code: number): void {
const next = this.input.charCodeAt(this.state.pos + 1);
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.readToken(code);
}
}
toAssignable(
node: N.Node,
isBinding: ?boolean,
contextDescription: string,
): N.Node {
if (node.type === "TypeCastExpression") {
return super.toAssignable(
this.typeCastToParameter(node),
isBinding,
contextDescription,
);
} else {
return super.toAssignable(node, isBinding, contextDescription);
}
}
// turn type casts that we found in function parameter head into type annotated params
toAssignableList(
exprList: N.Expression[],
isBinding: ?boolean,
contextDescription: string,
): $ReadOnlyArray<N.Pattern> {
for (let i = 0; i < exprList.length; i++) {
const expr = exprList[i];
if (expr && expr.type === "TypeCastExpression") {
exprList[i] = this.typeCastToParameter(expr);
}
}
return super.toAssignableList(exprList, isBinding, contextDescription);
}
// 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 || !expr.extra.parenthesized) &&
(exprList.length > 1 || !isParenthesizedExpr)
) {
this.raise(
expr.typeAnnotation.start,
"The type cast expression is expected to be wrapped with parenthesis",
);
}
}
return exprList;
}
checkLVal(
expr: N.Expression,
isBinding: ?boolean,
checkClashes: ?{ [key: string]: boolean },
contextDescription: string,
): void {
if (expr.type !== "TypeCastExpression") {
return super.checkLVal(
expr,
isBinding,
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,
): void {
if ((method: $FlowFixMe).variance) {
this.unexpected((method: $FlowFixMe).variance.start);
}
delete (method: $FlowFixMe).variance;
if (this.isRelational("<")) {
method.typeParameters = this.flowParseTypeParameterDeclaration(
/* allowDefault */ false,
);
}
super.pushClassMethod(
classBody,
method,
isGenerator,
isAsync,
isConstructor,
);
}
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,
): N.Identifier {
const variance = this.flowParseVariance();
const key = super.parsePropertyName(node);
// $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,
refShorthandDefaultPos: ?Pos,
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(
/* allowDefault */ false,
);
if (!this.match(tt.parenL)) this.unexpected();
}
super.parseObjPropValue(
prop,
startPos,
startLoc,
isGenerator,
isAsync,
isPattern,
refShorthandDefaultPos,
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") {
throw this.raise(
param.start,
"A binding pattern parameter cannot be optional in an implementation signature.",
);
}
param.optional = true;
}
if (this.match(tt.colon)) {
param.typeAnnotation = this.flowParseTypeAnnotation();
}
this.finishNode(param, param.type);
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,
"Type annotations must come before default assignments, " +
"e.g. instead of `age = 25: number` use `age: number = 25`",
);
}
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(true)
: this.parseIdentifier();
this.checkLVal(specifier.local, true, undefined, contextDescription);
node.specifiers.push(this.finishNode(specifier, type));
}
// parse typeof and type imports
parseImportSpecifiers(node: N.ImportDeclaration): void {
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;
}
}
super.parseImportSpecifiers(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,
"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",
);
}
if (nodeIsTypeImport || specifierIsTypeImport) {
this.checkReservedType(specifier.local.name, specifier.local.start);
}
if (isBinding && !nodeIsTypeImport && !specifierIsTypeImport) {
this.checkReservedWord(
specifier.local.name,
specifier.start,
true,
true,
);
}
this.checkLVal(specifier.local, true, undefined, "import specifier");
node.specifiers.push(this.finishNode(specifier, "ImportSpecifier"));
}
// parse function type parameters - function foo<T>() {}
parseFunctionParams(node: N.Function): void {
// $FlowFixMe
const kind = node.kind;
if (kind !== "get" && kind !== "set" && this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterDeclaration(
/* allowDefault */ false,
);
}
super.parseFunctionParams(node);
}
// parse flow type annotations on variable declarator heads - let foo: string = bar
parseVarHead(decl: N.VariableDeclarator): void {
super.parseVarHead(decl);
if (this.match(tt.colon)) {
decl.id.typeAnnotation = this.flowParseTypeAnnotation();
this.finishNode(decl.id, decl.id.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,
refShorthandDefaultPos?: ?Pos,
afterLeftParse?: Function,
refNeedsArrowPos?: ?Pos,
): N.Expression {
let jsxError = null;
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;
// 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;
}
jsxError = err;
} else {
// istanbul ignore next: no such error is expected
throw err;
}
}
}
if (jsxError != null || this.isRelational("<")) {
let arrowExpression;
let typeParameters;
try {
typeParameters = this.flowParseTypeParameterDeclaration();
arrowExpression = this.forwardNoArrowParamsConversionAt(
typeParameters,
() =>
super.parseMaybeAssign(
noIn,
refShorthandDefaultPos,
afterLeftParse,
refNeedsArrowPos,
),
);
arrowExpression.typeParameters = typeParameters;
this.resetStartLocationFromNode(arrowExpression, typeParameters);
} catch (err) {
throw jsxError || err;
}
if (arrowExpression.type === "ArrowFunctionExpression") {
return arrowExpression;
} else if (jsxError != null) {
throw jsxError;
} else {
this.raise(
typeParameters.start,
"Expected an arrow function after this type parameter declaration",
);
}
}
return super.parseMaybeAssign(
noIn,
refShorthandDefaultPos,
afterLeftParse,
refNeedsArrowPos,
);
}
// handle return types for arrow functions
parseArrow(node: N.ArrowFunctionExpression): ?N.ArrowFunctionExpression {
if (this.match(tt.colon)) {
const state = this.state.clone();
try {
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();
// 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 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);
}
}
checkFunctionNameAndParams(
node: N.Function,
isArrowFunction: ?boolean,
): void {
if (
isArrowFunction &&
this.state.noArrowParamsConversionAt.indexOf(node.start) !== -1
) {
return;
}
return super.checkFunctionNameAndParams(node, isArrowFunction);
}
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();
let error;
try {
const node = this.parseAsyncArrowWithTypeParameters(
startPos,
startLoc,
);
if (node) return node;
} catch (e) {
error = e;
}
this.state = state;
try {
return super.parseSubscripts(base, startPos, startLoc, noCalls);
} catch (e) {
throw error || e;
}
}
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("<")) {
this.expectPlugin("optionalChaining");
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.finishNode(node, "OptionalCallExpression");
} else if (
!noCalls &&
this.shouldParseTypes() &&
this.isRelational("<")
) {
const node = this.startNodeAt(startPos, startLoc);
node.callee = base;
const state = this.state.clone();
try {
node.typeArguments = this.flowParseTypeParameterInstantiationCallOrNew();
this.expect(tt.parenL);
node.arguments = this.parseCallExpressionArguments(tt.parenR, false);
if (subscriptState.optionalChainMember) {
node.optional = false;
return this.finishNode(node, "OptionalCallExpression");
}
return this.finishNode(node, "CallExpression");
} catch (e) {
if (e instanceof SyntaxError) {
this.state = state;
} else {
throw e;
}
}
}
return super.parseSubscript(
base,
startPos,
startLoc,
noCalls,
subscriptState,
);
}
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;
}
}
}
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);
}
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");
}
return fileNode;
}
skipBlockComment(): void {
if (
this.hasPlugin("flow") &&
this.hasPlugin("flowComments") &&
this.skipFlowComment()
) {
if (this.state.hasFlowComment) {
this.unexpected(
null,
"Cannot have a flow comment inside another flow comment",
);
}
this.hasFlowCommentCompletion();
this.state.pos += this.skipFlowComment();
this.state.hasFlowComment = true;
return;
}
if (this.hasPlugin("flow") && this.state.hasFlowComment) {
const end = this.input.indexOf("*-/", (this.state.pos += 2));
if (end === -1) this.raise(this.state.pos - 2, "Unterminated comment");
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) {
this.raise(this.state.pos, "Unterminated comment");
}
}
};