text: // @flow
  
  import { type TokenType } from "../tokenizer/types";
  import type Parser from "../parser";
  import type { ExpressionErrors } from "../parser/util";
  import * as N from "../types";
  import type { Node as NodeType, NodeBase, File } from "../types";
  import type { Position } from "../util/location";
  import { Errors } from "../parser/error";
  
  const { defineProperty } = Object;
  const toUnenumerable = (object, key) =>
    defineProperty(object, key, { enumerable: false, value: object[key] });
  
  function toESTreeLocation(node: any) {
    toUnenumerable(node.loc.start, "index");
    toUnenumerable(node.loc.end, "index");
  
    return node;
  }
  
  export default (superClass: Class<Parser>): Class<Parser> =>
    class extends superClass {
      parse(): File {
        const file = toESTreeLocation(super.parse());
  
        if (this.options.tokens) {
          file.tokens = file.tokens.map(toESTreeLocation);
        }
  
        return file;
      }
  
      parseRegExpLiteral({ pattern, flags }): N.Node {
        let regex = null;
        try {
          regex = new RegExp(pattern, flags);
        } catch (e) {
          // In environments that don't support these flags value will
          // be null as the regex can't be represented natively.
        }
        const node = this.estreeParseLiteral<N.EstreeRegExpLiteral>(regex);
        node.regex = { pattern, flags };
  
        return node;
      }
  
      parseBigIntLiteral(value: any): N.Node {
        // https://github.com/estree/estree/blob/master/es2020.md#bigintliteral
        let bigInt;
        try {
          // $FlowIgnore
          bigInt = BigInt(value);
        } catch {
          bigInt = null;
        }
        const node = this.estreeParseLiteral<N.EstreeBigIntLiteral>(bigInt);
        node.bigint = String(node.value || value);
  
        return node;
      }
  
      parseDecimalLiteral(value: any): N.Node {
        // https://github.com/estree/estree/blob/master/experimental/decimal.md
        // todo: use BigDecimal when node supports it.
        const decimal = null;
        const node = this.estreeParseLiteral(decimal);
        node.decimal = String(node.value || value);
  
        return node;
      }
  
      estreeParseLiteral<T: N.Node>(value: any) {
        return this.parseLiteral<T>(value, "Literal");
      }
  
      parseStringLiteral(value: any): N.Node {
        return this.estreeParseLiteral(value);
      }
  
      parseNumericLiteral(value: any): any {
        return this.estreeParseLiteral(value);
      }
  
      parseNullLiteral(): N.Node {
        return this.estreeParseLiteral(null);
      }
  
      parseBooleanLiteral(value: boolean): N.BooleanLiteral {
        return this.estreeParseLiteral(value);
      }
  
      directiveToStmt(directive: N.Directive): N.ExpressionStatement {
        const directiveLiteral = directive.value;
  
        const stmt = this.startNodeAt(directive.start, directive.loc.start);
        const expression = this.startNodeAt(
          directiveLiteral.start,
          directiveLiteral.loc.start,
        );
  
        expression.value = directiveLiteral.extra.expressionValue;
        expression.raw = directiveLiteral.extra.raw;
  
        stmt.expression = this.finishNodeAt(
          expression,
          "Literal",
          directiveLiteral.loc.end,
        );
        stmt.directive = directiveLiteral.extra.raw.slice(1, -1);
  
        return this.finishNodeAt(stmt, "ExpressionStatement", directive.loc.end);
      }
  
      // ==================================
      // Overrides
      // ==================================
  
      initFunction(
        node: N.BodilessFunctionOrMethodBase,
        isAsync: ?boolean,
      ): void {
        super.initFunction(node, isAsync);
        node.expression = false;
      }
  
      checkDeclaration(node: N.Pattern | N.ObjectProperty): void {
        if (node != null && this.isObjectProperty(node)) {
          this.checkDeclaration(((node: any): N.EstreeProperty).value);
        } else {
          super.checkDeclaration(node);
        }
      }
  
      getObjectOrClassMethodParams(method: N.ObjectMethod | N.ClassMethod) {
        return ((method: any): N.EstreeProperty | N.EstreeMethodDefinition).value
          .params;
      }
  
      isValidDirective(stmt: N.Statement): boolean {
        return (
          stmt.type === "ExpressionStatement" &&
          stmt.expression.type === "Literal" &&
          typeof stmt.expression.value === "string" &&
          !stmt.expression.extra?.parenthesized
        );
      }
  
      parseBlockBody(
        node: N.BlockStatementLike,
        ...args: [?boolean, boolean, TokenType, void | (boolean => void)]
      ): void {
        super.parseBlockBody(node, ...args);
  
        const directiveStatements = node.directives.map(d =>
          this.directiveToStmt(d),
        );
        node.body = directiveStatements.concat(node.body);
        // $FlowIgnore - directives isn't optional in the type definition
        delete node.directives;
      }
  
      pushClassMethod(
        classBody: N.ClassBody,
        method: N.ClassMethod,
        isGenerator: boolean,
        isAsync: boolean,
        isConstructor: boolean,
        allowsDirectSuper: boolean,
      ): void {
        this.parseMethod(
          method,
          isGenerator,
          isAsync,
          isConstructor,
          allowsDirectSuper,
          "ClassMethod",
          true,
        );
        if (method.typeParameters) {
          // $FlowIgnore
          method.value.typeParameters = method.typeParameters;
          delete method.typeParameters;
        }
        classBody.body.push(method);
      }
  
      parsePrivateName(): any {
        const node = super.parsePrivateName();
        if (!process.env.BABEL_8_BREAKING) {
          if (!this.getPluginOption("estree", "classFeatures")) {
            return node;
          }
        }
        return this.convertPrivateNameToPrivateIdentifier(node);
      }
  
      convertPrivateNameToPrivateIdentifier(
        node: N.PrivateName,
      ): N.EstreePrivateIdentifier {
        const name = super.getPrivateNameSV(node);
        node = (node: any);
        delete node.id;
        node.name = name;
        node.type = "PrivateIdentifier";
        return node;
      }
  
      isPrivateName(node: N.Node): boolean {
        if (!process.env.BABEL_8_BREAKING) {
          if (!this.getPluginOption("estree", "classFeatures")) {
            return super.isPrivateName(node);
          }
        }
        return node.type === "PrivateIdentifier";
      }
  
      getPrivateNameSV(node: N.Node): string {
        if (!process.env.BABEL_8_BREAKING) {
          if (!this.getPluginOption("estree", "classFeatures")) {
            return super.getPrivateNameSV(node);
          }
        }
        return node.name;
      }
  
      parseLiteral<T: N.Node>(value: any, type: $ElementType<T, "type">): T {
        const node = super.parseLiteral<T>(value, type);
        node.raw = node.extra.raw;
        delete node.extra;
  
        return node;
      }
  
      parseFunctionBody(
        node: N.Function,
        allowExpression: ?boolean,
        isMethod?: boolean = false,
      ): void {
        super.parseFunctionBody(node, allowExpression, isMethod);
        node.expression = node.body.type !== "BlockStatement";
      }
  
      parseMethod<T: N.MethodLike>(
        node: T,
        isGenerator: boolean,
        isAsync: boolean,
        isConstructor: boolean,
        allowDirectSuper: boolean,
        type: string,
        inClassScope: boolean = false,
      ): T {
        let funcNode = this.startNode();
        funcNode.kind = node.kind; // provide kind, so super method correctly sets state
        funcNode = super.parseMethod(
          funcNode,
          isGenerator,
          isAsync,
          isConstructor,
          allowDirectSuper,
          type,
          inClassScope,
        );
        funcNode.type = "FunctionExpression";
        delete funcNode.kind;
        // $FlowIgnore
        node.value = funcNode;
        if (type === "ClassPrivateMethod") {
          // $FlowIgnore
          node.computed = false;
        }
        type = "MethodDefinition";
        return this.finishNode(node, type);
      }
  
      parseClassProperty(...args: [N.ClassProperty]): any {
        const propertyNode = (super.parseClassProperty(...args): any);
        if (!process.env.BABEL_8_BREAKING) {
          if (!this.getPluginOption("estree", "classFeatures")) {
            return (propertyNode: N.EstreePropertyDefinition);
          }
        }
        propertyNode.type = "PropertyDefinition";
        return (propertyNode: N.EstreePropertyDefinition);
      }
  
      parseClassPrivateProperty(...args: [N.ClassPrivateProperty]): any {
        const propertyNode = (super.parseClassPrivateProperty(...args): any);
        if (!process.env.BABEL_8_BREAKING) {
          if (!this.getPluginOption("estree", "classFeatures")) {
            return (propertyNode: N.EstreePropertyDefinition);
          }
        }
        propertyNode.type = "PropertyDefinition";
        propertyNode.computed = false;
        return (propertyNode: N.EstreePropertyDefinition);
      }
  
      parseObjectMethod(
        prop: N.ObjectMethod,
        isGenerator: boolean,
        isAsync: boolean,
        isPattern: boolean,
        isAccessor: boolean,
      ): ?N.ObjectMethod {
        const node: N.EstreeProperty = (super.parseObjectMethod(
          prop,
          isGenerator,
          isAsync,
          isPattern,
          isAccessor,
        ): any);
  
        if (node) {
          node.type = "Property";
          if (((node: any): N.ClassMethod).kind === "method") node.kind = "init";
          node.shorthand = false;
        }
  
        return (node: any);
      }
  
      parseObjectProperty(
        prop: N.ObjectProperty,
        startPos: ?number,
        startLoc: ?Position,
        isPattern: boolean,
        refExpressionErrors: ?ExpressionErrors,
      ): ?N.ObjectProperty {
        const node: N.EstreeProperty = (super.parseObjectProperty(
          prop,
          startPos,
          startLoc,
          isPattern,
          refExpressionErrors,
        ): any);
  
        if (node) {
          node.kind = "init";
          node.type = "Property";
        }
  
        return (node: any);
      }
  
      isAssignable(node: N.Node, isBinding?: boolean): boolean {
        if (node != null && this.isObjectProperty(node)) {
          return this.isAssignable(node.value, isBinding);
        }
        return super.isAssignable(node, isBinding);
      }
  
      toAssignable(node: N.Node, isLHS: boolean = false): N.Node {
        if (node != null && this.isObjectProperty(node)) {
          const { key, value } = node;
          if (this.isPrivateName(key)) {
            this.classScope.usePrivateName(
              this.getPrivateNameSV(key),
              key.loc.start,
            );
          }
          this.toAssignable(value, isLHS);
          return node;
        }
  
        return super.toAssignable(node, isLHS);
      }
  
      toAssignableObjectExpressionProp(prop: N.Node, ...args) {
        if (prop.kind === "get" || prop.kind === "set") {
          this.raise(Errors.PatternHasAccessor, { node: prop.key });
        } else if (prop.method) {
          this.raise(Errors.PatternHasMethod, { node: prop.key });
        } else {
          super.toAssignableObjectExpressionProp(prop, ...args);
        }
      }
  
      finishCallExpression<T: N.CallExpression | N.OptionalCallExpression>(
        node: T,
        optional: boolean,
      ): N.Expression {
        super.finishCallExpression(node, optional);
  
        if (node.callee.type === "Import") {
          ((node: N.Node): N.EstreeImportExpression).type = "ImportExpression";
          ((node: N.Node): N.EstreeImportExpression).source = node.arguments[0];
          if (this.hasPlugin("importAssertions")) {
            ((node: N.Node): N.EstreeImportExpression).attributes =
              node.arguments[1] ?? null;
          }
          // $FlowIgnore - arguments isn't optional in the type definition
          delete node.arguments;
          // $FlowIgnore - callee isn't optional in the type definition
          delete node.callee;
        }
  
        return node;
      }
  
      toReferencedArguments(
        node:
          | N.CallExpression
          | N.OptionalCallExpression
          | N.EstreeImportExpression,
        /* isParenthesizedExpr?: boolean, */
      ) {
        // ImportExpressions do not have an arguments array.
        if (node.type === "ImportExpression") {
          return;
        }
  
        super.toReferencedArguments(node);
      }
  
      parseExport(node: N.Node) {
        super.parseExport(node);
  
        switch (node.type) {
          case "ExportAllDeclaration":
            node.exported = null;
            break;
  
          case "ExportNamedDeclaration":
            if (
              node.specifiers.length === 1 &&
              node.specifiers[0].type === "ExportNamespaceSpecifier"
            ) {
              node.type = "ExportAllDeclaration";
              node.exported = node.specifiers[0].exported;
              delete node.specifiers;
            }
  
            break;
        }
  
        return node;
      }
  
      parseSubscript(
        base: N.Expression,
        startPos: number,
        startLoc: Position,
        noCalls: ?boolean,
        state: N.ParseSubscriptState,
      ) {
        const node = super.parseSubscript(
          base,
          startPos,
          startLoc,
          noCalls,
          state,
        );
  
        if (state.optionalChainMember) {
          // https://github.com/estree/estree/blob/master/es2020.md#chainexpression
          if (
            node.type === "OptionalMemberExpression" ||
            node.type === "OptionalCallExpression"
          ) {
            node.type = node.type.substring(8); // strip Optional prefix
          }
          if (state.stop) {
            const chain = this.startNodeAtNode(node);
            chain.expression = node;
            return this.finishNode(chain, "ChainExpression");
          }
        } else if (
          node.type === "MemberExpression" ||
          node.type === "CallExpression"
        ) {
          node.optional = false;
        }
  
        return node;
      }
  
      hasPropertyAsPrivateName(node: N.Node): boolean {
        if (node.type === "ChainExpression") {
          node = node.expression;
        }
        return super.hasPropertyAsPrivateName(node);
      }
  
      isOptionalChain(node: N.Node): boolean {
        return node.type === "ChainExpression";
      }
  
      isObjectProperty(node: N.Node): boolean {
        return node.type === "Property" && node.kind === "init" && !node.method;
      }
  
      isObjectMethod(node: N.Node): boolean {
        return node.method || node.kind === "get" || node.kind === "set";
      }
  
      finishNodeAt<T: NodeType>(node: T, type: string, endLoc: Position): T {
        return toESTreeLocation(super.finishNodeAt(node, type, endLoc));
      }
  
      resetEndLocation(
        node: NodeBase,
        endLoc?: Position = this.state.lastTokEndLoc,
      ): void {
        super.resetEndLocation(node, endLoc);
        toESTreeLocation(node);
      }
    };
  