import {
  Accessibility,
  ArrayExpression,
  ArrayPattern,
  ArrowFunctionExpression,
  Argument,
  AssignmentExpression,
  AssignmentPattern,
  AssignmentPatternProperty,
  AssignmentProperty,
  AwaitExpression,
  BinaryExpression,
  BlockStatement,
  BooleanLiteral,
  BreakStatement,
  CallExpression,
  CatchClause,
  Class,
  ClassDeclaration,
  ClassExpression,
  ClassMember,
  ClassMethod,
  ClassProperty,
  ComputedPropName,
  ConditionalExpression,
  Constructor,
  ContinueStatement,
  DebuggerStatement,
  Declaration,
  Decorator,
  DefaultDecl,
  DoWhileStatement,
  EmptyStatement,
  ExportAllDeclaration,
  ExportDeclaration,
  ExportDefaultDeclaration,
  ExportDefaultExpression,
  ExportDefaultSpecifier,
  ExportNamedDeclaration,
  ExportNamespaceSpecifer,
  ExportSpecifier,
  Expression,
  ExpressionStatement,
  Fn,
  ForInStatement,
  ForOfStatement,
  ForStatement,
  FunctionDeclaration,
  FunctionExpression,
  GetterProperty,
  Identifier,
  IfStatement,
  ImportDeclaration,
  ImportDefaultSpecifier,
  ImportNamespaceSpecifier,
  ImportSpecifier,
  JSXAttribute,
  JSXAttributeName,
  JSXAttributeOrSpread,
  JSXAttrValue,
  JSXClosingElement,
  JSXClosingFragment,
  JSXElement,
  JSXElementChild,
  JSXElementName,
  JSXEmptyExpression,
  JSXExpressionContainer,
  JSXFragment,
  JSXMemberExpression,
  JSXNamespacedName,
  JSXObject,
  JSXOpeningElement,
  JSXOpeningFragment,
  JSXSpreadChild,
  JSXText,
  KeyValuePatternProperty,
  KeyValueProperty,
  LabeledStatement,
  MemberExpression,
  MetaProperty,
  MethodProperty,
  Module,
  ModuleDeclaration,
  ModuleItem,
  NamedExportSpecifier,
  NamedImportSpecifier,
  NewExpression,
  NullLiteral,
  NumericLiteral,
  ObjectExpression,
  ObjectPattern,
  ObjectPatternProperty,
  OptionalChainingExpression,
  ParenthesisExpression,
  Pattern,
  PrivateMethod,
  PrivateName,
  PrivateProperty,
  Program,
  Property,
  PropertyName,
  RegExpLiteral,
  RestElement,
  ReturnStatement,
  Script,
  SequenceExpression,
  SetterProperty,
  SpreadElement,
  Statement,
  StringLiteral,
  Super,
  SwitchCase,
  SwitchStatement,
  TaggedTemplateExpression,
  TemplateLiteral,
  ThisExpression,
  ThrowStatement,
  TryStatement,
  TsAsExpression,
  TsEntityName,
  TsEnumDeclaration,
  TsEnumMember,
  TsEnumMemberId,
  TsExportAssignment,
  TsExpressionWithTypeArguments,
  TsExternalModuleReference,
  TsFnParameter,
  TsImportEqualsDeclaration,
  TsIndexSignature,
  TsInterfaceBody,
  TsInterfaceDeclaration,
  TsModuleBlock,
  TsModuleDeclaration,
  TsModuleName,
  TsModuleReference,
  TsNamespaceBody,
  TsNamespaceDeclaration,
  TsNamespaceExportDeclaration,
  TsNonNullExpression,
  TsParameterProperty,
  TsParameterPropertyParameter,
  TsQualifiedName,
  TsType,
  TsTypeAliasDeclaration,
  TsTypeAnnotation,
  TsTypeAssertion,
  TsTypeCastExpression,
  TsTypeElement,
  TsTypeParameter,
  TsTypeParameterDeclaration,
  TsTypeParameterInstantiation,
  UnaryExpression,
  UpdateExpression,
  VariableDeclaration,
  VariableDeclarator,
  WhileStatement,
  WithStatement,
  YieldExpression,
  Param,
} from "./types";

export default class Visitor {
  visitProgram(n: Program): Program {
    switch (n.type) {
      case "Module":
        return this.visitModule(n);
      case "Script":
        return this.visitScript(n);
    }
  }

  visitModule(m: Module): Module {
    m.body = this.visitModuleItems(m.body);
    return m;
  }

  visitScript(m: Script): Script {
    m.body = this.visitStatements(m.body);
    return m;
  }

  visitModuleItems(items: ModuleItem[]): ModuleItem[] {
    return items.map(this.visitModuleItem.bind(this));
  }

  visitModuleItem(n: ModuleItem): ModuleItem {
    switch (n.type) {
      case "ExportDeclaration":
      case "ExportDefaultDeclaration":
      case "ExportNamedDeclaration":
      case "ExportDefaultExpression":
      case "ImportDeclaration":
      case "ExportAllDeclaration":
      case "TsImportEqualsDeclaration":
      case "TsExportAssignment":
      case "TsNamespaceExportDeclaration":
        return this.visitModuleDeclaration(n);
      default:
        return this.visitStatement(n);
    }
  }

  visitModuleDeclaration(n: ModuleDeclaration): ModuleDeclaration {
    switch (n.type) {
      case "ExportDeclaration":
        return this.visitExportDeclaration(n);
      case "ExportDefaultDeclaration":
        return this.visitExportDefaultDeclaration(n);
      case "ExportNamedDeclaration":
        return this.visitExportNamedDeclration(n);
      case "ExportDefaultExpression":
        return this.visitExportDefaultExpression(n);
      case "ImportDeclaration":
        return this.visitImportDeclaration(n);
      case "ExportAllDeclaration":
        return this.visitExportAllDeclration(n);
      case "TsImportEqualsDeclaration":
        return this.visitTsImportEqualsDeclaration(n);
      case "TsExportAssignment":
        return this.visitTsExportAssignment(n);
      case "TsNamespaceExportDeclaration":
        return this.visitTsNamespaceExportDeclaration(n);
    }
  }

  visitTsNamespaceExportDeclaration(
    n: TsNamespaceExportDeclaration
  ): ModuleDeclaration {
    n.id = this.visitBindingIdentifier(n.id);
    return n;
  }

  visitTsExportAssignment(n: TsExportAssignment): TsExportAssignment {
    n.expression = this.visitExpression(n.expression);

    return n;
  }

