// @flow /*:: declare var invariant; */ // Error messages are colocated with the plugin. /* eslint-disable @babel/development-internal/dry-error-messages */ import type Parser from "../parser"; import { types as tt, type TokenType } from "../tokenizer/types"; import * as N from "../types"; import type { Options } from "../options"; import type { Pos, Position } from "../util/location"; import type State from "../tokenizer/state"; import { types as tc } from "../tokenizer/context"; import * as charCodes from "charcodes"; import { isIteratorStart } from "../util/identifier"; import { type BindingTypes, BIND_NONE, BIND_LEXICAL, BIND_VAR, BIND_FUNCTION, SCOPE_ARROW, SCOPE_FUNCTION, SCOPE_OTHER, } from "../util/scopeflags"; import type { ExpressionErrors } from "../parser/util"; import { Errors } from "../parser/error"; const reservedTypes = new Set([ "_", "any", "bool", "boolean", "empty", "extends", "false", "interface", "mixed", "null", "number", "static", "string", "true", "typeof", "void", ]); /* eslint sort-keys: "error" */ // The Errors key follows https://github.com/facebook/flow/blob/master/src/parser/parse_error.ml unless it does not exist const FlowErrors = Object.freeze({ AmbiguousConditionalArrow: "Ambiguous expression: wrap the arrow functions in parentheses to disambiguate.", AmbiguousDeclareModuleKind: "Found both `declare module.exports` and `declare export` in the same module. Modules can only have 1 since they are either an ES module or they are a CommonJS module", AssignReservedType: "Cannot overwrite reserved type %0", DeclareClassElement: "The `declare` modifier can only appear on class fields.", DeclareClassFieldInitializer: "Initializers are not allowed in fields with the `declare` modifier.", DuplicateDeclareModuleExports: "Duplicate `declare module.exports` statement", EnumBooleanMemberNotInitialized: "Boolean enum members need to be initialized. Use either `%0 = true,` or `%0 = false,` in enum `%1`.", EnumDuplicateMemberName: "Enum member names need to be unique, but the name `%0` has already been used before in enum `%1`.", EnumInconsistentMemberValues: "Enum `%0` has inconsistent member initializers. Either use no initializers, or consistently use literals (either booleans, numbers, or strings) for all member initializers.", EnumInvalidExplicitType: "Enum type `%1` is not valid. Use one of `boolean`, `number`, `string`, or `symbol` in enum `%0`.", EnumInvalidExplicitTypeUnknownSupplied: "Supplied enum type is not valid. Use one of `boolean`, `number`, `string`, or `symbol` in enum `%0`.", EnumInvalidMemberInitializerPrimaryType: "Enum `%0` has type `%2`, so the initializer of `%1` needs to be a %2 literal.", EnumInvalidMemberInitializerSymbolType: "Symbol enum members cannot be initialized. Use `%1,` in enum `%0`.", EnumInvalidMemberInitializerUnknownType: "The enum member initializer for `%1` needs to be a literal (either a boolean, number, or string) in enum `%0`.", EnumInvalidMemberName: "Enum member names cannot start with lowercase 'a' through 'z'. Instead of using `%0`, consider using `%1`, in enum `%2`.", EnumNumberMemberNotInitialized: "Number enum members need to be initialized, e.g. `%1 = 1` in enum `%0`.", EnumStringMemberInconsistentlyInitailized: "String enum members need to consistently either all use initializers, or use no initializers, in enum `%0`.", ImportTypeShorthandOnlyInPureImport: "The `type` and `typeof` keywords on named imports can only be used on regular `import` statements. It cannot be used with `import type` or `import typeof` statements", InexactInsideExact: "Explicit inexact syntax cannot appear inside an explicit exact object type", InexactInsideNonObject: "Explicit inexact syntax cannot appear in class or interface definitions", InexactVariance: "Explicit inexact syntax cannot have variance", InvalidNonTypeImportInDeclareModule: "Imports within a `declare module` body must always be `import type` or `import typeof`", MissingTypeParamDefault: "Type parameter declaration needs a default, since a preceding type parameter declaration has a default.", NestedDeclareModule: "`declare module` cannot be used inside another `declare module`", NestedFlowComment: "Cannot have a flow comment inside another flow comment", OptionalBindingPattern: "A binding pattern parameter cannot be optional in an implementation signature.", SpreadVariance: "Spread properties cannot have variance", TypeBeforeInitializer: "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`", TypeCastInPattern: "The type cast expression is expected to be wrapped with parenthesis", UnexpectedExplicitInexactInObject: "Explicit inexact syntax must appear at the end of an inexact object", UnexpectedReservedType: "Unexpected reserved type %0", UnexpectedReservedUnderscore: "`_` is only allowed as a type argument to call or new", UnexpectedSpaceBetweenModuloChecks: "Spaces between `%` and `checks` are not allowed here.", UnexpectedSpreadType: "Spread operator cannot appear in class or interface definitions", UnexpectedSubtractionOperand: 'Unexpected token, expected "number" or "bigint"', UnexpectedTokenAfterTypeParameter: "Expected an arrow function after this type parameter declaration", UnexpectedTypeParameterBeforeAsyncArrowFunction: "Type parameters must come after the async keyword, e.g. instead of ` async () => {}`, use `async () => {}`", 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( 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): Class => 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 object 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, 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, 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(undefined, undefined, undefined), ); return this.finishNode(node, "ConditionalExpression"); } tryParseConditionalConsequent(): { consequent: N.Expression, failed: boolean, } { this.state.noArrowParamsConversionAt.push(this.state.start); const consequent = this.parseMaybeAssignAllowIn(); 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)), ); } finishArrowValidation(node: N.ArrowFunctionExpression) { this.toAssignableList( // node.params is Expression[] instead of $ReadOnlyArray 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(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 (this.state.inType && code === charCodes.questionMark) { // allow double nullable types in Flow: ??string return this.finishOp(tt.question, 1); } else if (isIteratorStart(code, next)) { this.state.isIterator = true; return super.readWord(); } else { return super.getTokenFromCode(code); } } isAssignable(node: N.Node): boolean { switch (node.type) { case "TypeCastExpression": return this.isAssignable(node.expression); default: return super.isAssignable(node); } } 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 { for (let i = 0; i < exprList.length; i++) { const expr = exprList[i]; if (expr?.type === "TypeCastExpression") { exprList[i] = this.typeCastToParameter(expr); } } return super.toAssignableList(exprList, trailingCommaPos); } // this is a list of nodes, from something like a call expression, we need to filter the // type casts that we've found that are illegal in this context toReferencedList( exprList: $ReadOnlyArray, isParenthesizedExpr?: boolean, ): $ReadOnlyArray { for (let i = 0; i < exprList.length; i++) { const expr = exprList[i]; if ( expr && expr.type === "TypeCastExpression" && !expr.extra?.parenthesized && (exprList.length > 1 || !isParenthesizedExpr) ) { this.raise(expr.typeAnnotation.start, FlowErrors.TypeCastInPattern); } } return exprList; } checkLVal( expr: N.Expression, bindingType: BindingTypes = BIND_NONE, checkClashes: ?{ [key: string]: boolean }, contextDescription: string, ): void { if (expr.type !== "TypeCastExpression") { return super.checkLVal( expr, bindingType, checkClashes, contextDescription, ); } } // parse class property type annotations parseClassProperty(node: N.ClassProperty): N.ClassProperty { if (this.match(tt.colon)) { node.typeAnnotation = this.flowParseTypeAnnotation(); } return super.parseClassProperty(node); } parseClassPrivateProperty( node: N.ClassPrivateProperty, ): N.ClassPrivateProperty { if (this.match(tt.colon)) { node.typeAnnotation = this.flowParseTypeAnnotation(); } return super.parseClassPrivateProperty(node); } // determine whether or not we're currently in the position where a class method would appear isClassMethod(): boolean { return this.isRelational("<") || super.isClassMethod(); } // determine whether or not we're currently in the position where a class property would appear isClassProperty(): boolean { return this.match(tt.colon) || super.isClassProperty(); } isNonstaticConstructor(method: N.ClassMethod | N.ClassProperty): boolean { return !this.match(tt.colon) && super.isNonstaticConstructor(method); } // parse type parameters for class methods pushClassMethod( classBody: N.ClassBody, method: N.ClassMethod, isGenerator: boolean, isAsync: boolean, isConstructor: boolean, allowsDirectSuper: boolean, ): void { if ((method: $FlowFixMe).variance) { this.unexpected((method: $FlowFixMe).variance.start); } delete (method: $FlowFixMe).variance; if (this.isRelational("<")) { method.typeParameters = this.flowParseTypeParameterDeclaration(); } super.pushClassMethod( classBody, method, isGenerator, isAsync, isConstructor, allowsDirectSuper, ); } pushClassPrivateMethod( classBody: N.ClassBody, method: N.ClassPrivateMethod, isGenerator: boolean, isAsync: boolean, ): void { if ((method: $FlowFixMe).variance) { this.unexpected((method: $FlowFixMe).variance.start); } delete (method: $FlowFixMe).variance; if (this.isRelational("<")) { method.typeParameters = this.flowParseTypeParameterDeclaration(); } super.pushClassPrivateMethod(classBody, method, isGenerator, isAsync); } // parse a the super class type parameters and implements parseClassSuper(node: N.Class): void { super.parseClassSuper(node); if (node.superClass && this.isRelational("<")) { node.superTypeParameters = this.flowParseTypeParameterInstantiation(); } if (this.isContextual("implements")) { this.next(); const implemented: N.FlowClassImplements[] = (node.implements = []); do { const node = this.startNode(); node.id = this.flowParseRestrictedIdentifier(/*liberal*/ true); if (this.isRelational("<")) { node.typeParameters = this.flowParseTypeParameterInstantiation(); } else { node.typeParameters = null; } implemented.push(this.finishNode(node, "ClassImplements")); } while (this.eat(tt.comma)); } } parsePropertyName( node: N.ObjectOrClassMember | N.ClassMember | N.TsNamedTypeElementBase, isPrivateNameAllowed: boolean, ): N.Identifier { const variance = this.flowParseVariance(); const key = super.parsePropertyName(node, isPrivateNameAllowed); // $FlowIgnore ("variance" not defined on TsNamedTypeElementBase) node.variance = variance; return key; } // parse type parameters for object method shorthand parseObjPropValue( prop: N.ObjectMember, startPos: ?number, startLoc: ?Position, isGenerator: boolean, isAsync: boolean, isPattern: boolean, isAccessor: boolean, refExpressionErrors: ?ExpressionErrors, ): void { if ((prop: $FlowFixMe).variance) { this.unexpected((prop: $FlowFixMe).variance.start); } delete (prop: $FlowFixMe).variance; let typeParameters; // method shorthand if (this.isRelational("<") && !isAccessor) { typeParameters = this.flowParseTypeParameterDeclaration(); if (!this.match(tt.parenL)) this.unexpected(); } super.parseObjPropValue( prop, startPos, startLoc, isGenerator, isAsync, isPattern, isAccessor, refExpressionErrors, ); // 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() {} 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( 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( refExpressionErrors, afterLeftParse, refNeedsArrowPos, ), state, ); /*:: invariant(!jsx.aborted) */ if (!jsx.error) return jsx.node; // Remove `tc.j_expr` and `tc.j_oTag` from context added // by parsing `jsxTagStart` to stop the JSX plugin from // messing with the tokens const { context } = this.state; if (context[context.length - 1] === tc.j_oTag) { context.length -= 2; } else if (context[context.length - 1] === tc.j_expr) { context.length -= 1; } } if (jsx?.error || this.isRelational("<")) { state = state || this.state.clone(); let typeParameters; const arrow = this.tryParse(abort => { typeParameters = this.flowParseTypeParameterDeclaration(); const arrowExpression = this.forwardNoArrowParamsConversionAt( typeParameters, () => { const result = super.parseMaybeAssign( refExpressionErrors, afterLeftParse, refNeedsArrowPos, ); this.resetStartLocationFromNode(result, typeParameters); return result; }, ); // (() => {}: any); if ( arrowExpression.type !== "ArrowFunctionExpression" && arrowExpression.extra?.parenthesized ) { abort(); } // The above can return a TypeCastExpression when the arrow // expression is not wrapped in parens. See also `this.parseParenItem`. const expr = this.maybeUnwrapTypeCastExpression(arrowExpression); expr.typeParameters = typeParameters; this.resetStartLocationFromNode(expr, typeParameters); return arrowExpression; }, state); let arrowExpression: ?( | N.ArrowFunctionExpression | N.TypeCastExpression ) = null; if ( arrow.node && this.maybeUnwrapTypeCastExpression(arrow.node).type === "ArrowFunctionExpression" ) { if (!arrow.error && !arrow.aborted) { // async () => {} if (arrow.node.async) { /*:: invariant(typeParameters) */ this.raise( typeParameters.start, FlowErrors.UnexpectedTypeParameterBeforeAsyncArrowFunction, ); } return arrow.node; } arrowExpression = arrow.node; } // If we are here, both JSX and Flow parsing attempts failed. // Give the precedence to the JSX error, except if JSX had an // unrecoverable error while Flow didn't. // If the error is recoverable, we can only re-report it if there is // a node we can return. if (jsx?.node) { /*:: invariant(jsx.failState) */ this.state = jsx.failState; return jsx.node; } if (arrowExpression) { /*:: invariant(arrow.failState) */ this.state = arrow.failState; return arrowExpression; } if (jsx?.thrown) throw jsx.error; if (arrow.thrown) throw arrow.error; /*:: invariant(typeParameters) */ throw this.raise( typeParameters.start, FlowErrors.UnexpectedTokenAfterTypeParameter, ); } return super.parseMaybeAssign( refExpressionErrors, afterLeftParse, refNeedsArrowPos, ); } // handle return types for arrow functions parseArrow( node: N.ArrowFunctionExpression, exprList: N.Node[], ): ?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, exprList); } 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.isLookaheadToken_lt()) { 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); // set exprList to `[]` as the parameters has been validated in `parseFunctionParams` 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, numberMembers: Array, stringMembers: Array, defaultedMembers: Array, |} { 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, defaultedMembers: Array, { enumName }: { enumName: string }, ): Array { if (initializedMembers.length === 0) { return defaultedMembers; } else if (defaultedMembers.length === 0) { return initializedMembers; } else if (defaultedMembers.length > initializedMembers.length) { for (const member of initializedMembers) { this.flowEnumErrorStringMemberInconsistentlyInitailized( member.start, { enumName }, ); } return defaultedMembers; } else { for (const member of defaultedMembers) { this.flowEnumErrorStringMemberInconsistentlyInitailized( member.start, { enumName }, ); } return initializedMembers; } } flowEnumParseExplicitType({ enumName, }: { enumName: string, }): EnumExplicitType { if (this.eatContextual("of")) { if (!this.match(tt.name)) { throw this.flowEnumErrorInvalidExplicitType(this.state.start, { enumName, suppliedType: null, }); } const { value } = this.state; this.next(); if ( value !== "boolean" && value !== "number" && value !== "string" && value !== "symbol" ) { this.flowEnumErrorInvalidExplicitType(this.state.start, { enumName, suppliedType: value, }); } return value; } return null; } flowEnumBody(node: N.Node, { enumName, nameLoc }): N.Node { const explicitType = this.flowEnumParseExplicitType({ enumName }); this.expect(tt.braceL); const members = this.flowEnumMembers({ enumName, explicitType }); switch (explicitType) { case "boolean": node.explicitType = true; node.members = members.booleanMembers; this.expect(tt.braceR); return this.finishNode(node, "EnumBooleanBody"); case "number": node.explicitType = true; node.members = members.numberMembers; this.expect(tt.braceR); return this.finishNode(node, "EnumNumberBody"); case "string": node.explicitType = true; node.members = this.flowEnumStringMembers( members.stringMembers, members.defaultedMembers, { enumName }, ); this.expect(tt.braceR); return this.finishNode(node, "EnumStringBody"); case "symbol": node.members = members.defaultedMembers; this.expect(tt.braceR); return this.finishNode(node, "EnumSymbolBody"); default: { // `explicitType` is `null` const empty = () => { node.members = []; this.expect(tt.braceR); return this.finishNode(node, "EnumStringBody"); }; node.explicitType = false; const boolsLen = members.booleanMembers.length; const numsLen = members.numberMembers.length; const strsLen = members.stringMembers.length; const defaultedLen = members.defaultedMembers.length; if (!boolsLen && !numsLen && !strsLen && !defaultedLen) { return empty(); } else if (!boolsLen && !numsLen) { node.members = this.flowEnumStringMembers( members.stringMembers, members.defaultedMembers, { enumName }, ); this.expect(tt.braceR); return this.finishNode(node, "EnumStringBody"); } else if (!numsLen && !strsLen && boolsLen >= defaultedLen) { for (const member of members.defaultedMembers) { this.flowEnumErrorBooleanMemberNotInitialized(member.start, { enumName, memberName: member.id.name, }); } node.members = members.booleanMembers; this.expect(tt.braceR); return this.finishNode(node, "EnumBooleanBody"); } else if (!boolsLen && !strsLen && numsLen >= defaultedLen) { for (const member of members.defaultedMembers) { this.flowEnumErrorNumberMemberNotInitialized(member.start, { enumName, memberName: member.id.name, }); } node.members = members.numberMembers; this.expect(tt.braceR); return this.finishNode(node, "EnumNumberBody"); } else { this.flowEnumErrorInconsistentMemberValues(nameLoc, { enumName }); return empty(); } } } } flowParseEnumDeclaration(node: N.Node): N.Node { const id = this.parseIdentifier(); node.id = id; node.body = this.flowEnumBody(this.startNode(), { enumName: id.name, nameLoc: id.start, }); return this.finishNode(node, "EnumDeclaration"); } updateContext(prevType: TokenType): void { if ( this.match(tt.name) && this.state.value === "of" && prevType === tt.name && this.input.slice(this.state.lastTokStart, this.state.lastTokEnd) === "interface" ) { this.state.exprAllowed = false; } else { super.updateContext(prevType); } } // check if the next token is a tt.relation("<") isLookaheadToken_lt(): boolean { const next = this.nextTokenStart(); if (this.input.charCodeAt(next) === charCodes.lessThan) { const afterNext = this.input.charCodeAt(next + 1); return ( afterNext !== charCodes.lessThan && afterNext !== charCodes.equalsTo ); } return false; } maybeUnwrapTypeCastExpression(node: N.Node) { return node.type === "TypeCastExpression" ? node.expression : node; } };