* Add parser support for Flow declare fields * Add generator test * Add "allowDeclareFields" option to flow-strip-types * Add test * Update error messages * More tests
3461 lines
104 KiB
JavaScript
3461 lines
104 KiB
JavaScript
// @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/location";
|
||
|
||
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",
|
||
//todo: replace ´ by `
|
||
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 && 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 || !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 && 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 && arrow.node.type === "ArrowFunctionExpression"
|
||
? arrow.node
|
||
: null;
|
||
|
||
if (!arrow.error && arrowExpression) return arrowExpression;
|
||
|
||
// If we are here, both JSX and Flow parsing attemps failed.
|
||
// Give the precedence to the JSX error, except if JSX had an
|
||
// unrecoverable error while Flow didn't.
|
||
// If the error is recoverable, we can only re-report it if there is
|
||
// a node we can return.
|
||
|
||
if (jsx && jsx.node) {
|
||
/*:: invariant(jsx.failState) */
|
||
this.state = jsx.failState;
|
||
return jsx.node;
|
||
}
|
||
|
||
if (arrowExpression) {
|
||
/*:: invariant(arrow.failState) */
|
||
this.state = arrow.failState;
|
||
return arrowExpression;
|
||
}
|
||
|
||
if (jsx && 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");
|
||
}
|
||
};
|