  visitTsImportEqualsDeclaration(
    n: TsImportEqualsDeclaration
  ): ModuleDeclaration {
    n.id = this.visitBindingIdentifier(n.id);
    n.moduleRef = this.visitTsModuleReference(n.moduleRef);
    return n;
  }

  visitTsModuleReference(n: TsModuleReference): TsModuleReference {
    switch (n.type) {
      case "Identifier":
        return this.visitIdentifierReference(n);
      case "TsExternalModuleReference":
        return this.visitTsExternalModuleReference(n);
      case "TsQualifiedName":
        return this.visitTsQualifiedName(n);
    }
  }

  visitTsExternalModuleReference(
    n: TsExternalModuleReference
  ): TsExternalModuleReference {
    n.expression = this.visitExpression(n.expression);
    return n;
  }

  visitExportAllDeclration(n: ExportAllDeclaration): ModuleDeclaration {
    n.source = this.visitStringLiteral(n.source);
    return n;
  }

  visitExportDefaultExpression(n: ExportDefaultExpression): ModuleDeclaration {
    n.expression = this.visitExpression(n.expression);
    return n;
  }

  visitExportNamedDeclration(n: ExportNamedDeclaration): ModuleDeclaration {
    n.specifiers = this.visitExportSpecifiers(n.specifiers);
    n.source = this.visitOptionalStringLiteral(n.source);
    return n;
  }

  visitExportSpecifiers(nodes: ExportSpecifier[]): ExportSpecifier[] {
    return nodes.map(this.visitExportSpecifier.bind(this));
  }

  visitExportSpecifier(n: ExportSpecifier): ExportSpecifier {
    switch (n.type) {
      case "ExportDefaultSpecifier":
        return this.visitExportDefaultSpecifier(n);
      case "ExportNamespaceSpecifer":
        return this.visitExportNamespaceSpecifier(n);
      case "ExportSpecifier":
        return this.visitNamedExportSpecifier(n);
    }
  }
  visitNamedExportSpecifier(n: NamedExportSpecifier): ExportSpecifier {
    if (n.exported) {
      n.exported = this.visitBindingIdentifier(n.exported);
    }
    n.orig = this.visitIdentifierReference(n.orig);
    return n;
  }

  visitExportNamespaceSpecifier(n: ExportNamespaceSpecifer): ExportSpecifier {
    n.name = this.visitBindingIdentifier(n.name);
    return n;
  }

  visitExportDefaultSpecifier(n: ExportDefaultSpecifier): ExportSpecifier {
    n.exported = this.visitBindingIdentifier(n.exported);
    return n;
  }

  visitOptionalStringLiteral(
    n: StringLiteral | undefined
  ): StringLiteral | undefined {
    if (n) {
      return this.visitStringLiteral(n);
    }
  }

  visitExportDefaultDeclaration(
    n: ExportDefaultDeclaration
  ): ModuleDeclaration {
    n.decl = this.visitDefaultDeclaration(n.decl);
    return n;
  }

  visitDefaultDeclaration(n: DefaultDecl): DefaultDecl {
    switch (n.type) {
      case "ClassExpression":
        return this.visitClassExpression(n);
      case "FunctionExpression":
        return this.visitFunctionExpression(n);
      case "TsInterfaceDeclaration":
        return this.visitTsInterfaceDeclaration(n);
    }
  }

  visitFunctionExpression(n: FunctionExpression): FunctionExpression {
    n = this.visitFunction(n);
    if (n.identifier) {
      n.identifier = this.visitBindingIdentifier(n.identifier);
    }
    return n;
  }

  visitClassExpression(n: ClassExpression): ClassExpression {
    n = this.visitClass(n);
    if (n.identifier) {
      n.identifier = this.visitBindingIdentifier(n.identifier);
    }
    return n;
  }

  visitExportDeclaration(n: ExportDeclaration): ModuleDeclaration {
    n.declaration = this.visitDeclaration(n.declaration);
    return n;
  }

  visitArrayExpression(e: ArrayExpression): Expression {
    if (e.elements) {
      e.elements = e.elements.map(this.visitArrayElement.bind(this));
    }
    return e;
  }

  visitArrayElement(
    e: Expression | SpreadElement | undefined
  ): Expression | SpreadElement | undefined {
    if (e && e.type === "SpreadElement") {
      return this.visitSpreadElement(e);
    }
    return this.visitOptionalExpression(e as Expression | undefined);
  }

  visitSpreadElement(e: SpreadElement): SpreadElement {
    e.arguments = this.visitExpression(e.arguments);
    return e;
  }

  visitOptionalExpression(e: Expression | undefined): Expression | undefined {
    if (e) {
      return this.visitExpression(e);
    }
  }

  visitArrowFunctionExpression(e: ArrowFunctionExpression): Expression {
    e.body = this.visitArrowBody(e.body);
    e.params = this.visitPatterns(e.params);
    e.returnType = this.visitTsTypeAnnotation(e.returnType);
    e.typeParameters = this.visitTsTypeParameterDeclaration(e.typeParameters);

    return e;
  }

  visitArrowBody(
    body: BlockStatement | Expression
  ): BlockStatement | Expression {
    switch (body.type) {
      case "BlockStatement":
        return this.visitBlockStatement(body);
      default:
        return this.visitExpression(body);
    }
  }

  visitBlockStatement(block: BlockStatement): BlockStatement {
    block.stmts = this.visitStatements(block.stmts);

    return block;
  }

  visitStatements(stmts: Statement[]): Statement[] {
    return stmts.map(this.visitStatement.bind(this));
  }

  visitStatement(stmt: Statement): Statement {
    switch (stmt.type) {
      case "ClassDeclaration":
      case "FunctionDeclaration":
      case "TsEnumDeclaration":
      case "TsInterfaceDeclaration":
      case "TsModuleDeclaration":
      case "TsTypeAliasDeclaration":
      case "VariableDeclaration":
        return this.visitDeclaration(stmt);

      case "BreakStatement":
        return this.visitBreakStatement(stmt);
      case "BlockStatement":
        return this.visitBlockStatement(stmt);
      case "ContinueStatement":
        return this.visitContinueStatement(stmt);
      case "DebuggerStatement":
        return this.visitDebuggerStatement(stmt);
      case "DoWhileStatement":
        return this.visitDoWhileStatement(stmt);
      case "EmptyStatement":
        return this.visitEmptyStatement(stmt);
      case "ForInStatement":
        return this.visitForInStatement(stmt);
      case "ForOfStatement":
        return this.visitForOfStatement(stmt);
      case "ForStatement":
        return this.visitForStatement(stmt);
      case "IfStatement":
        return this.visitIfStatement(stmt);
      case "LabeledStatement":
        return this.visitLabeledStatement(stmt);
      case "ReturnStatement":
        return this.visitReturnStatement(stmt);
      case "SwitchStatement":
        return this.visitSwitchStatement(stmt);
      case "ThrowStatement":
        return this.visitThrowStatement(stmt);
      case "TryStatement":
        return this.visitTryStatement(stmt);
      case "WhileStatement":
        return this.visitWhileStatement(stmt);
      case "WithStatement":
        return this.visitWithStatement(stmt);
      case "ExpressionStatement":
        return this.visitExpressionStatement(stmt);

      default:
        throw new Error(`Unknown statement type: ` + (stmt as any).type);
    }
  }

