"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _types = require("../tokenizer/types");

var N = _interopRequireWildcard(require("../types"));

var _context = require("../tokenizer/context");

var _identifier = require("../util/identifier");

var _scopeflags = require("../util/scopeflags");

var _error = require("../parser/error");

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

const reservedTypes = new Set(["_", "any", "bool", "boolean", "empty", "extends", "false", "interface", "mixed", "null", "number", "static", "string", "true", "typeof", "void"]);
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 `<T> async () => {}`, use `async <T>() => {}`",
  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"
});

function isEsModuleType(bodyElement) {
  return bodyElement.type === "DeclareExportAllDeclaration" || bodyElement.type === "DeclareExportDeclaration" && (!bodyElement.declaration || bodyElement.declaration.type !== "TypeAlias" && bodyElement.declaration.type !== "InterfaceDeclaration");
}

function hasTypeImportKind(node) {
  return node.importKind === "type" || node.importKind === "typeof";
}

function isMaybeDefaultImport(state) {
  return (state.type === _types.types.name || !!state.type.keyword) && state.value !== "from";
}

const exportSuggestions = {
  const: "declare export var",
  let: "declare export var",
  type: "export type",
  interface: "export interface"
};

function partition(list, test) {
  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/;

var _default = superClass => {
  var _temp;

  return _temp = class extends superClass {
    constructor(options, input) {
      super(options, input);
      this.flowPragma = void 0;
      this.flowPragma = undefined;
    }

    shouldParseTypes() {
      return this.getPluginOption("flow", "all") || this.flowPragma === "flow";
    }

    shouldParseEnums() {
      return !!this.getPluginOption("flow", "enums");
    }

    finishToken(type, val) {
      if (type !== _types.types.string && type !== _types.types.semi && type !== _types.types.interpreterDirective) {
        if (this.flowPragma === undefined) {
          this.flowPragma = null;
        }
      }

      return super.finishToken(type, val);
    }

    addComment(comment) {
      if (this.flowPragma === undefined) {
        const matches = FLOW_PRAGMA_REGEX.exec(comment.value);

        if (!matches) {} 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) {
      const oldInType = this.state.inType;
      this.state.inType = true;
      this.expect(tok || _types.types.colon);
      const type = this.flowParseType();
      this.state.inType = oldInType;
      return type;
    }

    flowParsePredicate() {
      const node = this.startNode();
      const moduloLoc = this.state.startLoc;
      const moduloPos = this.state.start;
      this.expect(_types.types.modulo);
      const checksLoc = this.state.startLoc;
      this.expectContextual("checks");

      if (moduloLoc.line !== checksLoc.line || moduloLoc.column !== checksLoc.column - 1) {
        this.raise(moduloPos, FlowErrors.UnexpectedSpaceBetweenModuloChecks);
      }

      if (this.eat(_types.types.parenL)) {
        node.value = this.parseExpression();
        this.expect(_types.types.parenR);
        return this.finishNode(node, "DeclaredPredicate");
      } else {
        return this.finishNode(node, "InferredPredicate");
      }
    }

    flowParseTypeAndPredicateInitialiser() {
      const oldInType = this.state.inType;
      this.state.inType = true;
      this.expect(_types.types.colon);
      let type = null;
      let predicate = null;

      if (this.match(_types.types.modulo)) {
        this.state.inType = oldInType;
        predicate = this.flowParsePredicate();
      } else {
        type = this.flowParseType();
        this.state.inType = oldInType;

        if (this.match(_types.types.modulo)) {
          predicate = this.flowParsePredicate();
        }
      }

      return [type, predicate];
    }

    flowParseDeclareClass(node) {
      this.next();
      this.flowParseInterfaceish(node, true);
      return this.finishNode(node, "DeclareClass");
    }

    flowParseDeclareFunction(node) {
      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(_types.types.parenL);
      const tmp = this.flowParseFunctionTypeParams();
      typeNode.params = tmp.params;
      typeNode.rest = tmp.rest;
      this.expect(_types.types.parenR);
      [typeNode.returnType, 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, insideModule) {
      if (this.match(_types.types._class)) {
        return this.flowParseDeclareClass(node);
      } else if (this.match(_types.types._function)) {
        return this.flowParseDeclareFunction(node);
      } else if (this.match(_types.types._var)) {
        return this.flowParseDeclareVariable(node);
      } else if (this.eatContextual("module")) {
        if (this.match(_types.types.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(_types.types._export)) {
        return this.flowParseDeclareExportDeclaration(node, insideModule);
      } else {
        throw this.unexpected();
      }
    }

    flowParseDeclareVariable(node) {
      this.next();
      node.id = this.flowParseTypeAnnotatableIdentifier(true);
      this.scope.declareName(node.id.name, _scopeflags.BIND_VAR, node.id.start);
      this.semicolon();
      return this.finishNode(node, "DeclareVariable");
    }

    flowParseDeclareModule(node) {
      this.scope.enter(_scopeflags.SCOPE_OTHER);

      if (this.match(_types.types.string)) {
        node.id = this.parseExprAtom();
      } else {
        node.id = this.parseIdentifier();
      }

      const bodyNode = node.body = this.startNode();
      const body = bodyNode.body = [];
      this.expect(_types.types.braceL);

      while (!this.match(_types.types.braceR)) {
        let bodyNode = this.startNode();

        if (this.match(_types.types._import)) {
          this.next();

          if (!this.isContextual("type") && !this.match(_types.types._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(_types.types.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, insideModule) {
      this.expect(_types.types._export);

      if (this.eat(_types.types._default)) {
        if (this.match(_types.types._function) || this.match(_types.types._class)) {
          node.declaration = this.flowParseDeclare(this.startNode());
        } else {
          node.declaration = this.flowParseType();
          this.semicolon();
        }

        node.default = true;
        return this.finishNode(node, "DeclareExportDeclaration");
      } else {
        if (this.match(_types.types._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(_types.types._var) || this.match(_types.types._function) || this.match(_types.types._class) || this.isContextual("opaque")) {
            node.declaration = this.flowParseDeclare(this.startNode());
            node.default = false;
            return this.finishNode(node, "DeclareExportDeclaration");
          } else if (this.match(_types.types.star) || this.match(_types.types.braceL) || this.isContextual("interface") || this.isContextual("type") || this.isContextual("opaque")) {
            node = this.parseExport(node);

            if (node.type === "ExportNamedDeclaration") {
              node.type = "ExportDeclaration";
              node.default = false;
              delete node.exportKind;
            }

            node.type = "Declare" + node.type;
            return node;
          }
      }

      throw this.unexpected();
    }

    flowParseDeclareModuleExports(node) {
      this.next();
      this.expectContextual("exports");
      node.typeAnnotation = this.flowParseTypeAnnotation();
      this.semicolon();
      return this.finishNode(node, "DeclareModuleExports");
    }

    flowParseDeclareTypeAlias(node) {
      this.next();
      this.flowParseTypeAlias(node);
      node.type = "DeclareTypeAlias";
      return node;
    }

    flowParseDeclareOpaqueType(node) {
      this.next();
      this.flowParseOpaqueType(node, true);
      node.type = "DeclareOpaqueType";
      return node;
    }

    flowParseDeclareInterface(node) {
      this.next();
      this.flowParseInterfaceish(node);
      return this.finishNode(node, "DeclareInterface");
    }

    flowParseInterfaceish(node, isClass = false) {
      node.id = this.flowParseRestrictedIdentifier(!isClass, true);
      this.scope.declareName(node.id.name, isClass ? _scopeflags.BIND_FUNCTION : _scopeflags.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(_types.types._extends)) {
        do {
          node.extends.push(this.flowParseInterfaceExtends());
        } while (!isClass && this.eat(_types.types.comma));
      }

      if (this.isContextual("mixins")) {
        this.next();

        do {
          node.mixins.push(this.flowParseInterfaceExtends());
        } while (this.eat(_types.types.comma));
      }

      if (this.isContextual("implements")) {
        this.next();

        do {
          node.implements.push(this.flowParseInterfaceExtends());
        } while (this.eat(_types.types.comma));
      }

      node.body = this.flowParseObjectType({
        allowStatic: isClass,
        allowExact: false,
        allowSpread: false,
        allowProto: isClass,
        allowInexact: false
      });
    }

    flowParseInterfaceExtends() {
      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) {
      this.flowParseInterfaceish(node);
      return this.finishNode(node, "InterfaceDeclaration");
    }

    checkNotUnderscore(word) {
      if (word === "_") {
        this.raise(this.state.start, FlowErrors.UnexpectedReservedUnderscore);
      }
    }

    checkReservedType(word, startLoc, declaration) {
      if (!reservedTypes.has(word)) return;
      this.raise(startLoc, declaration ? FlowErrors.AssignReservedType : FlowErrors.UnexpectedReservedType, word);
    }

    flowParseRestrictedIdentifier(liberal, declaration) {
      this.checkReservedType(this.state.value, this.state.start, declaration);
      return this.parseIdentifier(liberal);
    }

    flowParseTypeAlias(node) {
      node.id = this.flowParseRestrictedIdentifier(false, true);
      this.scope.declareName(node.id.name, _scopeflags.BIND_LEXICAL, node.id.start);

      if (this.isRelational("<")) {
        node.typeParameters = this.flowParseTypeParameterDeclaration();
      } else {
        node.typeParameters = null;
      }

      node.right = this.flowParseTypeInitialiser(_types.types.eq);
      this.semicolon();
      return this.finishNode(node, "TypeAlias");
    }

    flowParseOpaqueType(node, declare) {
      this.expectContextual("type");
      node.id = this.flowParseRestrictedIdentifier(true, true);
      this.scope.declareName(node.id.name, _scopeflags.BIND_LEXICAL, node.id.start);

      if (this.isRelational("<")) {
        node.typeParameters = this.flowParseTypeParameterDeclaration();
      } else {
        node.typeParameters = null;
      }

      node.supertype = null;

      if (this.match(_types.types.colon)) {
        node.supertype = this.flowParseTypeInitialiser(_types.types.colon);
      }

      node.impltype = null;

      if (!declare) {
        node.impltype = this.flowParseTypeInitialiser(_types.types.eq);
      }

      this.semicolon();
      return this.finishNode(node, "OpaqueType");
    }

    flowParseTypeParameter(requireDefault = false) {
      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(_types.types.eq)) {
        this.eat(_types.types.eq);
        node.default = this.flowParseType();
      } else {
        if (requireDefault) {
          this.raise(nodeStart, FlowErrors.MissingTypeParamDefault);
        }
      }

      return this.finishNode(node, "TypeParameter");
    }

    flowParseTypeParameterDeclaration() {
      const oldInType = this.state.inType;
      const node = this.startNode();
      node.params = [];
      this.state.inType = true;

      if (this.isRelational("<") || this.match(_types.types.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(_types.types.comma);
        }
      } while (!this.isRelational(">"));

      this.expectRelational(">");
      this.state.inType = oldInType;
      return this.finishNode(node, "TypeParameterDeclaration");
    }

    flowParseTypeParameterInstantiation() {
      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(_types.types.comma);
        }
      }

      this.state.noAnonFunctionType = oldNoAnonFunctionType;
      this.expectRelational(">");
      this.state.inType = oldInType;
      return this.finishNode(node, "TypeParameterInstantiation");
    }

    flowParseTypeParameterInstantiationCallOrNew() {
      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(_types.types.comma);
        }
      }

      this.expectRelational(">");
      this.state.inType = oldInType;
      return this.finishNode(node, "TypeParameterInstantiation");
    }

    flowParseInterfaceType() {
      const node = this.startNode();
      this.expectContextual("interface");
      node.extends = [];

      if (this.eat(_types.types._extends)) {
        do {
          node.extends.push(this.flowParseInterfaceExtends());
        } while (this.eat(_types.types.comma));
      }

      node.body = this.flowParseObjectType({
        allowStatic: false,
        allowExact: false,
        allowSpread: false,
        allowProto: false,
        allowInexact: false
      });
      return this.finishNode(node, "InterfaceTypeAnnotation");
    }

    flowParseObjectPropertyKey() {
      return this.match(_types.types.num) || this.match(_types.types.string) ? this.parseExprAtom() : this.parseIdentifier(true);
    }

    flowParseObjectTypeIndexer(node, isStatic, variance) {
      node.static = isStatic;

      if (this.lookahead().type === _types.types.colon) {
        node.id = this.flowParseObjectPropertyKey();
        node.key = this.flowParseTypeInitialiser();
      } else {
        node.id = null;
        node.key = this.flowParseType();
      }

      this.expect(_types.types.bracketR);
      node.value = this.flowParseTypeInitialiser();
      node.variance = variance;
      return this.finishNode(node, "ObjectTypeIndexer");
    }

    flowParseObjectTypeInternalSlot(node, isStatic) {
      node.static = isStatic;
      node.id = this.flowParseObjectPropertyKey();
      this.expect(_types.types.bracketR);
      this.expect(_types.types.bracketR);

      if (this.isRelational("<") || this.match(_types.types.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(_types.types.question)) {
          node.optional = true;
        }

        node.value = this.flowParseTypeInitialiser();
      }

      return this.finishNode(node, "ObjectTypeInternalSlot");
    }

    flowParseObjectTypeMethodish(node) {
      node.params = [];
      node.rest = null;
      node.typeParameters = null;

      if (this.isRelational("<")) {
        node.typeParameters = this.flowParseTypeParameterDeclaration();
      }

      this.expect(_types.types.parenL);

      while (!this.match(_types.types.parenR) && !this.match(_types.types.ellipsis)) {
        node.params.push(this.flowParseFunctionTypeParam());

        if (!this.match(_types.types.parenR)) {
          this.expect(_types.types.comma);
        }
      }

      if (this.eat(_types.types.ellipsis)) {
        node.rest = this.flowParseFunctionTypeParam();
      }

      this.expect(_types.types.parenR);
      node.returnType = this.flowParseTypeInitialiser();
      return this.finishNode(node, "FunctionTypeAnnotation");
    }

    flowParseObjectTypeCallProperty(node, isStatic) {
      const valueNode = this.startNode();
      node.static = isStatic;
      node.value = this.flowParseObjectTypeMethodish(valueNode);
      return this.finishNode(node, "ObjectTypeCallProperty");
    }

    flowParseObjectType({
      allowStatic,
      allowExact,
      allowSpread,
      allowProto,
      allowInexact
    }) {
      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(_types.types.braceBarL)) {
        this.expect(_types.types.braceBarL);
        endDelim = _types.types.braceBarR;
        exact = true;
      } else {
        this.expect(_types.types.braceL);
        endDelim = _types.types.braceR;
        exact = false;
      }

      nodeStart.exact = exact;

      while (!this.match(endDelim)) {
        let isStatic = false;
        let protoStart = null;
        let inexactStart = null;
        const node = this.startNode();

        if (allowProto && this.isContextual("proto")) {
          const lookahead = this.lookahead();

          if (lookahead.type !== _types.types.colon && lookahead.type !== _types.types.question) {
            this.next();
            protoStart = this.state.start;
            allowStatic = false;
          }
        }

        if (allowStatic && this.isContextual("static")) {
          const lookahead = this.lookahead();

          if (lookahead.type !== _types.types.colon && lookahead.type !== _types.types.question) {
            this.next();
            isStatic = true;
          }
        }

        const variance = this.flowParseVariance();

        if (this.eat(_types.types.bracketL)) {
          if (protoStart != null) {
            this.unexpected(protoStart);
          }

          if (this.eat(_types.types.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(_types.types.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 === _types.types.name || lookahead.type === _types.types.string || lookahead.type === _types.types.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(_types.types.braceR) && !this.match(_types.types.braceBarR)) {
          this.raise(inexactStart, FlowErrors.UnexpectedExplicitInexactInObject);
        }
      }

      this.expect(endDelim);

      if (allowSpread) {
        nodeStart.inexact = inexact;
      }

      const out = this.finishNode(nodeStart, "ObjectTypeAnnotation");
      this.state.inType = oldInType;
      return out;
    }

    flowParseObjectTypeProperty(node, isStatic, protoStart, variance, kind, allowSpread, allowInexact) {
      if (this.eat(_types.types.ellipsis)) {
        const isInexactToken = this.match(_types.types.comma) || this.match(_types.types.semi) || this.match(_types.types.braceR) || this.match(_types.types.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(_types.types.parenL)) {
          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(_types.types.question)) {
            optional = true;
          }

          node.value = this.flowParseTypeInitialiser();
          node.variance = variance;
        }

        node.optional = optional;
        return this.finishNode(node, "ObjectTypeProperty");
      }
    }

    flowCheckGetterSetterParams(property) {
      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, _error.Errors.BadGetterArity);
        } else {
          this.raise(start, _error.Errors.BadSetterArity);
        }
      }

      if (property.kind === "set" && property.value.rest) {
        this.raise(start, _error.Errors.BadSetterRestParameter);
      }
    }

    flowObjectTypeSemicolon() {
      if (!this.eat(_types.types.semi) && !this.eat(_types.types.comma) && !this.match(_types.types.braceR) && !this.match(_types.types.braceBarR)) {
        this.unexpected();
      }
    }

    flowParseQualifiedTypeIdentifier(startPos, startLoc, id) {
      startPos = startPos || this.state.start;
      startLoc = startLoc || this.state.startLoc;
      let node = id || this.flowParseRestrictedIdentifier(true);

      while (this.eat(_types.types.dot)) {
        const node2 = this.startNodeAt(startPos, startLoc);
        node2.qualification = node;
        node2.id = this.flowParseRestrictedIdentifier(true);
        node = this.finishNode(node2, "QualifiedTypeIdentifier");
      }

      return node;
    }

    flowParseGenericType(startPos, startLoc, id) {
      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() {
      const node = this.startNode();
      this.expect(_types.types._typeof);
      node.argument = this.flowParsePrimaryType();
      return this.finishNode(node, "TypeofTypeAnnotation");
    }

    flowParseTupleType() {
      const node = this.startNode();
      node.types = [];
      this.expect(_types.types.bracketL);

      while (this.state.pos < this.length && !this.match(_types.types.bracketR)) {
        node.types.push(this.flowParseType());
        if (this.match(_types.types.bracketR)) break;
        this.expect(_types.types.comma);
      }

      this.expect(_types.types.bracketR);
      return this.finishNode(node, "TupleTypeAnnotation");
    }

    flowParseFunctionTypeParam() {
      let name = null;
      let optional = false;
      let typeAnnotation = null;
      const node = this.startNode();
      const lh = this.lookahead();

      if (lh.type === _types.types.colon || lh.type === _types.types.question) {
        name = this.parseIdentifier();

        if (this.eat(_types.types.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) {
      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 = []) {
      let rest = null;

      while (!this.match(_types.types.parenR) && !this.match(_types.types.ellipsis)) {
        params.push(this.flowParseFunctionTypeParam());

        if (!this.match(_types.types.parenR)) {
          this.expect(_types.types.comma);
        }
      }

      if (this.eat(_types.types.ellipsis)) {
        rest = this.flowParseFunctionTypeParam();
      }

      return {
        params,
        rest
      };
    }

    flowIdentToTypeAnnotation(startPos, startLoc, node, id) {
      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);
      }
    }

    flowParsePrimaryType() {
      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 _types.types.name:
          if (this.isContextual("interface")) {
            return this.flowParseInterfaceType();
          }

          return this.flowIdentToTypeAnnotation(startPos, startLoc, node, this.parseIdentifier());

        case _types.types.braceL:
          return this.flowParseObjectType({
            allowStatic: false,
            allowExact: false,
            allowSpread: true,
            allowProto: false,
            allowInexact: true
          });

        case _types.types.braceBarL:
          return this.flowParseObjectType({
            allowStatic: false,
            allowExact: true,
            allowSpread: true,
            allowProto: false,
            allowInexact: false
          });

        case _types.types.bracketL:
          this.state.noAnonFunctionType = false;
          type = this.flowParseTupleType();
          this.state.noAnonFunctionType = oldNoAnonFunctionType;
          return type;

        case _types.types.relational:
          if (this.state.value === "<") {
            node.typeParameters = this.flowParseTypeParameterDeclaration();
            this.expect(_types.types.parenL);
            tmp = this.flowParseFunctionTypeParams();
            node.params = tmp.params;
            node.rest = tmp.rest;
            this.expect(_types.types.parenR);
            this.expect(_types.types.arrow);
            node.returnType = this.flowParseType();
            return this.finishNode(node, "FunctionTypeAnnotation");
          }

          break;

        case _types.types.parenL:
          this.next();

          if (!this.match(_types.types.parenR) && !this.match(_types.types.ellipsis)) {
            if (this.match(_types.types.name)) {
              const token = this.lookahead().type;
              isGroupedType = token !== _types.types.question && token !== _types.types.colon;
            } else {
              isGroupedType = true;
            }
          }

          if (isGroupedType) {
            this.state.noAnonFunctionType = false;
            type = this.flowParseType();
            this.state.noAnonFunctionType = oldNoAnonFunctionType;

            if (this.state.noAnonFunctionType || !(this.match(_types.types.comma) || this.match(_types.types.parenR) && this.lookahead().type === _types.types.arrow)) {
              this.expect(_types.types.parenR);
              return type;
            } else {
              this.eat(_types.types.comma);
            }
          }

          if (type) {
            tmp = this.flowParseFunctionTypeParams([this.reinterpretTypeAsFunctionTypeParam(type)]);
          } else {
            tmp = this.flowParseFunctionTypeParams();
          }

          node.params = tmp.params;
          node.rest = tmp.rest;
          this.expect(_types.types.parenR);
          this.expect(_types.types.arrow);
          node.returnType = this.flowParseType();
          node.typeParameters = null;
          return this.finishNode(node, "FunctionTypeAnnotation");

        case _types.types.string:
          return this.parseLiteral(this.state.value, "StringLiteralTypeAnnotation");

        case _types.types._true:
        case _types.types._false:
          node.value = this.match(_types.types._true);
          this.next();
          return this.finishNode(node, "BooleanLiteralTypeAnnotation");

        case _types.types.plusMin:
          if (this.state.value === "-") {
            this.next();

            if (this.match(_types.types.num)) {
              return this.parseLiteral(-this.state.value, "NumberLiteralTypeAnnotation", node.start, node.loc.start);
            }

            if (this.match(_types.types.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 _types.types.num:
          return this.parseLiteral(this.state.value, "NumberLiteralTypeAnnotation");

        case _types.types.bigint:
          return this.parseLiteral(this.state.value, "BigIntLiteralTypeAnnotation");

        case _types.types._void:
          this.next();
          return this.finishNode(node, "VoidTypeAnnotation");

        case _types.types._null:
          this.next();
          return this.finishNode(node, "NullLiteralTypeAnnotation");

        case _types.types._this:
          this.next();
          return this.finishNode(node, "ThisTypeAnnotation");

        case _types.types.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() {
      const startPos = this.state.start,
            startLoc = this.state.startLoc;
      let type = this.flowParsePrimaryType();

      while (this.match(_types.types.bracketL) && !this.canInsertSemicolon()) {
        const node = this.startNodeAt(startPos, startLoc);
        node.elementType = type;
        this.expect(_types.types.bracketL);
        this.expect(_types.types.bracketR);
        type = this.finishNode(node, "ArrayTypeAnnotation");
      }

      return type;
    }

    flowParsePrefixType() {
      const node = this.startNode();

      if (this.eat(_types.types.question)) {
        node.typeAnnotation = this.flowParsePrefixType();
        return this.finishNode(node, "NullableTypeAnnotation");
      } else {
        return this.flowParsePostfixType();
      }
    }

    flowParseAnonFunctionWithoutParens() {
      const param = this.flowParsePrefixType();

      if (!this.state.noAnonFunctionType && this.eat(_types.types.arrow)) {
        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() {
      const node = this.startNode();
      this.eat(_types.types.bitwiseAND);
      const type = this.flowParseAnonFunctionWithoutParens();
      node.types = [type];

      while (this.eat(_types.types.bitwiseAND)) {
        node.types.push(this.flowParseAnonFunctionWithoutParens());
      }

      return node.types.length === 1 ? type : this.finishNode(node, "IntersectionTypeAnnotation");
    }

    flowParseUnionType() {
      const node = this.startNode();
      this.eat(_types.types.bitwiseOR);
      const type = this.flowParseIntersectionType();
      node.types = [type];

      while (this.eat(_types.types.bitwiseOR)) {
        node.types.push(this.flowParseIntersectionType());
      }

      return node.types.length === 1 ? type : this.finishNode(node, "UnionTypeAnnotation");
    }

    flowParseType() {
      const oldInType = this.state.inType;
      this.state.inType = true;
      const type = this.flowParseUnionType();
      this.state.inType = oldInType;
      this.state.exprAllowed = this.state.exprAllowed || this.state.noAnonFunctionType;
      return type;
    }

    flowParseTypeOrImplicitInstantiation() {
      if (this.state.type === _types.types.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() {
      const node = this.startNode();
      node.typeAnnotation = this.flowParseTypeInitialiser();
      return this.finishNode(node, "TypeAnnotation");
    }

    flowParseTypeAnnotatableIdentifier(allowPrimitiveOverride) {
      const ident = allowPrimitiveOverride ? this.parseIdentifier() : this.flowParseRestrictedIdentifier();

      if (this.match(_types.types.colon)) {
        ident.typeAnnotation = this.flowParseTypeAnnotation();
        this.resetEndLocation(ident);
      }

      return ident;
    }

    typeCastToParameter(node) {
      node.expression.typeAnnotation = node.typeAnnotation;
      this.resetEndLocation(node.expression, node.typeAnnotation.end, node.typeAnnotation.loc.end);
      return node.expression;
    }

    flowParseVariance() {
      let variance = null;

      if (this.match(_types.types.plusMin)) {
        variance = this.startNode();

        if (this.state.value === "+") {
          variance.kind = "plus";
        } else {
          variance.kind = "minus";
        }

        this.next();
        this.finishNode(variance, "Variance");
      }

      return variance;
    }

    parseFunctionBody(node, allowExpressionBody, isMethod = false) {
      if (allowExpressionBody) {
        return this.forwardNoArrowParamsConversionAt(node, () => super.parseFunctionBody(node, true, isMethod));
      }

      return super.parseFunctionBody(node, false, isMethod);
    }

    parseFunctionBodyAndFinish(node, type, isMethod = false) {
      if (this.match(_types.types.colon)) {
        const typeNode = this.startNode();
        [typeNode.typeAnnotation, node.predicate] = this.flowParseTypeAndPredicateInitialiser();
        node.returnType = typeNode.typeAnnotation ? this.finishNode(typeNode, "TypeAnnotation") : null;
      }

      super.parseFunctionBodyAndFinish(node, type, isMethod);
    }

    parseStatement(context, topLevel) {
      if (this.state.strict && this.match(_types.types.name) && this.state.value === "interface") {
        const lookahead = this.lookahead();

        if (lookahead.type === _types.types.name || (0, _identifier.isKeyword)(lookahead.value)) {
          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);
      }

      const stmt = super.parseStatement(context, topLevel);

      if (this.flowPragma === undefined && !this.isValidDirective(stmt)) {
        this.flowPragma = null;
      }

      return stmt;
    }

    parseExpressionStatement(node, expr) {
      if (expr.type === "Identifier") {
        if (expr.name === "declare") {
          if (this.match(_types.types._class) || this.match(_types.types.name) || this.match(_types.types._function) || this.match(_types.types._var) || this.match(_types.types._export)) {
            return this.flowParseDeclare(node);
          }
        } else if (this.match(_types.types.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);
    }

    shouldParseExportDeclaration() {
      return this.isContextual("type") || this.isContextual("interface") || this.isContextual("opaque") || this.shouldParseEnums() && this.isContextual("enum") || super.shouldParseExportDeclaration();
    }

    isExportDefaultSpecifier() {
      if (this.match(_types.types.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() {
      if (this.shouldParseEnums() && this.isContextual("enum")) {
        const node = this.startNode();
        this.next();
        return this.flowParseEnumDeclaration(node);
      }

      return super.parseExportDefaultExpression();
    }

    parseConditional(expr, startPos, startLoc, refNeedsArrowPos) {
      if (!this.match(_types.types.question)) return expr;

      if (refNeedsArrowPos) {
        const result = this.tryParse(() => super.parseConditional(expr, startPos, startLoc));

        if (!result.node) {
          refNeedsArrowPos.start = result.error.pos || this.state.start;
          return expr;
        }

        if (result.error) this.state = result.failState;
        return result.node;
      }

      this.expect(_types.types.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) {
          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(_types.types.colon);
      node.test = expr;
      node.consequent = consequent;
      node.alternate = this.forwardNoArrowParamsConversionAt(node, () => this.parseMaybeAssign(undefined, undefined, undefined));
      return this.finishNode(node, "ConditionalExpression");
    }

    tryParseConditionalConsequent() {
      this.state.noArrowParamsConversionAt.push(this.state.start);
      const consequent = this.parseMaybeAssignAllowIn();
      const failed = !this.match(_types.types.colon);
      this.state.noArrowParamsConversionAt.pop();
      return {
        consequent,
        failed
      };
    }

    getArrowLikeExpressions(node, disallowInvalid) {
      const stack = [node];
      const arrows = [];

      while (stack.length !== 0) {
        const node = stack.pop();

        if (node.type === "ArrowFunctionExpression") {
          if (node.typeParameters || !node.returnType) {
            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) {
      this.toAssignableList(node.params, node.extra?.trailingComma);
      this.scope.enter(_scopeflags.SCOPE_FUNCTION | _scopeflags.SCOPE_ARROW);
      super.checkParams(node, false, true);
      this.scope.exit();
    }

    forwardNoArrowParamsConversionAt(node, parse) {
      let result;

      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, startPos, startLoc) {
      node = super.parseParenItem(node, startPos, startLoc);

      if (this.eat(_types.types.question)) {
        node.optional = true;
        this.resetEndLocation(node);
      }

      if (this.match(_types.types.colon)) {
        const typeCastNode = this.startNodeAt(startPos, startLoc);
        typeCastNode.expression = node;
        typeCastNode.typeAnnotation = this.flowParseTypeAnnotation();
        return this.finishNode(typeCastNode, "TypeCastExpression");
      }

      return node;
    }

    assertModuleNodeAllowed(node) {
      if (node.type === "ImportDeclaration" && (node.importKind === "type" || node.importKind === "typeof") || node.type === "ExportNamedDeclaration" && node.exportKind === "type" || node.type === "ExportAllDeclaration" && node.exportKind === "type") {
        return;
      }

      super.assertModuleNodeAllowed(node);
    }

    parseExport(node) {
      const decl = super.parseExport(node);

      if (decl.type === "ExportNamedDeclaration" || decl.type === "ExportAllDeclaration") {
        decl.exportKind = decl.exportKind || "value";
      }

      return decl;
    }

    parseExportDeclaration(node) {
      if (this.isContextual("type")) {
        node.exportKind = "type";
        const declarationNode = this.startNode();
        this.next();

        if (this.match(_types.types.braceL)) {
          node.specifiers = this.parseExportSpecifiers();
          this.parseExportFrom(node);
          return null;
        } else {
          return this.flowParseTypeAlias(declarationNode);
        }
      } else if (this.isContextual("opaque")) {
        node.exportKind = "type";
        const declarationNode = this.startNode();
        this.next();
        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) {
      if (super.eatExportStar(...arguments)) return true;

      if (this.isContextual("type") && this.lookahead().type === _types.types.star) {
        node.exportKind = "type";
        this.next();
        this.next();
        return true;
      }

      return false;
    }

    maybeParseExportNamespaceSpecifier(node) {
      const pos = this.state.start;
      const hasNamespace = super.maybeParseExportNamespaceSpecifier(node);

      if (hasNamespace && node.exportKind === "type") {
        this.unexpected(pos);
      }

      return hasNamespace;
    }

    parseClassId(node, isStatement, optionalId) {
      super.parseClassId(node, isStatement, optionalId);

      if (this.isRelational("<")) {
        node.typeParameters = this.flowParseTypeParameterDeclaration();
      }
    }

    parseClassMember(classBody, member, state) {
      const pos = this.state.start;

      if (this.isContextual("declare")) {
        if (this.parseClassMemberFromModifier(classBody, member)) {
          return;
        }

        member.declare = true;
      }

      super.parseClassMember(classBody, member, state);

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

    getTokenFromCode(code) {
      const next = this.input.charCodeAt(this.state.pos + 1);

      if (code === 123 && next === 124) {
        return this.finishOp(_types.types.braceBarL, 2);
      } else if (this.state.inType && (code === 62 || code === 60)) {
        return this.finishOp(_types.types.relational, 1);
      } else if (this.state.inType && code === 63) {
        return this.finishOp(_types.types.question, 1);
      } else if ((0, _identifier.isIteratorStart)(code, next)) {
        this.state.isIterator = true;
        return super.readWord();
      } else {
        return super.getTokenFromCode(code);
      }
    }

    isAssignable(node, isBinding) {
      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) {
      if (node.type === "TypeCastExpression") {
        return super.toAssignable(this.typeCastToParameter(node));
      } else {
        return super.toAssignable(node);
      }
    }

    toAssignableList(exprList, trailingCommaPos) {
      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);
    }

    toReferencedList(exprList, isParenthesizedExpr) {
      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;
    }

    parseArrayLike(close, canBePattern, isTuple, refExpressionErrors) {
      const node = super.parseArrayLike(close, canBePattern, isTuple, refExpressionErrors);

      if (canBePattern && !this.state.maybeInArrowParameters) {
        this.toReferencedList(node.elements);
      }

      return node;
    }

    checkLVal(expr, bindingType = _scopeflags.BIND_NONE, checkClashes, contextDescription) {
      if (expr.type !== "TypeCastExpression") {
        return super.checkLVal(expr, bindingType, checkClashes, contextDescription);
      }
    }

    parseClassProperty(node) {
      if (this.match(_types.types.colon)) {
        node.typeAnnotation = this.flowParseTypeAnnotation();
      }

      return super.parseClassProperty(node);
    }

    parseClassPrivateProperty(node) {
      if (this.match(_types.types.colon)) {
        node.typeAnnotation = this.flowParseTypeAnnotation();
      }

      return super.parseClassPrivateProperty(node);
    }

    isClassMethod() {
      return this.isRelational("<") || super.isClassMethod();
    }

    isClassProperty() {
      return this.match(_types.types.colon) || super.isClassProperty();
    }

    isNonstaticConstructor(method) {
      return !this.match(_types.types.colon) && super.isNonstaticConstructor(method);
    }

    pushClassMethod(classBody, method, isGenerator, isAsync, isConstructor, allowsDirectSuper) {
      if (method.variance) {
        this.unexpected(method.variance.start);
      }

      delete method.variance;

      if (this.isRelational("<")) {
        method.typeParameters = this.flowParseTypeParameterDeclaration();
      }

      super.pushClassMethod(classBody, method, isGenerator, isAsync, isConstructor, allowsDirectSuper);
    }

    pushClassPrivateMethod(classBody, method, isGenerator, isAsync) {
      if (method.variance) {
        this.unexpected(method.variance.start);
      }

      delete method.variance;

      if (this.isRelational("<")) {
        method.typeParameters = this.flowParseTypeParameterDeclaration();
      }

      super.pushClassPrivateMethod(classBody, method, isGenerator, isAsync);
    }

    parseClassSuper(node) {
      super.parseClassSuper(node);

      if (node.superClass && this.isRelational("<")) {
        node.superTypeParameters = this.flowParseTypeParameterInstantiation();
      }

      if (this.isContextual("implements")) {
        this.next();
        const implemented = node.implements = [];

        do {
          const node = this.startNode();
          node.id = this.flowParseRestrictedIdentifier(true);

          if (this.isRelational("<")) {
            node.typeParameters = this.flowParseTypeParameterInstantiation();
          } else {
            node.typeParameters = null;
          }

          implemented.push(this.finishNode(node, "ClassImplements"));
        } while (this.eat(_types.types.comma));
      }
    }

    parsePropertyName(node, isPrivateNameAllowed) {
      const variance = this.flowParseVariance();
      const key = super.parsePropertyName(node, isPrivateNameAllowed);
      node.variance = variance;
      return key;
    }

    parseObjPropValue(prop, startPos, startLoc, isGenerator, isAsync, isPattern, isAccessor, refExpressionErrors) {
      if (prop.variance) {
        this.unexpected(prop.variance.start);
      }

      delete prop.variance;
      let typeParameters;

      if (this.isRelational("<") && !isAccessor) {
        typeParameters = this.flowParseTypeParameterDeclaration();
        if (!this.match(_types.types.parenL)) this.unexpected();
      }

      super.parseObjPropValue(prop, startPos, startLoc, isGenerator, isAsync, isPattern, isAccessor, refExpressionErrors);

      if (typeParameters) {
        (prop.value || prop).typeParameters = typeParameters;
      }
    }

    parseAssignableListItemTypes(param) {
      if (this.eat(_types.types.question)) {
        if (param.type !== "Identifier") {
          this.raise(param.start, FlowErrors.OptionalBindingPattern);
        }

        param.optional = true;
      }

      if (this.match(_types.types.colon)) {
        param.typeAnnotation = this.flowParseTypeAnnotation();
      }

      this.resetEndLocation(param);
      return param;
    }

    parseMaybeDefault(startPos, startLoc, left) {
      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) {
      if (!hasTypeImportKind(node)) {
        return super.shouldParseDefaultImport(node);
      }

      return isMaybeDefaultImport(this.state);
    }

    parseImportSpecifierLocal(node, specifier, type, contextDescription) {
      specifier.local = hasTypeImportKind(node) ? this.flowParseRestrictedIdentifier(true, true) : this.parseIdentifier();
      this.checkLVal(specifier.local, _scopeflags.BIND_LEXICAL, undefined, contextDescription);
      node.specifiers.push(this.finishNode(specifier, type));
    }

    maybeParseDefaultImportSpecifier(node) {
      node.importKind = "value";
      let kind = null;

      if (this.match(_types.types._typeof)) {
        kind = "typeof";
      } else if (this.isContextual("type")) {
        kind = "type";
      }

      if (kind) {
        const lh = this.lookahead();

        if (kind === "type" && lh.type === _types.types.star) {
          this.unexpected(lh.start);
        }

        if (isMaybeDefaultImport(lh) || lh.type === _types.types.braceL || lh.type === _types.types.star) {
          this.next();
          node.importKind = kind;
        }
      }

      return super.maybeParseDefaultImportSpecifier(node);
    }

    parseImportSpecifier(node) {
      const specifier = this.startNode();
      const firstIdentLoc = this.state.start;
      const firstIdent = this.parseModuleExportName();
      let specifierTypeKind = null;

      if (firstIdent.type === "Identifier") {
        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(_types.types.name) && !this.state.type.keyword) {
          specifier.imported = as_ident;
          specifier.importKind = specifierTypeKind;
          specifier.local = as_ident.__clone();
        } else {
          specifier.imported = firstIdent;
          specifier.importKind = null;
          specifier.local = this.parseIdentifier();
        }
      } else if (specifierTypeKind !== null && (this.match(_types.types.name) || this.state.type.keyword)) {
        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 {
        if (firstIdent.type === "StringLiteral") {
          throw this.raise(specifier.start, _error.Errors.ImportBindingIsString, firstIdent.value);
        }

        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, true);
      }

      if (isBinding && !nodeIsTypeImport && !specifierIsTypeImport) {
        this.checkReservedWord(specifier.local.name, specifier.start, true, true);
      }

      this.checkLVal(specifier.local, _scopeflags.BIND_LEXICAL, undefined, "import specifier");
      node.specifiers.push(this.finishNode(specifier, "ImportSpecifier"));
    }

    parseFunctionParams(node, allowModifiers) {
      const kind = node.kind;

      if (kind !== "get" && kind !== "set" && this.isRelational("<")) {
        node.typeParameters = this.flowParseTypeParameterDeclaration();
      }

      super.parseFunctionParams(node, allowModifiers);
    }

    parseVarId(decl, kind) {
      super.parseVarId(decl, kind);

      if (this.match(_types.types.colon)) {
        decl.id.typeAnnotation = this.flowParseTypeAnnotation();
        this.resetEndLocation(decl.id);
      }
    }

    parseAsyncArrowFromCallExpression(node, call) {
      if (this.match(_types.types.colon)) {
        const oldNoAnonFunctionType = this.state.noAnonFunctionType;
        this.state.noAnonFunctionType = true;
        node.returnType = this.flowParseTypeAnnotation();
        this.state.noAnonFunctionType = oldNoAnonFunctionType;
      }

      return super.parseAsyncArrowFromCallExpression(node, call);
    }

    shouldParseAsyncArrow() {
      return this.match(_types.types.colon) || super.shouldParseAsyncArrow();
    }

    parseMaybeAssign(refExpressionErrors, afterLeftParse, refNeedsArrowPos) {
      let state = null;
      let jsx;

      if (this.hasPlugin("jsx") && (this.match(_types.types.jsxTagStart) || this.isRelational("<"))) {
        state = this.state.clone();
        jsx = this.tryParse(() => super.parseMaybeAssign(refExpressionErrors, afterLeftParse, refNeedsArrowPos), state);
        if (!jsx.error) return jsx.node;
        const {
          context
        } = this.state;

        if (context[context.length - 1] === _context.types.j_oTag) {
          context.length -= 2;
        } else if (context[context.length - 1] === _context.types.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;
          });

          if (arrowExpression.type !== "ArrowFunctionExpression" && arrowExpression.extra?.parenthesized) {
            abort();
          }

          const expr = this.maybeUnwrapTypeCastExpression(arrowExpression);
          expr.typeParameters = typeParameters;
          this.resetStartLocationFromNode(expr, typeParameters);
          return arrowExpression;
        }, state);
        let arrowExpression = null;

        if (arrow.node && this.maybeUnwrapTypeCastExpression(arrow.node).type === "ArrowFunctionExpression") {
          if (!arrow.error && !arrow.aborted) {
            if (arrow.node.async) {
              this.raise(typeParameters.start, FlowErrors.UnexpectedTypeParameterBeforeAsyncArrowFunction);
            }

            return arrow.node;
          }

          arrowExpression = arrow.node;
        }

        if (jsx?.node) {
          this.state = jsx.failState;
          return jsx.node;
        }

        if (arrowExpression) {
          this.state = arrow.failState;
          return arrowExpression;
        }

        if (jsx?.thrown) throw jsx.error;
        if (arrow.thrown) throw arrow.error;
        throw this.raise(typeParameters.start, FlowErrors.UnexpectedTokenAfterTypeParameter);
      }

      return super.parseMaybeAssign(refExpressionErrors, afterLeftParse, refNeedsArrowPos);
    }

    parseArrow(node) {
      if (this.match(_types.types.colon)) {
        const result = this.tryParse(() => {
          const oldNoAnonFunctionType = this.state.noAnonFunctionType;
          this.state.noAnonFunctionType = true;
          const typeNode = this.startNode();
          [typeNode.typeAnnotation, node.predicate] = this.flowParseTypeAndPredicateInitialiser();
          this.state.noAnonFunctionType = oldNoAnonFunctionType;
          if (this.canInsertSemicolon()) this.unexpected();
          if (!this.match(_types.types.arrow)) this.unexpected();
          return typeNode;
        });
        if (result.thrown) return null;
        if (result.error) this.state = result.failState;
        node.returnType = result.node.typeAnnotation ? this.finishNode(result.node, "TypeAnnotation") : null;
      }

      return super.parseArrow(node);
    }

    shouldParseArrow() {
      return this.match(_types.types.colon) || super.shouldParseArrow();
    }

    setArrowFunctionParameters(node, params) {
      if (this.state.noArrowParamsConversionAt.indexOf(node.start) !== -1) {
        node.params = params;
      } else {
        super.setArrowFunctionParameters(node, params);
      }
    }

    checkParams(node, allowDuplicates, isArrowFunction) {
      if (isArrowFunction && this.state.noArrowParamsConversionAt.indexOf(node.start) !== -1) {
        return;
      }

      return super.checkParams(...arguments);
    }

    parseParenAndDistinguishExpression(canBeArrow) {
      return super.parseParenAndDistinguishExpression(canBeArrow && this.state.noArrowAt.indexOf(this.state.start) === -1);
    }

    parseSubscripts(base, startPos, startLoc, noCalls) {
      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(_types.types.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, startPos, startLoc, noCalls, subscriptState) {
      if (this.match(_types.types.questionDot) && this.isLookaheadToken_lt()) {
        subscriptState.optionalChainMember = true;

        if (noCalls) {
          subscriptState.stop = true;
          return base;
        }

        this.next();
        const node = this.startNodeAt(startPos, startLoc);
        node.callee = base;
        node.typeArguments = this.flowParseTypeParameterInstantiation();
        this.expect(_types.types.parenL);
        node.arguments = this.parseCallExpressionArguments(_types.types.parenR, false);
        node.optional = true;
        return this.finishCallExpression(node, 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(_types.types.parenL);
          node.arguments = this.parseCallExpressionArguments(_types.types.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) {
      let targs = null;

      if (this.shouldParseTypes() && this.isRelational("<")) {
        targs = this.tryParse(() => this.flowParseTypeParameterInstantiationCallOrNew()).node;
      }

      node.typeArguments = targs;
      super.parseNewArguments(node);
    }

    parseAsyncArrowWithTypeParameters(startPos, startLoc) {
      const node = this.startNodeAt(startPos, startLoc);
      this.parseFunctionParams(node);
      if (!this.parseArrow(node)) return;
      return this.parseArrowExpression(node, undefined, true);
    }

    readToken_mult_modulo(code) {
      const next = this.input.charCodeAt(this.state.pos + 1);

      if (code === 42 && next === 47 && this.state.hasFlowComment) {
        this.state.hasFlowComment = false;
        this.state.pos += 2;
        this.nextToken();
        return;
      }

      super.readToken_mult_modulo(code);
    }

    readToken_pipe_amp(code) {
      const next = this.input.charCodeAt(this.state.pos + 1);

      if (code === 124 && next === 125) {
        this.finishOp(_types.types.braceBarR, 2);
        return;
      }

      super.readToken_pipe_amp(code);
    }

    parseTopLevel(file, program) {
      const fileNode = super.parseTopLevel(file, program);

      if (this.state.hasFlowComment) {
        this.raise(this.state.pos, FlowErrors.UnterminatedFlowComment);
      }

      return fileNode;
    }

    skipBlockComment() {
      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, _error.Errors.UnterminatedComment);
        }

        this.state.pos = end + 3;
        return;
      }

      super.skipBlockComment();
    }

    skipFlowComment() {
      const {
        pos
      } = this.state;
      let shiftToFirstNonWhiteSpace = 2;

      while ([32, 9].includes(this.input.charCodeAt(pos + shiftToFirstNonWhiteSpace))) {
        shiftToFirstNonWhiteSpace++;
      }

      const ch2 = this.input.charCodeAt(shiftToFirstNonWhiteSpace + pos);
      const ch3 = this.input.charCodeAt(shiftToFirstNonWhiteSpace + pos + 1);

      if (ch2 === 58 && ch3 === 58) {
        return shiftToFirstNonWhiteSpace + 2;
      }

      if (this.input.slice(shiftToFirstNonWhiteSpace + pos, shiftToFirstNonWhiteSpace + pos + 12) === "flow-include") {
        return shiftToFirstNonWhiteSpace + 12;
      }

      if (ch2 === 58 && ch3 !== 58) {
        return shiftToFirstNonWhiteSpace;
      }

      return false;
    }

    hasFlowCommentCompletion() {
      const end = this.input.indexOf("*/", this.state.pos);

      if (end === -1) {
        throw this.raise(this.state.pos, _error.Errors.UnterminatedComment);
      }
    }

    flowEnumErrorBooleanMemberNotInitialized(pos, {
      enumName,
      memberName
    }) {
      this.raise(pos, FlowErrors.EnumBooleanMemberNotInitialized, memberName, enumName);
    }

    flowEnumErrorInvalidMemberName(pos, {
      enumName,
      memberName
    }) {
      const suggestion = memberName[0].toUpperCase() + memberName.slice(1);
      this.raise(pos, FlowErrors.EnumInvalidMemberName, memberName, suggestion, enumName);
    }

    flowEnumErrorDuplicateMemberName(pos, {
      enumName,
      memberName
    }) {
      this.raise(pos, FlowErrors.EnumDuplicateMemberName, memberName, enumName);
    }

    flowEnumErrorInconsistentMemberValues(pos, {
      enumName
    }) {
      this.raise(pos, FlowErrors.EnumInconsistentMemberValues, enumName);
    }

    flowEnumErrorInvalidExplicitType(pos, {
      enumName,
      suppliedType
    }) {
      return this.raise(pos, suppliedType === null ? FlowErrors.EnumInvalidExplicitTypeUnknownSupplied : FlowErrors.EnumInvalidExplicitType, enumName, suppliedType);
    }

    flowEnumErrorInvalidMemberInitializer(pos, {
      enumName,
      explicitType,
      memberName
    }) {
      let message = null;

      switch (explicitType) {
        case "boolean":
        case "number":
        case "string":
          message = FlowErrors.EnumInvalidMemberInitializerPrimaryType;
          break;

        case "symbol":
          message = FlowErrors.EnumInvalidMemberInitializerSymbolType;
          break;

        default:
          message = FlowErrors.EnumInvalidMemberInitializerUnknownType;
      }

      return this.raise(pos, message, enumName, memberName, explicitType);
    }

    flowEnumErrorNumberMemberNotInitialized(pos, {
      enumName,
      memberName
    }) {
      this.raise(pos, FlowErrors.EnumNumberMemberNotInitialized, enumName, memberName);
    }

    flowEnumErrorStringMemberInconsistentlyInitailized(pos, {
      enumName
    }) {
      this.raise(pos, FlowErrors.EnumStringMemberInconsistentlyInitailized, enumName);
    }

    flowEnumMemberInit() {
      const startPos = this.state.start;

      const endOfInit = () => this.match(_types.types.comma) || this.match(_types.types.braceR);

      switch (this.state.type) {
        case _types.types.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 _types.types.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 _types.types._true:
        case _types.types._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() {
      const pos = this.state.start;
      const id = this.parseIdentifier(true);
      const init = this.eat(_types.types.eq) ? this.flowEnumMemberInit() : {
        type: "none",
        pos
      };
      return {
        id,
        init
      };
    }

    flowEnumCheckExplicitTypeMismatch(pos, context, expectedType) {
      const {
        explicitType
      } = context;

      if (explicitType === null) {
        return;
      }

      if (explicitType !== expectedType) {
        this.flowEnumErrorInvalidMemberInitializer(pos, context);
      }
    }

    flowEnumMembers({
      enumName,
      explicitType
    }) {
      const seenNames = new Set();
      const members = {
        booleanMembers: [],
        numberMembers: [],
        stringMembers: [],
        defaultedMembers: []
      };

      while (!this.match(_types.types.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(_types.types.braceR)) {
          this.expect(_types.types.comma);
        }
      }

      return members;
    }

    flowEnumStringMembers(initializedMembers, defaultedMembers, {
      enumName
    }) {
      if (initializedMembers.length === 0) {
        return defaultedMembers;
      } else if (defaultedMembers.length === 0) {
        return initializedMembers;
      } else if (defaultedMembers.length > initializedMembers.length) {
        for (let _i = 0; _i < initializedMembers.length; _i++) {
          const member = initializedMembers[_i];
          this.flowEnumErrorStringMemberInconsistentlyInitailized(member.start, {
            enumName
          });
        }

        return defaultedMembers;
      } else {
        for (let _i2 = 0; _i2 < defaultedMembers.length; _i2++) {
          const member = defaultedMembers[_i2];
          this.flowEnumErrorStringMemberInconsistentlyInitailized(member.start, {
            enumName
          });
        }

        return initializedMembers;
      }
    }

    flowEnumParseExplicitType({
      enumName
    }) {
      if (this.eatContextual("of")) {
        if (!this.match(_types.types.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, {
      enumName,
      nameLoc
    }) {
      const explicitType = this.flowEnumParseExplicitType({
        enumName
      });
      this.expect(_types.types.braceL);
      const members = this.flowEnumMembers({
        enumName,
        explicitType
      });

      switch (explicitType) {
        case "boolean":
          node.explicitType = true;
          node.members = members.booleanMembers;
          this.expect(_types.types.braceR);
          return this.finishNode(node, "EnumBooleanBody");

        case "number":
          node.explicitType = true;
          node.members = members.numberMembers;
          this.expect(_types.types.braceR);
          return this.finishNode(node, "EnumNumberBody");

        case "string":
          node.explicitType = true;
          node.members = this.flowEnumStringMembers(members.stringMembers, members.defaultedMembers, {
            enumName
          });
          this.expect(_types.types.braceR);
          return this.finishNode(node, "EnumStringBody");

        case "symbol":
          node.members = members.defaultedMembers;
          this.expect(_types.types.braceR);
          return this.finishNode(node, "EnumSymbolBody");

        default:
          {
            const empty = () => {
              node.members = [];
              this.expect(_types.types.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(_types.types.braceR);
              return this.finishNode(node, "EnumStringBody");
            } else if (!numsLen && !strsLen && boolsLen >= defaultedLen) {
              for (let _i3 = 0, _members$defaultedMem = members.defaultedMembers; _i3 < _members$defaultedMem.length; _i3++) {
                const member = _members$defaultedMem[_i3];
                this.flowEnumErrorBooleanMemberNotInitialized(member.start, {
                  enumName,
                  memberName: member.id.name
                });
              }

              node.members = members.booleanMembers;
              this.expect(_types.types.braceR);
              return this.finishNode(node, "EnumBooleanBody");
            } else if (!boolsLen && !strsLen && numsLen >= defaultedLen) {
              for (let _i4 = 0, _members$defaultedMem2 = members.defaultedMembers; _i4 < _members$defaultedMem2.length; _i4++) {
                const member = _members$defaultedMem2[_i4];
                this.flowEnumErrorNumberMemberNotInitialized(member.start, {
                  enumName,
                  memberName: member.id.name
                });
              }

              node.members = members.numberMembers;
              this.expect(_types.types.braceR);
              return this.finishNode(node, "EnumNumberBody");
            } else {
              this.flowEnumErrorInconsistentMemberValues(nameLoc, {
                enumName
              });
              return empty();
            }
          }
      }
    }

    flowParseEnumDeclaration(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) {
      if (this.match(_types.types.name) && this.state.value === "of" && prevType === _types.types.name && this.input.slice(this.state.lastTokStart, this.state.lastTokEnd) === "interface") {
        this.state.exprAllowed = false;
      } else {
        super.updateContext(prevType);
      }
    }

    isLookaheadToken_lt() {
      const next = this.nextTokenStart();

      if (this.input.charCodeAt(next) === 60) {
        const afterNext = this.input.charCodeAt(next + 1);
        return afterNext !== 60 && afterNext !== 61;
      }

      return false;
    }

    maybeUnwrapTypeCastExpression(node) {
      return node.type === "TypeCastExpression" ? node.expression : node;
    }

  }, _temp;
};

exports.default = _default;