  visitSwitchStatement(stmt: SwitchStatement): Statement {
    stmt.discriminant = this.visitExpression(stmt.discriminant);
    stmt.cases = this.visitSwitchCases(stmt.cases);
    return stmt;
  }

  visitSwitchCases(cases: SwitchCase[]): SwitchCase[] {
    return cases.map(this.visitSwitchCase.bind(this));
  }

  visitSwitchCase(c: SwitchCase): SwitchCase {
    c.test = this.visitOptionalExpression(c.test);
    c.consequent = this.visitStatements(c.consequent);

    return c;
  }

  visitIfStatement(stmt: IfStatement): Statement {
    stmt.test = this.visitExpression(stmt.test);
    stmt.consequent = this.visitStatement(stmt.consequent);
    stmt.alternate = this.visitOptionalStatement(stmt.alternate);

    return stmt;
  }

  visitOptionalStatement(stmt: Statement | undefined): Statement | undefined {
    if (stmt) {
      return this.visitStatement(stmt);
    }
  }

  visitBreakStatement(stmt: BreakStatement): Statement {
    if (stmt.label) {
      stmt.label = this.visitLabelIdentifier(stmt.label);
    }

    return stmt;
  }

  visitWhileStatement(stmt: WhileStatement): Statement {
    stmt.test = this.visitExpression(stmt.test);
    stmt.body = this.visitStatement(stmt.body);
    return stmt;
  }

  visitTryStatement(stmt: TryStatement): Statement {
    stmt.block = this.visitBlockStatement(stmt.block);
    stmt.handler = this.visitCatchClause(stmt.handler);
    if (stmt.finalizer) {
      stmt.finalizer = this.visitBlockStatement(stmt.finalizer);
    }
    return stmt;
  }

  visitCatchClause(handler: CatchClause | undefined): CatchClause | undefined {
    if (handler) {
      if (handler.param) {
        handler.param = this.visitPattern(handler.param);
      }

      handler.body = this.visitBlockStatement(handler.body);
    }

    return handler;
  }

  visitThrowStatement(stmt: ThrowStatement): Statement {
    stmt.argument = this.visitExpression(stmt.argument);
    return stmt;
  }

  visitReturnStatement(stmt: ReturnStatement): Statement {
    if (stmt.argument) {
      stmt.argument = this.visitExpression(stmt.argument);
    }
    return stmt;
  }

  visitLabeledStatement(stmt: LabeledStatement): Statement {
    stmt.label = this.visitLabelIdentifier(stmt.label);
    stmt.body = this.visitStatement(stmt.body);

    return stmt;
  }

  visitForStatement(stmt: ForStatement): Statement {
    if (stmt.init) {
      if (stmt.init.type === "VariableDeclaration") {
        stmt.init = this.visitVariableDeclaration(stmt.init);
      } else {
        stmt.init = this.visitOptionalExpression(stmt.init);
      }
    }

    stmt.test = this.visitOptionalExpression(stmt.test);
    stmt.update = this.visitOptionalExpression(stmt.update);
    stmt.body = this.visitStatement(stmt.body);

    return stmt;
  }

  visitForOfStatement(stmt: ForOfStatement): Statement {
    if (stmt.left.type === "VariableDeclaration") {
      stmt.left = this.visitVariableDeclaration(stmt.left);
    } else {
      stmt.left = this.visitPattern(stmt.left);
    }
    stmt.right = this.visitExpression(stmt.right);
    stmt.body = this.visitStatement(stmt.body);
    return stmt;
  }

  visitForInStatement(stmt: ForInStatement): Statement {
    if (stmt.left.type === "VariableDeclaration") {
      stmt.left = this.visitVariableDeclaration(stmt.left);
    } else {
      stmt.left = this.visitPattern(stmt.left);
    }
    stmt.right = this.visitExpression(stmt.right);
    stmt.body = this.visitStatement(stmt.body);
    return stmt;
  }

  visitEmptyStatement(stmt: EmptyStatement): Statement {
    return stmt;
  }

  visitDoWhileStatement(stmt: DoWhileStatement): Statement {
    stmt.body = this.visitStatement(stmt.body);
    stmt.test = this.visitExpression(stmt.test);
    return stmt;
  }

  visitDebuggerStatement(stmt: DebuggerStatement): Statement {
    return stmt;
  }

  visitWithStatement(stmt: WithStatement): Statement {
    stmt.object = this.visitExpression(stmt.object);
    stmt.body = this.visitStatement(stmt.body);
    return stmt;
  }

  visitDeclaration(decl: Declaration): Declaration {
    switch (decl.type) {
      case "ClassDeclaration":
        return this.visitClassDeclartion(decl);
      case "FunctionDeclaration":
        return this.visitFunctionDeclaration(decl);
      case "TsEnumDeclaration":
        return this.visitTsEnumDeclaration(decl);
      case "TsInterfaceDeclaration":
        return this.visitTsInterfaceDeclaration(decl);
      case "TsModuleDeclaration":
        return this.visitTsModuleDeclaration(decl);
      case "TsTypeAliasDeclaration":
        return this.visitTsTypeAliasDeclaration(decl);
      case "VariableDeclaration":
        return this.visitVariableDeclaration(decl);
    }
  }

  visitVariableDeclaration(n: VariableDeclaration): VariableDeclaration {
    n.declarations = this.visitVariableDeclarators(n.declarations);
    return n;
  }

  visitVariableDeclarators(nodes: VariableDeclarator[]): VariableDeclarator[] {
    return nodes.map(this.visitVariableDeclarator.bind(this));
  }

  visitVariableDeclarator(n: VariableDeclarator): VariableDeclarator {
    n.id = this.visitPattern(n.id);
    n.init = this.visitOptionalExpression(n.init);
    return n;
  }

  visitTsTypeAliasDeclaration(n: TsTypeAliasDeclaration): Declaration {
    n.id = this.visitBindingIdentifier(n.id);
    n.typeAnnotation = this.visitTsType(n.typeAnnotation);
    n.typeParams = this.visitTsTypeParameterDeclaration(n.typeParams);
    return n;
  }

  visitTsModuleDeclaration(n: TsModuleDeclaration): Declaration {
    n.id = this.visitTsModuleName(n.id);
    if (n.body) {
      n.body = this.visitTsNamespaceBody(n.body);
    }
    return n;
  }

  visitTsModuleName(n: TsModuleName): TsModuleName {
    switch (n.type) {
      case "Identifier":
        return this.visitBindingIdentifier(n);
      case "StringLiteral":
        return this.visitStringLiteral(n);
    }
  }

  visitTsNamespaceBody(n: TsNamespaceBody): TsNamespaceBody | undefined {
    if (n) {
      switch (n.type) {
        case "TsModuleBlock":
          return this.visitTsModuleBlock(n);
        case "TsNamespaceDeclaration":
          return this.visitTsNamespaceDeclaration(n);
      }
    }
  }

  visitTsNamespaceDeclaration(
    n: TsNamespaceDeclaration
  ): TsModuleBlock | TsNamespaceDeclaration {
    const body = this.visitTsNamespaceBody(n.body);
    if (body) {
      n.body = body;
    }
    n.id = this.visitBindingIdentifier(n.id);
    return n;
  }

  visitTsModuleBlock(n: TsModuleBlock): TsModuleBlock | TsNamespaceDeclaration {
    n.body = this.visitModuleItems(n.body);
    return n;
  }

  visitTsInterfaceDeclaration(
    n: TsInterfaceDeclaration
  ): TsInterfaceDeclaration {
    n.id = this.visitBindingIdentifier(n.id);
    n.typeParams = this.visitTsTypeParameterDeclaration(n.typeParams);
    n.extends = this.visitTsExpressionsWithTypeArguments(n.extends);
    n.body = this.visitTsInterfaceBody(n.body);
    return n;
  }

  visitTsInterfaceBody(n: TsInterfaceBody): TsInterfaceBody {
    n.body = this.visitTsTypeElements(n.body);
    return n;
  }

  visitTsTypeElements(nodes: TsTypeElement[]): TsTypeElement[] {
    return nodes.map(this.visitTsTypeElement.bind(this));
  }

  visitTsTypeElement(n: TsTypeElement): TsTypeElement {
    n.params = this.visitTsFnParameters(n.params);
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation);
    return n;
  }

  visitTsEnumDeclaration(n: TsEnumDeclaration): Declaration {
    n.id = this.visitIdentifier(n.id);
    n.member = this.visitTsEnumMembers(n.member);
    return n;
  }

  visitTsEnumMembers(nodes: TsEnumMember[]): TsEnumMember[] {
    return nodes.map(this.visitTsEnumMember.bind(this));
  }

  visitTsEnumMember(n: TsEnumMember): TsEnumMember {
    n.id = this.visitTsEnumMemberId(n.id);
    n.init = this.visitOptionalExpression(n.init);
    return n;
  }

  visitTsEnumMemberId(n: TsEnumMemberId): TsEnumMemberId {
    switch (n.type) {
      case "Identifier":
        return this.visitBindingIdentifier(n);
      case "StringLiteral":
        return this.visitStringLiteral(n);
    }
  }

  visitFunctionDeclaration(decl: FunctionDeclaration): Declaration {
    decl.ident = this.visitIdentifier(decl.ident);
    decl = this.visitFunction(decl);

    return decl;
  }

  visitClassDeclartion(decl: ClassDeclaration): Declaration {
    decl = this.visitClass(decl);
    decl.identifier = this.visitIdentifier(decl.identifier);
    return decl;
  }

  visitClassBody(members: ClassMember[]): ClassMember[] {
    return members.map(this.visitClassMember.bind(this));
  }

  visitClassMember(member: ClassMember): ClassMember {
    switch (member.type) {
      case "ClassMethod":
        return this.visitClassMethod(member);
      case "ClassProperty":
        return this.visitClassProperty(member);
      case "Constructor":
        return this.visitConstructor(member);
      case "PrivateMethod":
        return this.visitPrivateMethod(member);
      case "PrivateProperty":
        return this.visitPrivateProperty(member);
      case "TsIndexSignature":
        return this.visitTsIndexSignature(member);
    }
  }

  visitTsIndexSignature(n: TsIndexSignature): ClassMember {
    n.params = this.visitTsFnParameters(n.params);
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation);
    return n;
  }

  visitTsFnParameters(params: TsFnParameter[]): TsFnParameter[] {
    return params.map(this.visitTsFnParameter.bind(this));
  }

  visitTsFnParameter(n: TsFnParameter): TsFnParameter {
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation);
    return n;
  }

  visitPrivateProperty(n: PrivateProperty): ClassMember {
    n.decorators = this.visitDecorators(n.decorators);
    n.key = this.visitPrivateName(n.key);
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation);
    n.value = this.visitOptionalExpression(n.value);
    return n;
  }

  visitPrivateMethod(n: PrivateMethod): ClassMember {
    n.accessibility = this.visitAccessibility(n.accessibility);
    n.function = this.visitFunction(n.function);
    n.key = this.visitPrivateName(n.key);
    return n;
  }

  visitPrivateName(n: PrivateName): PrivateName {
    return n;
  }

  visitConstructor(n: Constructor): ClassMember {
    n.accessibility = this.visitAccessibility(n.accessibility);
    n.key = this.visitPropertyName(n.key);
    n.params = this.visitConstructorParameters(n.params);
    if (n.body) {
      n.body = this.visitBlockStatement(n.body);
    }
    return n;
  }

  visitConstructorParameters(
    nodes: (Param | TsParameterProperty)[]
  ): (Param | TsParameterProperty)[] {
    return nodes.map(this.visitConstructorParameter.bind(this));
  }

  visitConstructorParameter(
    n: Param | TsParameterProperty
  ): Param | TsParameterProperty {
    switch (n.type) {
      case "TsParameterProperty":
        return this.visitTsParameterProperty(n);
      default:
        return this.visitParameter(n);
    }
  }

  visitTsParameterProperty(
    n: TsParameterProperty
  ): TsParameterProperty | Param {
    n.accessibility = this.visitAccessibility(n.accessibility);
    n.decorators = this.visitDecorators(n.decorators);
    n.param = this.visitTsParameterPropertyParameter(n.param);
    return n;
  }

  visitTsParameterPropertyParameter(
    n: TsParameterPropertyParameter
  ): TsParameterPropertyParameter {
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation);
    return n;
  }

  visitPropertyName(key: PropertyName): PropertyName {
    switch (key.type) {
      case "Identifier":
        return this.visitBindingIdentifier(key);
      case "StringLiteral":
        return this.visitStringLiteral(key);
      case "NumericLiteral":
        return this.visitNumericLiteral(key);
      default:
        return this.visitComputedPropertyKey(key);
    }
  }

  visitAccessibility(n: Accessibility | undefined): Accessibility | undefined {
    return n;
  }

  visitClassProperty(n: ClassProperty): ClassMember {
    n.accessibility = this.visitAccessibility(n.accessibility);
    n.decorators = this.visitDecorators(n.decorators);
    n.key = this.visitExpression(n.key);
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation);
    n.value = this.visitOptionalExpression(n.value);
    return n;
  }

  visitClassMethod(n: ClassMethod): ClassMember {
    n.accessibility = this.visitAccessibility(n.accessibility);
    n.function = this.visitFunction(n.function);
    n.key = this.visitPropertyName(n.key);
    return n;
  }

  visitPropertName(n: PropertyName): PropertyName {
    switch (n.type) {
      case "Identifier":
        return this.visitIdentifier(n);
      case "NumericLiteral":
        return this.visitNumericLiteral(n);
      case "StringLiteral":
        return this.visitStringLiteral(n);
      case "Computed":
        return this.visitComputedPropertyKey(n);
    }
  }
  visitComputedPropertyKey(n: ComputedPropName): ComputedPropName {
    n.expression = this.visitExpression(n.expression);
    return n;
  }

  visitClass<T extends Class>(n: T): T {
    n.decorators = this.visitDecorators(n.decorators);

    n.superClass = this.visitOptionalExpression(n.superClass);

    n.superTypeParams = this.visitTsTypeParameterInstantiation(
      n.superTypeParams
    );
    if (n.implements) {
      n.implements = this.visitTsExpressionsWithTypeArguments(n.implements);
    }

    n.body = this.visitClassBody(n.body);
    return n;
  }

  visitFunction<T extends Fn>(n: T): T {
    n.decorators = this.visitDecorators(n.decorators);
    n.params = this.visitParameters(n.params);
    if (n.body) {
      n.body = this.visitBlockStatement(n.body);
    }
    n.returnType = this.visitTsTypeAnnotation(n.returnType);
    n.typeParameters = this.visitTsTypeParameterDeclaration(n.typeParameters);
    return n;
  }

  visitTsExpressionsWithTypeArguments(
    nodes: TsExpressionWithTypeArguments[]
  ): TsExpressionWithTypeArguments[] {
    return nodes.map(this.visitTsExpressionWithTypeArguments.bind(this));
  }

  visitTsExpressionWithTypeArguments(
    n: TsExpressionWithTypeArguments
  ): TsExpressionWithTypeArguments {
    n.expression = this.visitTsEntityName(n.expression);
    n.typeArguments = this.visitTsTypeParameterInstantiation(n.typeArguments);
    return n;
  }

  visitTsTypeParameterInstantiation(
    n: TsTypeParameterInstantiation | undefined
  ): TsTypeParameterInstantiation | undefined {
    if (n) {
      n.params = this.visitTsTypes(n.params);
    }
    return n;
  }

  visitTsTypes(nodes: TsType[]): TsType[] {
    return nodes.map(this.visitTsType.bind(this));
  }

  visitTsEntityName(n: TsEntityName): TsEntityName {
    switch (n.type) {
      case "Identifier":
        return this.visitBindingIdentifier(n);
      case "TsQualifiedName":
        return this.visitTsQualifiedName(n);
    }
  }

  visitTsQualifiedName(n: TsQualifiedName): TsQualifiedName {
    n.left = this.visitTsEntityName(n.left);
    n.right = this.visitIdentifier(n.right);
    return n;
  }

  visitDecorators(nodes: Decorator[] | undefined): Decorator[] | undefined {
    if (nodes) {
      return nodes.map(this.visitDecorator.bind(this));
    }
  }

  visitDecorator(n: Decorator): Decorator {
    n.expression = this.visitExpression(n.expression);

    return n;
  }

  visitExpressionStatement(stmt: ExpressionStatement): Statement {
    stmt.expression = this.visitExpression(stmt.expression);
    return stmt;
  }

  visitContinueStatement(stmt: ContinueStatement): Statement {
    if (stmt.label) {
      stmt.label = this.visitLabelIdentifier(stmt.label);
    }
    return stmt;
  }

  visitExpression(n: Expression): Expression {
    switch (n.type) {
      case "ArrayExpression":
        return this.visitArrayExpression(n);
      case "ArrowFunctionExpression":
        return this.visitArrowFunctionExpression(n);
      case "AssignmentExpression":
        return this.visitAssignmentExpression(n);
      case "AwaitExpression":
        return this.visitAwaitExpression(n);
      case "BinaryExpression":
        return this.visitBinaryExpression(n);
      case "BooleanLiteral":
        return this.visitBooleanLiteral(n);
      case "CallExpression":
        return this.visitCallExpression(n);
      case "ClassExpression":
        return this.visitClassExpression(n);
      case "ConditionalExpression":
        return this.visitConditionalExpression(n);
      case "FunctionExpression":
        return this.visitFunctionExpression(n);
      case "Identifier":
        return this.visitIdentifierReference(n);
      case "JSXElement":
        return this.visitJSXElement(n);
      case "JSXEmptyExpression":
        return this.visitJSXEmptyExpression(n);
      case "JSXFragment":
        return this.visitJSXFragment(n);
      case "JSXMemberExpression":
        return this.visitJSXMemberExpression(n);
      case "JSXNamespacedName":
        return this.visitJSXNamespacedName(n);
      case "JSXText":
        return this.visitJSXText(n);
      case "MemberExpression":
        return this.visitMemberExpression(n);
      case "MetaProperty":
        return this.visitMetaProperty(n);
      case "NewExpression":
        return this.visitNewExpression(n);
      case "NullLiteral":
        return this.visitNullLiteral(n);
      case "NumericLiteral":
        return this.visitNumericLiteral(n);
      case "ObjectExpression":
        return this.visitObjectExpression(n);
      case "ParenthesisExpression":
        return this.visitParenthesisExpression(n);
      case "PrivateName":
        return this.visitPrivateName(n);
      case "RegExpLiteral":
        return this.visitRegExpLiteral(n);
      case "SequenceExpression":
        return this.visitSequenceExpression(n);
      case "StringLiteral":
        return this.visitStringLiteral(n);
      case "TaggedTemplateExpression":
        return this.visitTaggedTemplateExpression(n);
      case "TemplateLiteral":
        return this.visitTemplateLiteral(n);
      case "ThisExpression":
        return this.visitThisExpression(n);
      case "TsAsExpression":
        return this.visitTsAsExpression(n);
      case "TsNonNullExpression":
        return this.visitTsNonNullExpression(n);
      case "TsTypeAssertion":
        return this.visitTsTypeAssertion(n);
      case "TsTypeCastExpression":
        return this.visitTsTypeCastExpression(n);
      case "UnaryExpression":
        return this.visitUnaryExpression(n);
      case "UpdateExpression":
        return this.visitUpdateExpression(n);
      case "YieldExpression":
        return this.visitYieldExpression(n);
      case "OptionalChainingExpression":
        return this.visitOptionalChainingExpression(n);
      case "Invalid":
        return n;
    }
  }
  visitOptionalChainingExpression(n: OptionalChainingExpression): Expression {
    n.expr = this.visitExpression(n.expr);
    return n;
  }

  visitAssignmentExpression(n: AssignmentExpression): Expression {
    n.left = this.visitPatternOrExpressison(n.left);
    n.right = this.visitExpression(n.right);
    return n;
  }

  visitPatternOrExpressison(n: Pattern | Expression): Pattern | Expression {
    switch (n.type) {
      case "ObjectPattern":
      case "ArrayPattern":
      case "Identifier":
      case "AssignmentPattern":
      case "RestElement":
        return this.visitPattern(n);
      default:
        return this.visitExpression(n);
    }
  }

  visitYieldExpression(n: YieldExpression): Expression {
    n.argument = this.visitOptionalExpression(n.argument);
    return n;
  }

  visitUpdateExpression(n: UpdateExpression): Expression {
    n.argument = this.visitExpression(n.argument);
    return n;
  }

  visitUnaryExpression(n: UnaryExpression): Expression {
    n.argument = this.visitExpression(n.argument);
    return n;
  }

  visitTsTypeCastExpression(n: TsTypeCastExpression): Expression {
    n.expression = this.visitExpression(n.expression);
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation)!;
    return n;
  }

  visitTsTypeAssertion(n: TsTypeAssertion): Expression {
    n.expression = this.visitExpression(n.expression);
    n.typeAnnotation = this.visitTsType(n.typeAnnotation);
    return n;
  }

  visitTsNonNullExpression(n: TsNonNullExpression): Expression {
    n.expression = this.visitExpression(n.expression);
    return n;
  }

  visitTsAsExpression(n: TsAsExpression): Expression {
    n.expression = this.visitExpression(n.expression);
    n.typeAnnotation = this.visitTsType(n.typeAnnotation);
    return n;
  }

  visitThisExpression(n: ThisExpression): Expression {
    return n;
  }

  visitTemplateLiteral(n: TemplateLiteral): Expression {
    n.expressions = n.expressions.map(this.visitExpression.bind(this));
    return n;
  }

  visitParameters(n: Param[]): Param[] {
    return n.map(this.visitParameter.bind(this))
  }

  visitParameter(n: Param): Param {
    n.pat = this.visitPattern(n.pat);
    return n;
  }

  visitTaggedTemplateExpression(n: TaggedTemplateExpression): Expression {
    n.tag = this.visitExpression(n.tag);
    n.expressions = n.expressions.map(this.visitExpression.bind(this));
    return n;
  }

  visitSequenceExpression(n: SequenceExpression): Expression {
    n.expressions = n.expressions.map(this.visitExpression.bind(this));
    return n;
  }

  visitRegExpLiteral(n: RegExpLiteral): Expression {
    return n;
  }

  visitParenthesisExpression(n: ParenthesisExpression): Expression {
    n.expression = this.visitExpression(n.expression);
    return n;
  }

  visitObjectExpression(n: ObjectExpression): Expression {
    if (n.properties) {
      n.properties = this.visitObjectProperties(n.properties);
    }
    return n;
  }

  visitObjectProperties(
    nodes: (Property | SpreadElement)[]
  ): (Property | SpreadElement)[] {
    return nodes.map(this.visitObjectProperty.bind(this));
  }

  visitObjectProperty(n: Property | SpreadElement): Property | SpreadElement {
    switch (n.type) {
      case "SpreadElement":
        return this.visitSpreadElement(n);
      default:
        return this.visitProperty(n);
    }
  }

  visitProperty(n: Property): Property | SpreadElement {
    switch (n.type) {
      case "Identifier":
        return this.visitIdentifier(n);
      case "AssignmentProperty":
        return this.visitAssignmentProperty(n);
      case "GetterProperty":
        return this.visitGetterProperty(n);
      case "KeyValueProperty":
        return this.visitKeyValueProperty(n);
      case "MethodProperty":
        return this.visitMethodProperty(n);
      case "SetterProperty":
        return this.visitSetterProperty(n);
    }
  }

  visitSetterProperty(n: SetterProperty): Property | SpreadElement {
    n.key = this.visitPropertyName(n.key);
    n.param = this.visitPattern(n.param);
    if (n.body) {
      n.body = this.visitBlockStatement(n.body);
    }
    return n;
  }

  visitMethodProperty(n: MethodProperty): Property | SpreadElement {
    n.key = this.visitPropertyName(n.key);
    if (n.body) {
      n.body = this.visitBlockStatement(n.body);
    }
    n.decorators = this.visitDecorators(n.decorators);
    n.params = this.visitParameters(n.params);
    n.returnType = this.visitTsTypeAnnotation(n.returnType);
    n.typeParameters = this.visitTsTypeParameterDeclaration(n.typeParameters);
    return n;
  }

  visitKeyValueProperty(n: KeyValueProperty): Property | SpreadElement {
    n.key = this.visitPropertyName(n.key);
    n.value = this.visitExpression(n.value);
    return n;
  }

  visitGetterProperty(n: GetterProperty): Property | SpreadElement {
    n.key = this.visitPropertyName(n.key);
    if (n.body) {
      n.body = this.visitBlockStatement(n.body);
    }
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation);
    return n;
  }

  visitAssignmentProperty(n: AssignmentProperty): Property | SpreadElement {
    n.key = this.visitIdentifier(n.key);
    n.value = this.visitExpression(n.value);
    return n;
  }

  visitNullLiteral(n: NullLiteral): NullLiteral {
    return n;
  }

  visitNewExpression(n: NewExpression): Expression {
    n.callee = this.visitExpression(n.callee);
    if (n.arguments) {
      n.arguments = this.visitArguments(n.arguments);
    }
    n.typeArguments = this.visitTsTypeArguments(n.typeArguments);
    return n;
  }

  visitTsTypeArguments(
    n: TsTypeParameterInstantiation | undefined
  ): TsTypeParameterInstantiation | undefined {
    if (n) {
      n.params = this.visitTsTypes(n.params);
    }
    return n;
  }

  visitArguments(nodes: Argument[]): Argument[] {
    return nodes.map(this.visitArgument.bind(this));
  }

  visitArgument(n: Argument): Argument {
    n.expression = this.visitExpression(n.expression);
    return n;
  }

  visitMetaProperty(n: MetaProperty): Expression {
    n.meta = this.visitIdentifierReference(n.meta);
    n.property = this.visitIdentifier(n.property);
    return n;
  }

  visitMemberExpression(n: MemberExpression): Expression {
    n.object = this.visitExpressionOrSuper(n.object);
    n.property = this.visitExpression(n.property);
    return n;
  }

  visitExpressionOrSuper(n: Expression | Super): Expression | Super {
    if (n.type === "Super") {
      return n;
    }
    return this.visitExpression(n as Expression);
  }

  visitJSXText(n: JSXText): JSXText {
    return n;
  }

  visitJSXNamespacedName(n: JSXNamespacedName): JSXNamespacedName {
    n.namespace = this.visitIdentifierReference(n.namespace);
    n.name = this.visitIdentifierReference(n.name);
    return n;
  }

  visitJSXMemberExpression(n: JSXMemberExpression): JSXMemberExpression {
    n.object = this.visitJSXObject(n.object);
    n.property = this.visitIdentifierReference(n.property);
    return n;
  }

  visitJSXObject(n: JSXObject): JSXObject {
    switch (n.type) {
      case "Identifier":
        return this.visitIdentifierReference(n);
      case "JSXMemberExpression":
        return this.visitJSXMemberExpression(n);
    }
  }

  visitJSXFragment(n: JSXFragment): JSXFragment {
    n.opening = this.visitJSXOpeningFragment(n.opening);
    if (n.children) {
      n.children = this.visitJSXElementChildren(n.children);
    }
    n.closing = this.visitJSXClosingFragment(n.closing);
    return n;
  }

  visitJSXClosingFragment(n: JSXClosingFragment): JSXClosingFragment {
    return n;
  }

  visitJSXElementChildren(nodes: JSXElementChild[]): JSXElementChild[] {
    return nodes.map(this.visitJSXElementChild.bind(this));
  }

  visitJSXElementChild(n: JSXElementChild): JSXElementChild {
    switch (n.type) {
      case "JSXElement":
        return this.visitJSXElement(n);
      case "JSXExpressionContainer":
        return this.visitJSXExpressionContainer(n);
      case "JSXFragment":
        return this.visitJSXFragment(n);
      case "JSXSpreadChild":
        return this.visitJSXSpreadChild(n);
      case "JSXText":
        return this.visitJSXText(n);
    }
  }

  visitJSXExpressionContainer(
    n: JSXExpressionContainer
  ): JSXExpressionContainer {
    n.expression = this.visitExpression(n.expression);
    return n;
  }

  visitJSXSpreadChild(n: JSXSpreadChild): JSXElementChild {
    n.expression = this.visitExpression(n.expression);
    return n;
  }

  visitJSXOpeningFragment(n: JSXOpeningFragment): JSXOpeningFragment {
    return n;
  }

  visitJSXEmptyExpression(n: JSXEmptyExpression): Expression {
    return n;
  }

  visitJSXElement(n: JSXElement): JSXElement {
    n.opening = this.visitJSXOpeningElement(n.opening);
    n.children = this.visitJSXElementChildren(n.children);
    n.closing = this.visitJSXClosingElement(n.closing);
    return n;
  }

  visitJSXClosingElement(
    n: JSXClosingElement | undefined
  ): JSXClosingElement | undefined {
    if (n) {
      n.name = this.visitJSXElementName(n.name);
    }
    return n;
  }

  visitJSXElementName(n: JSXElementName): JSXElementName {
    switch (n.type) {
      case "Identifier":
        return this.visitIdentifierReference(n);
      case "JSXMemberExpression":
        return this.visitJSXMemberExpression(n);
      case "JSXNamespacedName":
        return this.visitJSXNamespacedName(n);
    }
  }

  visitJSXOpeningElement(n: JSXOpeningElement): JSXOpeningElement {
    n.name = this.visitJSXElementName(n.name);
    n.typeArguments = this.visitTsTypeParameterInstantiation(n.typeArguments);
    n.attrs = this.visitJSXAttributes(n.attrs);
    return n;
  }

  visitJSXAttributes(
    attrs: JSXAttributeOrSpread[] | undefined
  ): JSXAttributeOrSpread[] | undefined {
    if (attrs) return attrs.map(this.visitJSXAttributeOrSpread.bind(this));
  }

  visitJSXAttributeOrSpread(n: JSXAttributeOrSpread): JSXAttributeOrSpread {
    switch (n.type) {
      case "JSXAttribute":
        return this.visitJSXAttribute(n);
      case "SpreadElement":
        return this.visitSpreadElement(n);
    }
  }

  visitJSXAttribute(n: JSXAttribute): JSXAttributeOrSpread {
    n.name = this.visitJSXAttributeName(n.name);
    n.value = this.visitJSXAttributeValue(n.value);
    return n;
  }

  visitJSXAttributeValue(
    n: JSXAttrValue | undefined
  ): JSXAttrValue | undefined {
    if (!n) return n;

    switch (n.type) {
      case "BooleanLiteral":
        return this.visitBooleanLiteral(n);
      case "NullLiteral":
        return this.visitNullLiteral(n);
      case "NumericLiteral":
        return this.visitNumericLiteral(n);
      case "JSXText":
        return this.visitJSXText(n);
      case "StringLiteral":
        return this.visitStringLiteral(n);

      case "JSXElement":
        return this.visitJSXElement(n);
      case "JSXExpressionContainer":
        return this.visitJSXExpressionContainer(n);
      case "JSXFragment":
        return this.visitJSXFragment(n);
    }
    return n;
  }

  visitJSXAttributeName(n: JSXAttributeName): JSXAttributeName {
    switch (n.type) {
      case "Identifier":
        return this.visitIdentifierReference(n);
      case "JSXNamespacedName":
        return this.visitJSXNamespacedName(n);
    }
  }

  visitConditionalExpression(n: ConditionalExpression): Expression {
    n.test = this.visitExpression(n.test);
    n.consequent = this.visitExpression(n.consequent);
    n.alternate = this.visitExpression(n.alternate);
    return n;
  }

  visitCallExpression(n: CallExpression): Expression {
    n.callee = this.visitExpressionOrSuper(n.callee);
    n.typeArguments = this.visitTsTypeParameterInstantiation(n.typeArguments);
    if (n.arguments) {
      n.arguments = this.visitArguments(n.arguments);
    }

    return n;
  }

  visitBooleanLiteral(n: BooleanLiteral): BooleanLiteral {
    return n;
  }

  visitBinaryExpression(n: BinaryExpression): Expression {
    n.left = this.visitExpression(n.left);
    n.right = this.visitExpression(n.right);
    return n;
  }

  visitAwaitExpression(n: AwaitExpression): Expression {
    n.argument = this.visitExpression(n.argument);
    return n;
  }

  visitTsTypeParameterDeclaration(
    n: TsTypeParameterDeclaration | undefined
  ): TsTypeParameterDeclaration | undefined {
    if (n) {
      n.parameters = this.visitTsTypeParameters(n.parameters);
    }
    return n;
  }

  visitTsTypeParameters(nodes: TsTypeParameter[]): TsTypeParameter[] {
    return nodes.map(this.visitTsTypeParameter.bind(this));
  }

  visitTsTypeParameter(n: TsTypeParameter): TsTypeParameter {
    if (n.constraint) {
      n.constraint = this.visitTsType(n.constraint);
    }
    if (n.default) {
      n.default = this.visitTsType(n.default);
    }
    n.name = this.visitIdentifierReference(n.name);
    return n;
  }

  visitTsTypeAnnotation(
    a: TsTypeAnnotation | undefined
  ): TsTypeAnnotation | undefined {
    if (a) {
      a.typeAnnotation = this.visitTsType(a.typeAnnotation);
    }
    return a;
  }

  visitTsType(n: TsType): TsType {
    throw new Error("Method visitTsType not implemented.");
  }

  visitPatterns(nodes: Pattern[]): Pattern[] {
    return nodes.map(this.visitPattern.bind(this));
  }

  visitImportDeclaration(n: ImportDeclaration): ImportDeclaration {
    n.source = this.visitStringLiteral(n.source);
    n.specifiers = this.visitImportSpecifiers(n.specifiers || []);
    return n;
  }

  visitImportSpecifiers(nodes: ImportSpecifier[]): ImportSpecifier[] {
    return nodes.map(this.visitImportSpecifier.bind(this));
  }

  visitImportSpecifier(node: ImportSpecifier): ImportSpecifier {
    switch (node.type) {
      case "ImportDefaultSpecifier":
        return this.visitImportDefaultSpecifier(node);

      case "ImportNamespaceSpecifier":
        return this.visitImportNamespaceSpecifier(node);

      case "ImportSpecifier":
        return this.visitNamedImportSpecifier(node);
    }
  }
  visitNamedImportSpecifier(node: NamedImportSpecifier): NamedImportSpecifier {
    node.local = this.visitBindingIdentifier(node.local);

    if (node.imported) {
      node.imported = this.visitIdentifierReference(node.imported);
    }

    return node;
  }

  visitImportNamespaceSpecifier(
    node: ImportNamespaceSpecifier
  ): ImportNamespaceSpecifier {
    node.local = this.visitBindingIdentifier(node.local);

    return node;
  }

  visitImportDefaultSpecifier(node: ImportDefaultSpecifier): ImportSpecifier {
    node.local = this.visitBindingIdentifier(node.local);

    return node;
  }

  visitBindingIdentifier(i: Identifier): Identifier {
    return this.visitIdentifier(i);
  }

  visitIdentifierReference(i: Identifier): Identifier {
    return this.visitIdentifier(i);
  }

  visitLabelIdentifier(label: Identifier): Identifier {
    return this.visitIdentifier(label);
  }

  visitIdentifier(n: Identifier): Identifier {
    return n;
  }

  visitStringLiteral(n: StringLiteral): StringLiteral {
    return n;
  }

  visitNumericLiteral(n: NumericLiteral): NumericLiteral {
    return n;
  }

  visitPattern(n: Pattern): Pattern {
    switch (n.type) {
      case "Identifier":
        return this.visitBindingIdentifier(n);
      case "ArrayPattern":
        return this.visitArrayPattern(n);
      case "ObjectPattern":
        return this.visitObjectPattern(n);
      case "AssignmentPattern":
        return this.visitAssignmentPattern(n);
      case "RestElement":
        return this.visitRestElement(n);
      default:
        return this.visitExpression(n);
    }
  }

  visitRestElement(n: RestElement): RestElement {
    n.argument = this.visitPattern(n.argument);
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation);
    return n;
  }

  visitAssignmentPattern(n: AssignmentPattern): Pattern {
    n.left = this.visitPattern(n.left);
    n.right = this.visitExpression(n.right);
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation);
    return n;
  }

  visitObjectPattern(n: ObjectPattern): Pattern {
    n.props = this.visitObjectPatternProperties(n.props || []);
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation);
    return n;
  }

  visitObjectPatternProperties(
    nodes: ObjectPatternProperty[]
  ): ObjectPatternProperty[] {
    return nodes.map(this.visitObjectPatternProperty.bind(this));
  }

  visitObjectPatternProperty(n: ObjectPatternProperty): ObjectPatternProperty {
    switch (n.type) {
      case "AssignmentPatternProperty":
        return this.visitAssignmentPatternProperty(n);
      case "KeyValuePatternProperty":
        return this.visitKeyValuePatternProperty(n);
      case "RestElement":
        return this.visitRestElement(n);
    }
  }

  visitKeyValuePatternProperty(
    n: KeyValuePatternProperty
  ): ObjectPatternProperty {
    n.key = this.visitPropertyName(n.key);
    n.value = this.visitPattern(n.value);
    return n;
  }

  visitAssignmentPatternProperty(
    n: AssignmentPatternProperty
  ): ObjectPatternProperty {
    n.key = this.visitBindingIdentifier(n.key);
    n.value = this.visitOptionalExpression(n.value);
    return n;
  }

  visitArrayPattern(n: ArrayPattern): Pattern {
    n.typeAnnotation = this.visitTsTypeAnnotation(n.typeAnnotation);
    n.elements = this.visitArrayPatternElements(n.elements);
    return n;
  }

  visitArrayPatternElements(
    nodes: (Pattern | undefined)[]
  ): (Pattern | undefined)[] {
    return nodes.map(this.visitArrayPatternElement.bind(this));
  }

  visitArrayPatternElement(n: Pattern | undefined): Pattern | undefined {
    if (n) {
      n = this.visitPattern(n);
    }
    return n;
  }
}
