Skip to content

⬅️ Back to Table of Contents

πŸ“„ convert

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 30
🧱 Classes 1
πŸ“¦ Imports 33
πŸ“Š Variables & Constants 1
πŸ“ Interfaces 2

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/typescript-estree/src/convert.ts

πŸ“¦ Imports

Name Source
TSError ./node-utils
ParserWeakMap ./parser-options
ParserWeakMapESTreeToTSNode ./parser-options
SemanticOrSyntacticError ./semantic-or-syntactic-errors
TSESTree ./ts-estree
TSESTreeToTSNode ./ts-estree
TSNode ./ts-estree
checkSyntaxError ./check-syntax-errors
getImportClausePhaseModifier ./getImportClausePhaseModifier
getDecorators ./getModifiers
getModifiers ./getModifiers
canContainDirective ./node-utils
createError ./node-utils
findNextToken ./node-utils
getBinaryExpressionType ./node-utils
getDeclarationKind ./node-utils
getLastModifier ./node-utils
getLineAndCharacterFor ./node-utils
getLocFor ./node-utils
getNamespaceModifiers ./node-utils
getRange ./node-utils
getTextForTokenKind ./node-utils
getTSNodeAccessibility ./node-utils
hasModifier ./node-utils
isChainExpression ./node-utils
isChildUnwrappableOptionalChain ./node-utils
isComma ./node-utils
isComputedProperty ./node-utils
isESTreeClassMember ./node-utils
isOptional ./node-utils
isThisInTypeQuery ./node-utils
unescapeStringLiteralText ./node-utils
AST_NODE_TYPES ./ts-estree

Variables & Constants

Name Type Kind Value Exported
SyntaxKind any const ts.SyntaxKind βœ—

Functions

convertError(error: SemanticOrSyntacticError | ts.Diagnosti…): TSError

Extends and formats a given error object

Parameters:

  • error any: the error object

Returns: undefined converted error object

Raw JSDoc
/**
 * Extends and formats a given error object
 * @param error the error object
 * @returns converted error object
 */

Calls:

  • createError (from ./node-utils)
Code
export function convertError(
  error: SemanticOrSyntacticError | ts.DiagnosticWithLocation,
): TSError {
  return createError(
    error.start!,
    ('message' in error && error.message) || (error.messageText as string),
    error.file!,
  );
}

Converter.#checkSyntaxError(node: ts.Node, parent: TSNode): void

Parameters:

  • node ts.Node
  • parent TSNode

Returns: void

Calls:

  • checkSyntaxError (from ./check-syntax-errors)
Code
#checkSyntaxError(node: ts.Node, parent: TSNode): void {
    if (this.options.allowInvalidAST) {
      return;
    }

    checkSyntaxError(node, parent, this.allowPattern);
  }

Converter.#isValidEscape(text: string): boolean

Parameters:

  • text string

Returns: boolean

Calls:

  • /\\[xu]/.test
  • /\\u(?![0-9a-fA-F]{4}|{)/.test
  • /\\x(?![0-9a-fA-F]{2})/.test
Code
#isValidEscape(text: string): boolean {
    if (/\\[xu]/.test(text)) {
      const hasInvalidUnicodeEscape = /\\u(?![0-9a-fA-F]{4}|{)/.test(text);
      const hasInvalidHexEscape = /\\x(?![0-9a-fA-F]{2})/.test(text);

      return !hasInvalidUnicodeEscape && !hasInvalidHexEscape;
    }
    return true;
  }

Converter.#throwError(node: number | ts.Node | TSESTree.Range, message: string): asserts node is never

Parameters:

  • node number | ts.Node | TSESTree.Range
  • message string

Returns: asserts node is never

Calls:

  • createError (from ./node-utils)
Code
#throwError(
    node: number | ts.Node | TSESTree.Range,
    message: string,
  ): asserts node is never {
    if (this.options.allowInvalidAST) {
      return;
    }

    throw createError(node, message, this.ast);
  }

Converter.#withDeprecatedAliasGetter(node: Properties, aliasKey: AliasKey, valueKey: ValueKey, suppressWarnings: boolean): Properties & Record<AliasKey, Properties[ValueKey]>

Creates a getter for a property under aliasKey that returns the value under valueKey. If suppressDeprecatedPropertyWarnings is not enabled, the getter also console warns about the deprecation.

See: https://github.com/typescript-eslint/typescript-eslint/issues/6469

Raw JSDoc
/**
   * Creates a getter for a property under aliasKey that returns the value under
   * valueKey. If suppressDeprecatedPropertyWarnings is not enabled, the
   * getter also console warns about the deprecation.
   *
   * @see https://github.com/typescript-eslint/typescript-eslint/issues/6469
   */

Calls:

  • Object.defineProperty
  • process.emitWarning
Code
#withDeprecatedAliasGetter<
    Properties extends { type: string },
    AliasKey extends string,
    ValueKey extends keyof Properties & string,
  >(
    node: Properties,
    aliasKey: AliasKey,
    valueKey: ValueKey,
    suppressWarnings = false,
  ): Properties & Record<AliasKey, Properties[ValueKey]> {
    let warned = suppressWarnings;

    Object.defineProperty(node, aliasKey, {
      configurable: true,
      get: this.options.suppressDeprecatedPropertyWarnings
        ? (): Properties[typeof valueKey] => node[valueKey]
        : (): Properties[typeof valueKey] => {
            if (!warned) {
              process.emitWarning(
                `The '${aliasKey}' property is deprecated on ${node.type} nodes. Use '${valueKey}' instead. See https://tseslint.com/key-property-deprecated.`,
                'DeprecationWarning',
              );
              warned = true;
            }

            return node[valueKey];
          },
      set(value): void {
        Object.defineProperty(node, aliasKey, {
          enumerable: true,
          value,
          writable: true,
        });
      },
    });

    return node as Properties & Record<AliasKey, Properties[ValueKey]>;
  }

Converter.#withDeprecatedGetter(node: Properties, deprecatedKey: Key, preferredKey: string | undefined, value: Value): Properties & Record<Key, Value>

Parameters:

  • node Properties
  • deprecatedKey Key
  • preferredKey string | undefined
  • value Value

Returns: Properties & Record<Key, Value>

Calls:

  • Object.defineProperty
  • process.emitWarning
Code
#withDeprecatedGetter<
    Properties extends { type: string },
    Key extends string,
    Value,
  >(
    node: Properties,
    deprecatedKey: Key,
    preferredKey: string | undefined,
    value: Value,
  ): Properties & Record<Key, Value> {
    let warned = false;

    Object.defineProperty(node, deprecatedKey, {
      configurable: true,
      get: this.options.suppressDeprecatedPropertyWarnings
        ? (): Value => value
        : (): Value => {
            if (!warned) {
              let message = `The '${deprecatedKey}' property is deprecated on ${node.type} nodes.`;
              if (preferredKey) {
                message += ` Use ${preferredKey} instead.`;
              }
              message += ' See https://tseslint.com/key-property-deprecated.';
              process.emitWarning(message, 'DeprecationWarning');
              warned = true;
            }

            return value;
          },
      set(value): void {
        Object.defineProperty(node, deprecatedKey, {
          enumerable: true,
          value,
          writable: true,
        });
      },
    });

    return node as Properties & Record<Key, Value>;
  }

Converter.convertBindingNameWithTypeAnnotation(name: ts.BindingName, tsType: ts.TypeNode | undefined, parent: ts.Node): TSESTree.BindingName

Parameters:

  • name ts.BindingName
  • tsType ts.TypeNode | undefined
  • parent ts.Node

Returns: TSESTree.BindingName

Calls:

  • this.convertPattern
  • this.convertTypeAnnotation
  • this.fixParentLocation
Code
private convertBindingNameWithTypeAnnotation(
    name: ts.BindingName,
    tsType: ts.TypeNode | undefined,
    parent?: ts.Node,
  ): TSESTree.BindingName {
    const id = this.convertPattern(name) as TSESTree.BindingName;

    if (tsType) {
      id.typeAnnotation = this.convertTypeAnnotation(tsType, parent);
      this.fixParentLocation(id, id.typeAnnotation.range);
    }

    return id;
  }

Converter.convertBodyExpressions(nodes: ts.NodeArray<ts.Statement>, parent: | ts.Block | ts.ClassStaticBlockDeclara…): TSESTree.Statement[]

Coverts body Nodes and add a directive field to StringLiterals

Parameters:

  • nodes any: of ts.Node
  • parent any: parentNode

Returns: undefined Array of body statements

Raw JSDoc
/**
   * Coverts body Nodes and add a directive field to StringLiterals
   * @param nodes of ts.Node
   * @param parent parentNode
   * @returns Array of body statements
   */

Calls:

  • canContainDirective (from ./node-utils)
  • nodes .map(statement => { const child = this.convertChild(statement); if (allowDirectives) { if ( child?.expression && ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression) ) { const raw = child.expression.raw; child.directive = raw.slice(1, -1); return child; // child can be null, but it's filtered below } allowDirectives = false; } return child; // child can be null, but it's filtered below }) // filter out unknown nodes for now .filter
Code
private convertBodyExpressions(
    nodes: ts.NodeArray<ts.Statement>,
    parent:
      | ts.Block
      | ts.ClassStaticBlockDeclaration
      | ts.ModuleBlock
      | ts.SourceFile,
  ): TSESTree.Statement[] {
    let allowDirectives = canContainDirective(parent);

    return (
      nodes
        .map(statement => {
          const child = this.convertChild(statement);
          if (allowDirectives) {
            if (
              child?.expression &&
              ts.isExpressionStatement(statement) &&
              ts.isStringLiteral(statement.expression)
            ) {
              const raw = child.expression.raw;
              child.directive = raw.slice(1, -1);
              return child; // child can be null, but it's filtered below
            }
            allowDirectives = false;
          }
          return child; // child can be null, but it's filtered below
        })
        // filter out unknown nodes for now
        .filter(statement => statement)
    );
  }

Converter.convertChainExpression(node: TSESTree.ChainElement, tsNode: | ts.CallExpression | ts.ElementAccessE…): TSESTree.ChainElement | TSESTree.ChainExpression

Parameters:

  • node TSESTree.ChainElement
  • tsNode | ts.CallExpression | ts.ElementAccessExpression | ts.NonNullExpression | ts.PropertyAccessExpression

Returns: TSESTree.ChainElement | TSESTree.ChainExpression

Calls:

  • complex_call_7975
  • isChildUnwrappableOptionalChain (from ./node-utils)
  • isChainExpression (from ./node-utils)
  • this.createNode

Internal Comments:

// unwrap the chain expression child (x2)

Code
private convertChainExpression(
    node: TSESTree.ChainElement,
    tsNode:
      | ts.CallExpression
      | ts.ElementAccessExpression
      | ts.NonNullExpression
      | ts.PropertyAccessExpression,
  ): TSESTree.ChainElement | TSESTree.ChainExpression {
    const { child, isOptional } = ((): {
      child: TSESTree.Node;
      isOptional: boolean;
    } => {
      if (node.type === AST_NODE_TYPES.MemberExpression) {
        return { child: node.object, isOptional: node.optional };
      }
      if (node.type === AST_NODE_TYPES.CallExpression) {
        return { child: node.callee, isOptional: node.optional };
      }
      return { child: node.expression, isOptional: false };
    })();
    const isChildUnwrappable = isChildUnwrappableOptionalChain(tsNode, child);

    if (!isChildUnwrappable && !isOptional) {
      return node;
    }

    if (isChildUnwrappable && isChainExpression(child)) {
      // unwrap the chain expression child
      const newChild = child.expression;
      if (node.type === AST_NODE_TYPES.MemberExpression) {
        node.object = newChild;
      } else if (node.type === AST_NODE_TYPES.CallExpression) {
        node.callee = newChild;
      } else {
        node.expression = newChild;
      }
    }

    return this.createNode<TSESTree.ChainExpression>(tsNode, {
      type: AST_NODE_TYPES.ChainExpression,
      expression: node,
    });
  }

Converter.convertChild(child: ts.Node, parent: ts.Node): any

Converts a TypeScript node into an ESTree node.

Parameters:

  • child any: the child ts.Node
  • parent any: parentNode

Returns: undefined the converted ESTree node

Raw JSDoc
/**
   * Converts a TypeScript node into an ESTree node.
   * @param child the child ts.Node
   * @param parent parentNode
   * @returns the converted ESTree node
   */

Calls:

  • this.converter
Code
private convertChild(child?: ts.Node, parent?: ts.Node): any {
    return this.converter(child, parent, false);
  }

Converter.convertChildren(children: ts.Node[] | ts.NodeArray<ts.Node>, parent: ts.Node): any[]

Converts TypeScript node array into an ESTree node list.

Parameters:

  • children any: the child ts.NodeArray or ts.Node[]
  • parent any: parentNode

Returns: undefined the converted ESTree node list

Raw JSDoc
/**
   * Converts TypeScript node array into an ESTree node list.
   * @param children the child `ts.NodeArray` or `ts.Node[]`
   * @param parent parentNode
   * @returns the converted ESTree node list
   */

Calls:

  • children.map
  • this.converter
Code
private convertChildren(
    children: ts.Node[] | ts.NodeArray<ts.Node>,
    parent?: ts.Node,
  ): any[] {
    return children.map(child => this.converter(child, parent, false));
  }

Converter.convertPattern(child: ts.Node, parent: ts.Node): any

Converts a TypeScript node into an ESTree node.

Parameters:

  • child any: the child ts.Node
  • parent any: parentNode

Returns: undefined the converted ESTree node

Raw JSDoc
/**
   * Converts a TypeScript node into an ESTree node.
   * @param child the child ts.Node
   * @param parent parentNode
   * @returns the converted ESTree node
   */

Calls:

  • this.converter
Code
private convertPattern(child?: ts.Node, parent?: ts.Node): any {
    return this.converter(child, parent, true);
  }

Converter.convertTypeAnnotation(child: ts.TypeNode, parent: ts.Node | undefined): TSESTree.TSTypeAnnotation

Converts a child into a type annotation. This creates an intermediary TypeAnnotation node to match what Flow does.

Parameters:

  • child any: The TypeScript AST node to convert.
  • parent any: parentNode

Returns: undefined The type annotation node.

Raw JSDoc
/**
   * Converts a child into a type annotation. This creates an intermediary
   * TypeAnnotation node to match what Flow does.
   * @param child The TypeScript AST node to convert.
   * @param parent parentNode
   * @returns The type annotation node.
   */

Calls:

  • child.getFullStart
  • getLocFor (from ./node-utils)
  • this.convertChild

Internal Comments:

// in FunctionType and ConstructorType typeAnnotation has 2 characters `=>` and in other places is just colon (x2)

Code
private convertTypeAnnotation(
    child: ts.TypeNode,
    parent: ts.Node | undefined,
  ): TSESTree.TSTypeAnnotation {
    // in FunctionType and ConstructorType typeAnnotation has 2 characters `=>` and in other places is just colon
    const offset =
      parent?.kind === SyntaxKind.FunctionType ||
      parent?.kind === SyntaxKind.ConstructorType
        ? 2
        : 1;
    const annotationStartCol = child.getFullStart() - offset;
    const range: TSESTree.Range = [annotationStartCol, child.end];
    const loc = getLocFor(range, this.ast);

    return {
      type: AST_NODE_TYPES.TSTypeAnnotation,
      loc,
      range,
      typeAnnotation: this.convertChild(child),
    } as TSESTree.TSTypeAnnotation;
  }

Converter.convertTypeArguments(node: TSESTreeToTSNode<TSESTree.TSTypeParamet…): TSESTree.TSTypeParameterInstantiation | undefined

Converts a ts.Node's typeArguments to TSTypeParameterInstantiation node

Parameters:

  • typeArguments any: ts.NodeArray typeArguments
  • node any: parent used to create this node

Returns: undefined TypeParameterInstantiation node

Raw JSDoc
/**
   * Converts a ts.Node's typeArguments to TSTypeParameterInstantiation node
   * @param typeArguments ts.NodeArray typeArguments
   * @param node parent used to create this node
   * @returns TypeParameterInstantiation node
   */

Calls:

  • findNextToken (from ./node-utils)
  • this.#throwError
  • this.createNode
  • this.convertChildren
Code
private convertTypeArguments(
    node: TSESTreeToTSNode<TSESTree.TSTypeParameterInstantiation>,
  ): TSESTree.TSTypeParameterInstantiation | undefined {
    const { typeArguments } = node;

    if (!typeArguments) {
      return undefined;
    }

    const greaterThanToken = findNextToken(typeArguments, this.ast, this.ast)!;
    const range: TSESTree.Range = [typeArguments.pos - 1, greaterThanToken.end];

    if (typeArguments.length === 0) {
      this.#throwError(range, 'Type argument list cannot be empty.');
    }

    return this.createNode<TSESTree.TSTypeParameterInstantiation>(node, {
      type: AST_NODE_TYPES.TSTypeParameterInstantiation,
      range,
      params: this.convertChildren(typeArguments),
    });
  }

Converter.convertTypeParameters(node: | ts.ArrowFunction | ts.CallSignatureDe…): TSESTree.TSTypeParameterDeclaration | undefined

Converts a ts.Node's typeParameters to TSTypeParameterDeclaration node

Parameters:

  • typeParameters any: ts.Node typeParameters

Returns: undefined TypeParameterDeclaration node

Raw JSDoc
/**
   * Converts a ts.Node's typeParameters to TSTypeParameterDeclaration node
   * @param typeParameters ts.Node typeParameters
   * @returns TypeParameterDeclaration node
   */

Calls:

  • findNextToken (from ./node-utils)
  • this.#throwError
  • getLocFor (from ./node-utils)
  • this.convertChildren
Code
private convertTypeParameters(
    node:
      | ts.ArrowFunction
      | ts.CallSignatureDeclaration
      | ts.ClassDeclaration
      | ts.ClassExpression
      | ts.ConstructorDeclaration
      | ts.ConstructorTypeNode
      | ts.ConstructSignatureDeclaration
      | ts.FunctionDeclaration
      | ts.FunctionExpression
      | ts.FunctionTypeNode
      | ts.GetAccessorDeclaration
      | ts.IndexSignatureDeclaration
      | ts.InterfaceDeclaration
      | ts.MethodDeclaration
      | ts.MethodSignature
      | ts.SetAccessorDeclaration
      | ts.TypeAliasDeclaration,
  ): TSESTree.TSTypeParameterDeclaration | undefined {
    const { typeParameters } = node;

    if (!typeParameters) {
      return undefined;
    }

    const greaterThanToken = findNextToken(typeParameters, this.ast, this.ast)!;
    const range: TSESTree.Range = [
      typeParameters.pos - 1,
      greaterThanToken.end,
    ];

    if (typeParameters.length === 0) {
      this.#throwError(range, 'Type parameter list cannot be empty.');
    }

    return {
      type: AST_NODE_TYPES.TSTypeParameterDeclaration,
      loc: getLocFor(range, this.ast),
      range,
      params: this.convertChildren(typeParameters),
    } as TSESTree.TSTypeParameterDeclaration;
  }

Converter.convertParameters(parameters: ts.NodeArray<ts.ParameterDeclaration>): TSESTree.Parameter[]

Converts an array of ts.Node parameters into an array of ESTreeNode params

Parameters:

  • parameters any: An array of ts.Node params to be converted

Returns: undefined an array of converted ESTreeNode params

Raw JSDoc
/**
   * Converts an array of ts.Node parameters into an array of ESTreeNode params
   * @param parameters An array of ts.Node params to be converted
   * @returns an array of converted ESTreeNode params
   */

Calls:

  • parameters.map
  • this.convertChild
  • this.convertChildren
  • getDecorators (from ./getModifiers)
Code
private convertParameters(
    parameters: ts.NodeArray<ts.ParameterDeclaration>,
  ): TSESTree.Parameter[] {
    if (!parameters?.length) {
      return [];
    }
    return parameters.map(param => {
      const convertedParam = this.convertChild(param) as TSESTree.Parameter;

      convertedParam.decorators = this.convertChildren(
        getDecorators(param) ?? [],
      );

      return convertedParam;
    });
  }

Converter.converter(node: ts.Node, parent: ts.Node, allowPattern: boolean): any

Converts a TypeScript node into an ESTree node.

Parameters:

  • node any: the child ts.Node
  • parent any: parentNode
  • allowPattern any: flag to determine if patterns are allowed

Returns: undefined the converted ESTree node

Raw JSDoc
/**
   * Converts a TypeScript node into an ESTree node.
   * @param node the child ts.Node
   * @param parent parentNode
   * @param allowPattern flag to determine if patterns are allowed
   * @returns the converted ESTree node
   */

Calls:

  • this.#checkSyntaxError
  • this.convertNode
  • this.registerTSNodeInNodeMap

Internal Comments:

/**
     * Exit early for null and undefined
     */

Code
private converter(
    node?: ts.Node,
    parent?: ts.Node,
    allowPattern?: boolean,
  ): any {
    /**
     * Exit early for null and undefined
     */
    if (!node) {
      return null;
    }

    const pattern = this.allowPattern;
    if (allowPattern != null) {
      this.allowPattern = allowPattern;
    }

    const parentNode = (parent ?? node.parent) as TSNode;
    this.#checkSyntaxError(node, parentNode);

    const result = this.convertNode(node as TSNode, parentNode);

    this.registerTSNodeInNodeMap(node, result);

    this.allowPattern = pattern;
    return result;
  }

Converter.convertImportAttributes(node: ts.ExportDeclaration | ts.ImportDeclara…): TSESTree.ImportAttribute[]

Parameters:

  • node ts.ExportDeclaration | ts.ImportDeclaration

Returns: TSESTree.ImportAttribute[]

Calls:

  • this.convertChildren

Internal Comments:

// eslint-disable-next-line @typescript-eslint/no-deprecated (x2)

Code
private convertImportAttributes(
    node: ts.ExportDeclaration | ts.ImportDeclaration,
  ): TSESTree.ImportAttribute[] {
    // eslint-disable-next-line @typescript-eslint/no-deprecated
    const attributes = node.attributes ?? node.assertClause;
    return this.convertChildren(attributes?.elements ?? []);
  }

Converter.convertJSXIdentifier(node: ts.Identifier | ts.ThisExpression): TSESTree.JSXIdentifier

Parameters:

  • node ts.Identifier | ts.ThisExpression

Returns: TSESTree.JSXIdentifier

Calls:

  • this.createNode
  • node.getText
  • this.registerTSNodeInNodeMap
Code
private convertJSXIdentifier(
    node: ts.Identifier | ts.ThisExpression,
  ): TSESTree.JSXIdentifier {
    const result = this.createNode<TSESTree.JSXIdentifier>(node, {
      type: AST_NODE_TYPES.JSXIdentifier,
      name: node.getText(),
    });
    this.registerTSNodeInNodeMap(node, result);
    return result;
  }

Converter.convertJSXNamespaceOrIdentifier(node: ts.Identifier | ts.JsxNamespacedName | …): TSESTree.JSXIdentifier | TSESTree.JSXNamespacedName

Parameters:

  • node ts.Identifier | ts.JsxNamespacedName | ts.ThisExpression

Returns: TSESTree.JSXIdentifier | TSESTree.JSXNamespacedName

Calls:

  • this.createNode
  • this.registerTSNodeInNodeMap
  • node.getText
  • text.indexOf
  • getRange (from ./node-utils)
  • text.slice
  • this.convertJSXIdentifier

Internal Comments:

// TypeScript@5.1 added in ts.JsxNamespacedName directly
// We prefer using that if it's relevant for this node type
// TypeScript@<5.1 has to manually parse the JSX attributes (x2)
// this is intentional we can ignore conversion if `:` is in first character

Code
private convertJSXNamespaceOrIdentifier(
    node: ts.Identifier | ts.JsxNamespacedName | ts.ThisExpression,
  ): TSESTree.JSXIdentifier | TSESTree.JSXNamespacedName {
    // TypeScript@5.1 added in ts.JsxNamespacedName directly
    // We prefer using that if it's relevant for this node type
    if (node.kind === ts.SyntaxKind.JsxNamespacedName) {
      const result = this.createNode<TSESTree.JSXNamespacedName>(node, {
        type: AST_NODE_TYPES.JSXNamespacedName,
        name: this.createNode(node.name, {
          type: AST_NODE_TYPES.JSXIdentifier,
          name: node.name.text,
        }),
        namespace: this.createNode(node.namespace, {
          type: AST_NODE_TYPES.JSXIdentifier,
          name: node.namespace.text,
        }),
      });
      this.registerTSNodeInNodeMap(node, result);
      return result;
    }

    // TypeScript@<5.1 has to manually parse the JSX attributes
    const text = node.getText();
    const colonIndex = text.indexOf(':');
    // this is intentional we can ignore conversion if `:` is in first character
    if (colonIndex > 0) {
      const range = getRange(node, this.ast);
      const result = this.createNode<TSESTree.JSXNamespacedName>(node, {
        type: AST_NODE_TYPES.JSXNamespacedName,
        range,
        name: this.createNode<TSESTree.JSXIdentifier>(node, {
          type: AST_NODE_TYPES.JSXIdentifier,
          range: [range[0] + colonIndex + 1, range[1]],
          name: text.slice(colonIndex + 1),
        }),
        namespace: this.createNode<TSESTree.JSXIdentifier>(node, {
          type: AST_NODE_TYPES.JSXIdentifier,
          range: [range[0], range[0] + colonIndex],
          name: text.slice(0, colonIndex),
        }),
      });
      this.registerTSNodeInNodeMap(node, result);
      return result;
    }

    return this.convertJSXIdentifier(node);
  }

Converter.convertJSXTagName(node: ts.JsxTagNameExpression, parent: ts.Node): TSESTree.JSXTagNameExpression

Converts a TypeScript JSX node.tagName into an ESTree node.name

Parameters:

  • node any: the tagName object from a JSX ts.Node

Returns: undefined the converted ESTree name object

Raw JSDoc
/**
   * Converts a TypeScript JSX node.tagName into an ESTree node.name
   * @param node the tagName object from a JSX ts.Node
   * @returns the converted ESTree name object
   */

Calls:

  • this.#throwError
  • this.createNode
  • this.convertJSXTagName
  • this.convertJSXIdentifier
  • this.convertJSXNamespaceOrIdentifier
  • this.registerTSNodeInNodeMap

Internal Comments:

// This is one of the few times where TS explicitly errors, and doesn't even gracefully handle the syntax. (x4)
// So we shouldn't ever get into this state to begin with. (x4)

Code
private convertJSXTagName(
    node: ts.JsxTagNameExpression,
    parent: ts.Node,
  ): TSESTree.JSXTagNameExpression {
    let result: TSESTree.JSXTagNameExpression;
    switch (node.kind) {
      case SyntaxKind.PropertyAccessExpression:
        if (node.name.kind === SyntaxKind.PrivateIdentifier) {
          // This is one of the few times where TS explicitly errors, and doesn't even gracefully handle the syntax.
          // So we shouldn't ever get into this state to begin with.
          this.#throwError(node.name, 'Non-private identifier expected.');
        }

        result = this.createNode<TSESTree.JSXMemberExpression>(node, {
          type: AST_NODE_TYPES.JSXMemberExpression,
          object: this.convertJSXTagName(node.expression, parent),
          property: this.convertJSXIdentifier(node.name),
        });
        break;

      case SyntaxKind.ThisKeyword:
      case SyntaxKind.Identifier:
      default:
        return this.convertJSXNamespaceOrIdentifier(node);
    }

    this.registerTSNodeInNodeMap(node, result);
    return result;
  }

Converter.convertMethodSignature(node: | ts.GetAccessorDeclaration | ts.Method…): TSESTree.TSMethodSignature

Parameters:

  • node | ts.GetAccessorDeclaration | ts.MethodSignature | ts.SetAccessorDeclaration

Returns: TSESTree.TSMethodSignature

Calls:

  • this.createNode
  • getTSNodeAccessibility (from ./node-utils)
  • isComputedProperty (from ./node-utils)
  • this.convertChild
  • complex_call_19097
  • isOptional (from ./node-utils)
  • this.convertParameters
  • hasModifier (from ./node-utils)
  • this.convertTypeAnnotation
  • this.convertTypeParameters
Code
private convertMethodSignature(
    node:
      | ts.GetAccessorDeclaration
      | ts.MethodSignature
      | ts.SetAccessorDeclaration,
  ): TSESTree.TSMethodSignature {
    return this.createNode<TSESTree.TSMethodSignature>(node, {
      type: AST_NODE_TYPES.TSMethodSignature,
      accessibility: getTSNodeAccessibility(node),
      computed: isComputedProperty(node.name),
      key: this.convertChild(node.name),
      kind: ((): 'get' | 'method' | 'set' => {
        switch (node.kind) {
          case SyntaxKind.GetAccessor:
            return 'get';

          case SyntaxKind.SetAccessor:
            return 'set';

          case SyntaxKind.MethodSignature:
            return 'method';
        }
      })(),
      optional: isOptional(node),
      params: this.convertParameters(node.parameters),
      readonly: hasModifier(SyntaxKind.ReadonlyKeyword, node),
      returnType: node.type && this.convertTypeAnnotation(node.type, node),
      static: hasModifier(SyntaxKind.StaticKeyword, node),
      typeParameters: this.convertTypeParameters(node),
    });
  }

Converter.fixParentLocation(result: TSESTree.BaseNode, childRange: [number, number]): void

Uses the provided range location to adjust the location data of the given Node

Parameters:

  • result any: The node that will have its location data mutated
  • childRange any: The child node range used to expand location
Raw JSDoc
/**
   * Uses the provided range location to adjust the location data of the given Node
   * @param result The node that will have its location data mutated
   * @param childRange The child node range used to expand location
   */

Calls:

  • getLineAndCharacterFor (from ./node-utils)
Code
private fixParentLocation(
    result: TSESTree.BaseNode,
    childRange: [number, number],
  ): void {
    if (childRange[0] < result.range[0]) {
      result.range[0] = childRange[0];
      result.loc.start = getLineAndCharacterFor(result.range[0], this.ast);
    }
    if (childRange[1] > result.range[1]) {
      result.range[1] = childRange[1];
      result.loc.end = getLineAndCharacterFor(result.range[1], this.ast);
    }
  }

Converter.convertNode(node: TSNode, parent: TSNode): TSESTree.Node | null

Converts a TypeScript node into an ESTree node. The core of the conversion logic: Identify and convert each relevant TypeScript SyntaxKind

Returns: undefined the converted ESTree node

Raw JSDoc
/**
   * Converts a TypeScript node into an ESTree node.
   * The core of the conversion logic:
   * Identify and convert each relevant TypeScript SyntaxKind
   * @returns the converted ESTree node
   */

Calls:

  • this.createNode
  • node.getStart
  • this.convertBodyExpressions
  • isThisInTypeQuery (from ./node-utils)
  • node.text.slice
  • this.convertChild
  • this.convertChildren
  • this.convertBindingNameWithTypeAnnotation
  • this.convertPattern
  • hasModifier (from ./node-utils)
  • this.convertParameters
  • this.convertTypeAnnotation
  • this.convertTypeParameters
  • this.fixExports
  • getDeclarationKind (from ./node-utils)
  • node.elements.map
  • node.properties.map
  • isComputedProperty (from ./node-utils)
  • this.converter
  • complex_call_33680
  • getTSNodeAccessibility (from ./node-utils)
  • getDecorators (from ./getModifiers)
  • this.convertMethodSignature
  • this.fixParentLocation
  • getLastModifier (from ./node-utils)
  • findNextToken (from ./node-utils)
  • node.getFirstToken
  • constructorToken.getText
  • constructorToken.getStart
  • node.name.getStart
  • this.ast.text.slice
  • this.#isValidEscape
  • node.templateSpans.forEach
  • result.expressions.push
  • result.quasis.push
  • this.convertTypeArguments
  • getModifiers (from ./getModifiers)
  • getLocFor (from ./node-utils)
  • getLineAndCharacterFor (from ./node-utils)
  • node.heritageClauses?.find
  • node.members.filter
  • this.#withDeprecatedAliasGetter
  • this.convertImportAttributes
  • getImportClausePhaseModifier (from ./getImportClausePhaseModifier)
  • result.specifiers.push
  • getTextForTokenKind (from ./node-utils)
  • this.#throwError
  • isComma (from ./node-utils)
  • getBinaryExpressionType (from ./node-utils)
  • this.convertChainExpression
  • complex_call_65018
  • node.getText
  • unescapeStringLiteralText (from ./node-utils)
  • Number
  • getRange (from ./node-utils)
  • rawValue // remove suffixn.slice(0, -1) //BigIntdoesn't accept numeric separator // andbigintproperty should not include numeric separator .replaceAll
  • BigInt
  • String
  • node.text.lastIndexOf
  • this.convertJSXTagName
  • node.getEnd
  • this.convertJSXNamespaceOrIdentifier
  • node.getFullStart
  • this.#withDeprecatedGetter
  • isOptional (from ./node-utils)
  • node.heritageClauses?.flatMap
  • heritageClause.types.map
  • token.getStart
  • node.attributes.elements.map
  • openBraceToken.getStart
  • complex_call_91633
  • ts.isStringLiteral
  • node.name.getEnd
  • ts.isModuleDeclaration
  • nextName.getStart
  • nextName.getEnd
  • result.types.push
  • this.deeplyCopy

Internal Comments:

// special case for `typeof this.foo` - TS emits an Identifier for `this`
// but we want to treat it as a ThisExpression for consistency
// typescript includes the `#` in the text (x2)
// Control Flow
// Choice
// expression is present in case only (x2)
// Exceptions
// Loops
/**
       * Unlike other parsers, TypeScript calls a "DoWhileStatement"
       * a "DoStatement"
       */
// Declarations
// declare implies no body due to the invariant above (x2)
/**
         * Semantically, decorators are not allowed on variable declarations,
         * Pre 4.8 TS would include them in the AST, so we did as well.
         * However as of 4.8 TS no longer includes it (as it is, well, invalid).
         *
         * So for consistency across versions, we no longer include it either.
         */
// mostly for for-of, for-in
// Expressions
// TypeScript uses ArrayLiteralExpression in destructuring assignment, too
// TypeScript uses ObjectLiteralExpression in destructuring assignment, too
// otherwise, it is a non-type accessor - intentional fallthrough
// class (x2)
/**
           * Unlike in object literal methods, class method params can have decorators
           */ (x4)
/**
           * TypeScript class methods can be defined as "abstract"
           */ (x2)
// TypeScript uses this even for static methods named "constructor"
// occurs with missing array elements like [,]
// Template Literals
// Patterns
// AssignmentPattern should not contain modifiers in range (x5)
// Classes
// Modules
// Unary Operations
/**
         * ESTree uses UpdateExpression for ++/--
         */
// Guaranteed to be true in a valid AST (and asserted above) but (x2)
// technically could be false at runtime with allowInvalidAST: true. (x2)
// Binary Operations
// TypeScript uses BinaryExpression for sequences as well
// import(...);
// import.defer(...);
// NOTE - NewExpression cannot have an optional chain in it
// TODO: do we really want to convert it to Token? (x4)
// Literals
// JSX
/**
           * Convert SyntaxKind.JsxSelfClosingElement to SyntaxKind.JsxOpeningElement,
           * TypeScript does not seem to have the idea of openingElement when tag is self-closing
           */ (x2)
// TypeScript specific
// `InterfaceDeclaration` can only have `extends`, (x4)
// already checked in `check-syntax-errors.ts`, (x4)
// However, `allowInvalidAST` allow to bypass the syntax check, (x4)
// We'll just ignore clauses after other keywords (x4)
/**
         * Specific fix for type-guard location data
         */
// Since TS 5.9, there could be a trailing comma, i.e. `{ with: { ... }, }` (x2)
// Nested module declarations are stored in TypeScript as nested tree nodes. (x2)
// We "unravel" them here by making our own nested TSQualifiedName, (x2)
// with the innermost node's body as the actual node body. (x2)
// eslint-disable-next-line @typescript-eslint/no-deprecated (x5)
// TypeScript specific types
// 4.0 started nesting null types inside a LiteralType node
// but our AST is designed around the old way of null being a keyword
// Tuple
// adjust the start to account for the "..." (x5)
// Template Literal Types

Code
private convertNode(node: TSNode, parent: TSNode): TSESTree.Node | null {
    switch (node.kind) {
      case SyntaxKind.SourceFile: {
        return this.createNode<TSESTree.Program>(node, {
          type: AST_NODE_TYPES.Program,
          range: [node.getStart(this.ast), node.endOfFileToken.end],
          body: this.convertBodyExpressions(node.statements, node),
          comments: undefined,
          sourceType: node.externalModuleIndicator ? 'module' : 'script',
          tokens: undefined,
        });
      }

      case SyntaxKind.Block: {
        return this.createNode<TSESTree.BlockStatement>(node, {
          type: AST_NODE_TYPES.BlockStatement,
          body: this.convertBodyExpressions(node.statements, node),
        });
      }

      case SyntaxKind.Identifier: {
        if (isThisInTypeQuery(node)) {
          // special case for `typeof this.foo` - TS emits an Identifier for `this`
          // but we want to treat it as a ThisExpression for consistency
          return this.createNode<TSESTree.ThisExpression>(node, {
            type: AST_NODE_TYPES.ThisExpression,
          });
        }
        return this.createNode<TSESTree.Identifier>(node, {
          type: AST_NODE_TYPES.Identifier,
          decorators: [],
          name: node.text,
          optional: false,
          typeAnnotation: undefined,
        });
      }

      case SyntaxKind.PrivateIdentifier: {
        return this.createNode<TSESTree.PrivateIdentifier>(node, {
          type: AST_NODE_TYPES.PrivateIdentifier,
          // typescript includes the `#` in the text
          name: node.text.slice(1),
        });
      }

      case SyntaxKind.WithStatement:
        return this.createNode<TSESTree.WithStatement>(node, {
          type: AST_NODE_TYPES.WithStatement,
          body: this.convertChild(node.statement),
          object: this.convertChild(node.expression),
        });

      // Control Flow

      case SyntaxKind.ReturnStatement:
        return this.createNode<TSESTree.ReturnStatement>(node, {
          type: AST_NODE_TYPES.ReturnStatement,
          argument: this.convertChild(node.expression),
        });

      case SyntaxKind.LabeledStatement:
        return this.createNode<TSESTree.LabeledStatement>(node, {
          type: AST_NODE_TYPES.LabeledStatement,
          body: this.convertChild(node.statement),
          label: this.convertChild(node.label),
        });

      case SyntaxKind.ContinueStatement:
        return this.createNode<TSESTree.ContinueStatement>(node, {
          type: AST_NODE_TYPES.ContinueStatement,
          label: this.convertChild(node.label),
        });

      case SyntaxKind.BreakStatement:
        return this.createNode<TSESTree.BreakStatement>(node, {
          type: AST_NODE_TYPES.BreakStatement,
          label: this.convertChild(node.label),
        });

      // Choice

      case SyntaxKind.IfStatement:
        return this.createNode<TSESTree.IfStatement>(node, {
          type: AST_NODE_TYPES.IfStatement,
          alternate: this.convertChild(node.elseStatement),
          consequent: this.convertChild(node.thenStatement),
          test: this.convertChild(node.expression),
        });

      case SyntaxKind.SwitchStatement:
        return this.createNode<TSESTree.SwitchStatement>(node, {
          type: AST_NODE_TYPES.SwitchStatement,
          cases: this.convertChildren(node.caseBlock.clauses),
          discriminant: this.convertChild(node.expression),
        });

      case SyntaxKind.CaseClause:
      case SyntaxKind.DefaultClause:
        return this.createNode<TSESTree.SwitchCase>(node, {
          type: AST_NODE_TYPES.SwitchCase,
          // expression is present in case only
          consequent: this.convertChildren(node.statements),
          test:
            node.kind === SyntaxKind.CaseClause
              ? this.convertChild(node.expression)
              : null,
        });

      // Exceptions

      case SyntaxKind.ThrowStatement:
        return this.createNode<TSESTree.ThrowStatement>(node, {
          type: AST_NODE_TYPES.ThrowStatement,
          argument: this.convertChild(node.expression),
        });

      case SyntaxKind.TryStatement:
        return this.createNode<TSESTree.TryStatement>(node, {
          type: AST_NODE_TYPES.TryStatement,
          block: this.convertChild(node.tryBlock),
          finalizer: this.convertChild(node.finallyBlock),
          handler: this.convertChild(node.catchClause),
        });

      case SyntaxKind.CatchClause:
        return this.createNode<TSESTree.CatchClause>(node, {
          type: AST_NODE_TYPES.CatchClause,
          body: this.convertChild(node.block),
          param: node.variableDeclaration
            ? this.convertBindingNameWithTypeAnnotation(
                node.variableDeclaration.name,
                node.variableDeclaration.type,
              )
            : null,
        });

      // Loops

      case SyntaxKind.WhileStatement:
        return this.createNode<TSESTree.WhileStatement>(node, {
          type: AST_NODE_TYPES.WhileStatement,
          body: this.convertChild(node.statement),
          test: this.convertChild(node.expression),
        });

      /**
       * Unlike other parsers, TypeScript calls a "DoWhileStatement"
       * a "DoStatement"
       */
      case SyntaxKind.DoStatement:
        return this.createNode<TSESTree.DoWhileStatement>(node, {
          type: AST_NODE_TYPES.DoWhileStatement,
          body: this.convertChild(node.statement),
          test: this.convertChild(node.expression),
        });

      case SyntaxKind.ForStatement:
        return this.createNode<TSESTree.ForStatement>(node, {
          type: AST_NODE_TYPES.ForStatement,
          body: this.convertChild(node.statement),
          init: this.convertChild(node.initializer),
          test: this.convertChild(node.condition),
          update: this.convertChild(node.incrementor),
        });

      case SyntaxKind.ForInStatement:
        return this.createNode<TSESTree.ForInStatement>(node, {
          type: AST_NODE_TYPES.ForInStatement,
          body: this.convertChild(node.statement),
          left: this.convertPattern(node.initializer),
          right: this.convertChild(node.expression),
        });

      case SyntaxKind.ForOfStatement: {
        return this.createNode<TSESTree.ForOfStatement>(node, {
          type: AST_NODE_TYPES.ForOfStatement,
          await: node.awaitModifier?.kind === SyntaxKind.AwaitKeyword,
          body: this.convertChild(node.statement),
          left: this.convertPattern(node.initializer),
          right: this.convertChild(node.expression),
        });
      }

      // Declarations

      case SyntaxKind.FunctionDeclaration: {
        const isDeclare = hasModifier(SyntaxKind.DeclareKeyword, node);
        const isAsync = hasModifier(SyntaxKind.AsyncKeyword, node);
        const isGenerator = !!node.asteriskToken;

        const result = this.createNode<
          TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction
        >(node, {
          // declare implies no body due to the invariant above
          type: !node.body
            ? AST_NODE_TYPES.TSDeclareFunction
            : AST_NODE_TYPES.FunctionDeclaration,
          async: isAsync,
          body: this.convertChild(node.body) || undefined,
          declare: isDeclare,
          expression: false,
          generator: isGenerator,
          id: this.convertChild(node.name),
          params: this.convertParameters(node.parameters),
          returnType: node.type && this.convertTypeAnnotation(node.type, node),
          typeParameters: this.convertTypeParameters(node),
        });

        return this.fixExports(node, result);
      }

      case SyntaxKind.VariableDeclaration: {
        const hasExclamationToken = !!node.exclamationToken;
        const init = this.convertChild(node.initializer);
        const id = this.convertBindingNameWithTypeAnnotation(
          node.name,
          node.type,
          node,
        );
        return this.createNode<TSESTree.VariableDeclarator>(node, {
          type: AST_NODE_TYPES.VariableDeclarator,
          definite: hasExclamationToken,
          id,
          init,
        });
      }

      case SyntaxKind.VariableStatement: {
        const declarations = node.declarationList.declarations;
        const result = this.createNode<TSESTree.VariableDeclaration>(node, {
          type: AST_NODE_TYPES.VariableDeclaration,
          declarations: this.convertChildren(declarations),
          declare: hasModifier(SyntaxKind.DeclareKeyword, node),
          kind: getDeclarationKind(node.declarationList),
        });

        /**
         * Semantically, decorators are not allowed on variable declarations,
         * Pre 4.8 TS would include them in the AST, so we did as well.
         * However as of 4.8 TS no longer includes it (as it is, well, invalid).
         *
         * So for consistency across versions, we no longer include it either.
         */
        return this.fixExports(node, result);
      }

      // mostly for for-of, for-in
      case SyntaxKind.VariableDeclarationList: {
        return this.createNode<TSESTree.VariableDeclaration>(node, {
          type: AST_NODE_TYPES.VariableDeclaration,
          declarations: this.convertChildren(node.declarations),
          declare: false,
          kind: getDeclarationKind(node),
        });
      }

      // Expressions

      case SyntaxKind.ExpressionStatement:
        return this.createNode<TSESTree.ExpressionStatement>(node, {
          type: AST_NODE_TYPES.ExpressionStatement,
          directive: undefined,
          expression: this.convertChild(node.expression),
        });

      case SyntaxKind.ThisKeyword:
        return this.createNode<TSESTree.ThisExpression>(node, {
          type: AST_NODE_TYPES.ThisExpression,
        });

      case SyntaxKind.ArrayLiteralExpression: {
        // TypeScript uses ArrayLiteralExpression in destructuring assignment, too
        if (this.allowPattern) {
          return this.createNode<TSESTree.ArrayPattern>(node, {
            type: AST_NODE_TYPES.ArrayPattern,
            decorators: [],
            elements: node.elements.map(el => this.convertPattern(el)),
            optional: false,
            typeAnnotation: undefined,
          });
        }
        return this.createNode<TSESTree.ArrayExpression>(node, {
          type: AST_NODE_TYPES.ArrayExpression,
          elements: this.convertChildren(node.elements),
        });
      }

      case SyntaxKind.ObjectLiteralExpression: {
        // TypeScript uses ObjectLiteralExpression in destructuring assignment, too
        if (this.allowPattern) {
          return this.createNode<TSESTree.ObjectPattern>(node, {
            type: AST_NODE_TYPES.ObjectPattern,
            decorators: [],
            optional: false,
            properties: node.properties.map(el => this.convertPattern(el)),
            typeAnnotation: undefined,
          });
        }

        return this.createNode<TSESTree.ObjectExpression>(node, {
          type: AST_NODE_TYPES.ObjectExpression,
          properties: this.convertChildren(node.properties),
        });
      }

      case SyntaxKind.PropertyAssignment: {
        return this.createNode<TSESTree.Property>(node, {
          type: AST_NODE_TYPES.Property,
          computed: isComputedProperty(node.name),
          key: this.convertChild(node.name),
          kind: 'init',
          method: false,
          optional: false,
          shorthand: false,
          value: this.converter(node.initializer, node, this.allowPattern),
        });
      }

      case SyntaxKind.ShorthandPropertyAssignment: {
        if (node.objectAssignmentInitializer) {
          return this.createNode<TSESTree.Property>(node, {
            type: AST_NODE_TYPES.Property,
            computed: false,
            key: this.convertChild(node.name),
            kind: 'init',
            method: false,
            optional: false,
            shorthand: true,
            value: this.createNode<TSESTree.AssignmentPattern>(node, {
              type: AST_NODE_TYPES.AssignmentPattern,
              decorators: [],
              left: this.convertPattern(node.name),
              optional: false,
              right: this.convertChild(node.objectAssignmentInitializer),
              typeAnnotation: undefined,
            }),
          });
        }
        return this.createNode<TSESTree.Property>(node, {
          type: AST_NODE_TYPES.Property,
          computed: false,
          key: this.convertChild(node.name),
          kind: 'init',
          method: false,
          optional: false,
          shorthand: true,
          value: this.convertChild(node.name),
        });
      }

      case SyntaxKind.ComputedPropertyName:
        return this.convertChild(node.expression);

      case SyntaxKind.PropertyDeclaration: {
        const isAbstract = hasModifier(SyntaxKind.AbstractKeyword, node);
        const isAccessor = hasModifier(SyntaxKind.AccessorKeyword, node);
        const type = (() => {
          if (isAccessor) {
            if (isAbstract) {
              return AST_NODE_TYPES.TSAbstractAccessorProperty;
            }
            return AST_NODE_TYPES.AccessorProperty;
          }

          if (isAbstract) {
            return AST_NODE_TYPES.TSAbstractPropertyDefinition;
          }
          return AST_NODE_TYPES.PropertyDefinition;
        })();

        const key = this.convertChild(node.name);

        return this.createNode<
          | TSESTree.AccessorProperty
          | TSESTree.PropertyDefinition
          | TSESTree.TSAbstractAccessorProperty
          | TSESTree.TSAbstractPropertyDefinition
        >(node, {
          type,
          accessibility: getTSNodeAccessibility(node),
          computed: isComputedProperty(node.name),
          declare: hasModifier(SyntaxKind.DeclareKeyword, node),
          decorators: this.convertChildren(getDecorators(node) ?? []),
          definite: !!node.exclamationToken,
          key,
          optional:
            (key.type === AST_NODE_TYPES.Literal ||
              node.name.kind === SyntaxKind.Identifier ||
              node.name.kind === SyntaxKind.ComputedPropertyName ||
              node.name.kind === SyntaxKind.PrivateIdentifier) &&
            !!node.questionToken,

          override: hasModifier(SyntaxKind.OverrideKeyword, node),
          readonly: hasModifier(SyntaxKind.ReadonlyKeyword, node),
          static: hasModifier(SyntaxKind.StaticKeyword, node),
          typeAnnotation:
            node.type && this.convertTypeAnnotation(node.type, node),
          value: isAbstract ? null : this.convertChild(node.initializer),
        });
      }

      case SyntaxKind.GetAccessor:
      case SyntaxKind.SetAccessor: {
        if (
          node.parent.kind === SyntaxKind.InterfaceDeclaration ||
          node.parent.kind === SyntaxKind.TypeLiteral
        ) {
          return this.convertMethodSignature(node);
        }
      }
      // otherwise, it is a non-type accessor - intentional fallthrough
      case SyntaxKind.MethodDeclaration: {
        const method = this.createNode<
          TSESTree.FunctionExpression | TSESTree.TSEmptyBodyFunctionExpression
        >(node, {
          type: !node.body
            ? AST_NODE_TYPES.TSEmptyBodyFunctionExpression
            : AST_NODE_TYPES.FunctionExpression,
          range: [node.parameters.pos - 1, node.end],
          async: hasModifier(SyntaxKind.AsyncKeyword, node),
          body: this.convertChild(node.body),
          declare: false,
          expression: false, // ESTreeNode as ESTreeNode here
          generator: !!node.asteriskToken,
          id: null,
          params: [],
          returnType: node.type && this.convertTypeAnnotation(node.type, node),
          typeParameters: this.convertTypeParameters(node),
        });

        if (method.typeParameters) {
          this.fixParentLocation(method, method.typeParameters.range);
        }

        let result:
          | TSESTree.MethodDefinition
          | TSESTree.Property
          | TSESTree.TSAbstractMethodDefinition;

        if (parent.kind === SyntaxKind.ObjectLiteralExpression) {
          method.params = this.convertChildren(node.parameters);

          result = this.createNode<TSESTree.Property>(node, {
            type: AST_NODE_TYPES.Property,
            computed: isComputedProperty(node.name),
            key: this.convertChild(node.name),
            kind: 'init',
            method: node.kind === SyntaxKind.MethodDeclaration,
            optional: !!node.questionToken,
            shorthand: false,
            value: method,
          });
        } else {
          // class

          const isAbstract = hasModifier(SyntaxKind.AbstractKeyword, node);

          /**
           * Unlike in object literal methods, class method params can have decorators
           */
          method.params = this.convertParameters(node.parameters);

          /**
           * TypeScript class methods can be defined as "abstract"
           */
          const methodDefinitionType = isAbstract
            ? AST_NODE_TYPES.TSAbstractMethodDefinition
            : AST_NODE_TYPES.MethodDefinition;

          result = this.createNode<
            TSESTree.MethodDefinition | TSESTree.TSAbstractMethodDefinition
          >(node, {
            type: methodDefinitionType,
            accessibility: getTSNodeAccessibility(node),
            computed: isComputedProperty(node.name),
            decorators: this.convertChildren(getDecorators(node) ?? []),
            key: this.convertChild(node.name),
            kind: 'method',
            optional: !!node.questionToken,
            override: hasModifier(SyntaxKind.OverrideKeyword, node),
            static: hasModifier(SyntaxKind.StaticKeyword, node),
            value: method,
          });
        }

        if (node.kind === SyntaxKind.GetAccessor) {
          result.kind = 'get';
        } else if (node.kind === SyntaxKind.SetAccessor) {
          result.kind = 'set';
        } else if (
          !(result as TSESTree.MethodDefinition).static &&
          node.name.kind === SyntaxKind.StringLiteral &&
          node.name.text === 'constructor' &&
          result.type !== AST_NODE_TYPES.Property
        ) {
          result.kind = 'constructor';
        }
        return result;
      }

      // TypeScript uses this even for static methods named "constructor"
      case SyntaxKind.Constructor: {
        const lastModifier = getLastModifier(node);
        const constructorToken =
          (lastModifier && findNextToken(lastModifier, node, this.ast)) ??
          node.getFirstToken()!;

        const constructor = this.createNode<
          TSESTree.FunctionExpression | TSESTree.TSEmptyBodyFunctionExpression
        >(node, {
          type: !node.body
            ? AST_NODE_TYPES.TSEmptyBodyFunctionExpression
            : AST_NODE_TYPES.FunctionExpression,
          range: [node.parameters.pos - 1, node.end],
          async: false,
          body: this.convertChild(node.body),
          declare: false,
          expression: false, // is not present in ESTreeNode
          generator: false,
          id: null,
          params: this.convertParameters(node.parameters),
          returnType: node.type && this.convertTypeAnnotation(node.type, node),
          typeParameters: this.convertTypeParameters(node),
        });

        if (constructor.typeParameters) {
          this.fixParentLocation(constructor, constructor.typeParameters.range);
        }

        const constructorKey =
          constructorToken.kind === SyntaxKind.StringLiteral
            ? this.createNode<TSESTree.StringLiteral>(constructorToken, {
                type: AST_NODE_TYPES.Literal,
                raw: constructorToken.getText(),
                value: 'constructor',
              })
            : this.createNode<TSESTree.Identifier>(node, {
                type: AST_NODE_TYPES.Identifier,
                range: [
                  constructorToken.getStart(this.ast),
                  constructorToken.end,
                ],
                decorators: [],
                name: 'constructor',
                optional: false,
                typeAnnotation: undefined,
              });

        const isStatic = hasModifier(SyntaxKind.StaticKeyword, node);

        return this.createNode<
          TSESTree.MethodDefinition | TSESTree.TSAbstractMethodDefinition
        >(node, {
          type: hasModifier(SyntaxKind.AbstractKeyword, node)
            ? AST_NODE_TYPES.TSAbstractMethodDefinition
            : AST_NODE_TYPES.MethodDefinition,
          accessibility: getTSNodeAccessibility(node),
          computed: false,
          decorators: [],
          key: constructorKey,
          kind: isStatic ? 'method' : 'constructor',
          optional: false,
          override: false,
          static: isStatic,
          value: constructor,
        });
      }

      case SyntaxKind.FunctionExpression: {
        return this.createNode<TSESTree.FunctionExpression>(node, {
          type: AST_NODE_TYPES.FunctionExpression,
          async: hasModifier(SyntaxKind.AsyncKeyword, node),
          body: this.convertChild(node.body),
          declare: false,
          expression: false,
          generator: !!node.asteriskToken,
          id: this.convertChild(node.name),
          params: this.convertParameters(node.parameters),
          returnType: node.type && this.convertTypeAnnotation(node.type, node),
          typeParameters: this.convertTypeParameters(node),
        });
      }

      case SyntaxKind.SuperKeyword:
        return this.createNode<TSESTree.Super>(node, {
          type: AST_NODE_TYPES.Super,
        });

      case SyntaxKind.ArrayBindingPattern:
        return this.createNode<TSESTree.ArrayPattern>(node, {
          type: AST_NODE_TYPES.ArrayPattern,
          decorators: [],
          elements: node.elements.map(el => this.convertPattern(el)),
          optional: false,
          typeAnnotation: undefined,
        });

      // occurs with missing array elements like [,]
      case SyntaxKind.OmittedExpression:
        return null;

      case SyntaxKind.ObjectBindingPattern:
        return this.createNode<TSESTree.ObjectPattern>(node, {
          type: AST_NODE_TYPES.ObjectPattern,
          decorators: [],
          optional: false,
          properties: node.elements.map(el => this.convertPattern(el)),
          typeAnnotation: undefined,
        });

      case SyntaxKind.BindingElement: {
        if (parent.kind === SyntaxKind.ArrayBindingPattern) {
          const arrayItem = this.convertChild(node.name, parent);

          if (node.initializer) {
            return this.createNode<TSESTree.AssignmentPattern>(node, {
              type: AST_NODE_TYPES.AssignmentPattern,
              decorators: [],
              left: arrayItem,
              optional: false,
              right: this.convertChild(node.initializer),
              typeAnnotation: undefined,
            });
          }

          if (node.dotDotDotToken) {
            return this.createNode<TSESTree.RestElement>(node, {
              type: AST_NODE_TYPES.RestElement,
              argument: arrayItem,
              decorators: [],
              optional: false,
              typeAnnotation: undefined,
              value: undefined,
            });
          }
          return arrayItem;
        }
        let result: TSESTree.Property | TSESTree.RestElement;
        if (node.dotDotDotToken) {
          result = this.createNode<TSESTree.RestElement>(node, {
            type: AST_NODE_TYPES.RestElement,
            argument: this.convertChild(node.propertyName ?? node.name),
            decorators: [],
            optional: false,
            typeAnnotation: undefined,
            value: undefined,
          });
        } else {
          result = this.createNode<TSESTree.Property>(node, {
            type: AST_NODE_TYPES.Property,
            computed:
              node.propertyName?.kind === SyntaxKind.ComputedPropertyName,
            key: this.convertChild(node.propertyName ?? node.name),
            kind: 'init',
            method: false,
            optional: false,
            shorthand: !node.propertyName,
            value: this.convertChild(node.name),
          });
        }

        if (node.initializer) {
          result.value = this.createNode<TSESTree.AssignmentPattern>(node, {
            type: AST_NODE_TYPES.AssignmentPattern,
            range: [node.name.getStart(this.ast), node.initializer.end],
            decorators: [],
            left: this.convertChild(node.name),
            optional: false,
            right: this.convertChild(node.initializer),
            typeAnnotation: undefined,
          });
        }
        return result;
      }

      case SyntaxKind.ArrowFunction: {
        return this.createNode<TSESTree.ArrowFunctionExpression>(node, {
          type: AST_NODE_TYPES.ArrowFunctionExpression,
          async: hasModifier(SyntaxKind.AsyncKeyword, node),
          body: this.convertChild(node.body),
          expression: node.body.kind !== SyntaxKind.Block,
          generator: false,
          id: null,
          params: this.convertParameters(node.parameters),
          returnType: node.type && this.convertTypeAnnotation(node.type, node),
          typeParameters: this.convertTypeParameters(node),
        });
      }

      case SyntaxKind.YieldExpression:
        return this.createNode<TSESTree.YieldExpression>(node, {
          type: AST_NODE_TYPES.YieldExpression,
          argument: this.convertChild(node.expression),
          delegate: !!node.asteriskToken,
        });

      case SyntaxKind.AwaitExpression:
        return this.createNode<TSESTree.AwaitExpression>(node, {
          type: AST_NODE_TYPES.AwaitExpression,
          argument: this.convertChild(node.expression),
        });

      // Template Literals

      case SyntaxKind.NoSubstitutionTemplateLiteral: {
        const rawText = this.ast.text.slice(
          node.getStart(this.ast) + 1,
          node.end - 1,
        );

        return this.createNode<TSESTree.TemplateLiteral>(node, {
          type: AST_NODE_TYPES.TemplateLiteral,
          expressions: [],
          quasis: [
            this.createNode<TSESTree.TemplateElement>(node, {
              type: AST_NODE_TYPES.TemplateElement,
              tail: true,
              value: {
                cooked:
                  node.parent.kind === SyntaxKind.TaggedTemplateExpression &&
                  !this.#isValidEscape(rawText)
                    ? null
                    : node.text,
                raw: rawText,
              },
            }),
          ],
        });
      }

      case SyntaxKind.TemplateExpression: {
        const result = this.createNode<TSESTree.TemplateLiteral>(node, {
          type: AST_NODE_TYPES.TemplateLiteral,
          expressions: [],
          quasis: [this.convertChild(node.head)],
        });

        node.templateSpans.forEach(templateSpan => {
          result.expressions.push(
            this.convertChild(templateSpan.expression) as TSESTree.Expression,
          );
          result.quasis.push(
            this.convertChild(templateSpan.literal) as TSESTree.TemplateElement,
          );
        });
        return result;
      }

      case SyntaxKind.TaggedTemplateExpression: {
        return this.createNode<TSESTree.TaggedTemplateExpression>(node, {
          type: AST_NODE_TYPES.TaggedTemplateExpression,
          quasi: this.convertChild(node.template),
          tag: this.convertChild(node.tag),
          typeArguments: this.convertTypeArguments(node),
        });
      }

      case SyntaxKind.TemplateHead:
      case SyntaxKind.TemplateMiddle:
      case SyntaxKind.TemplateTail: {
        const tail = node.kind === SyntaxKind.TemplateTail;
        const rawText = this.ast.text.slice(
          node.getStart(this.ast) + 1,
          node.end - (tail ? 1 : 2),
        );
        const isTagged =
          node.kind === SyntaxKind.TemplateHead
            ? node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression
            : node.parent.parent.parent.kind ===
              SyntaxKind.TaggedTemplateExpression;

        return this.createNode<TSESTree.TemplateElement>(node, {
          type: AST_NODE_TYPES.TemplateElement,
          tail,
          value: {
            cooked:
              isTagged && !this.#isValidEscape(rawText) ? null : node.text,
            raw: rawText,
          },
        });
      }

      // Patterns

      case SyntaxKind.SpreadAssignment:
      case SyntaxKind.SpreadElement: {
        if (this.allowPattern) {
          return this.createNode<TSESTree.RestElement>(node, {
            type: AST_NODE_TYPES.RestElement,
            argument: this.convertPattern(node.expression),
            decorators: [],
            optional: false,
            typeAnnotation: undefined,
            value: undefined,
          });
        }
        return this.createNode<TSESTree.SpreadElement>(node, {
          type: AST_NODE_TYPES.SpreadElement,
          argument: this.convertChild(node.expression),
        });
      }

      case SyntaxKind.Parameter: {
        let parameter: TSESTree.BindingName | TSESTree.RestElement;
        let result: TSESTree.AssignmentPattern | TSESTree.RestElement;

        if (node.dotDotDotToken) {
          parameter = result = this.createNode<TSESTree.RestElement>(node, {
            type: AST_NODE_TYPES.RestElement,
            argument: this.convertChild(node.name),
            decorators: [],
            optional: false,
            typeAnnotation: undefined,
            value: undefined,
          });
        } else if (node.initializer) {
          parameter = this.convertChild(node.name) as TSESTree.BindingName;
          result = this.createNode<TSESTree.AssignmentPattern>(node, {
            type: AST_NODE_TYPES.AssignmentPattern,
            range: [node.name.getStart(this.ast), node.initializer.end],
            decorators: [],
            left: parameter,
            optional: false,
            right: this.convertChild(node.initializer),
            typeAnnotation: undefined,
          });

          const modifiers = getModifiers(node);
          if (modifiers) {
            // AssignmentPattern should not contain modifiers in range
            result.range[0] = parameter.range[0];
            result.loc = getLocFor(result.range, this.ast);
          }
        } else {
          parameter = result = this.convertChild(node.name, parent);
        }

        if (node.type) {
          parameter.typeAnnotation = this.convertTypeAnnotation(
            node.type,
            node,
          );
          this.fixParentLocation(parameter, parameter.typeAnnotation.range);
        }

        if (node.questionToken) {
          if (node.questionToken.end > parameter.range[1]) {
            parameter.range[1] = node.questionToken.end;
            parameter.loc.end = getLineAndCharacterFor(
              parameter.range[1],
              this.ast,
            );
          }
          parameter.optional = true;
        }

        const modifiers = getModifiers(node);
        if (modifiers) {
          return this.createNode<TSESTree.TSParameterProperty>(node, {
            type: AST_NODE_TYPES.TSParameterProperty,
            accessibility: getTSNodeAccessibility(node),
            decorators: [],
            override: hasModifier(SyntaxKind.OverrideKeyword, node),
            parameter: result as TSESTree.TSParameterProperty['parameter'],
            readonly: hasModifier(SyntaxKind.ReadonlyKeyword, node),
            static: hasModifier(SyntaxKind.StaticKeyword, node),
          });
        }
        return result;
      }

      // Classes

      case SyntaxKind.ClassDeclaration:
      case SyntaxKind.ClassExpression: {
        const classNodeType =
          node.kind === SyntaxKind.ClassDeclaration
            ? AST_NODE_TYPES.ClassDeclaration
            : AST_NODE_TYPES.ClassExpression;
        const extendsClause: ts.HeritageClause | undefined =
          node.heritageClauses?.find(
            heritageClause =>
              heritageClause.token === SyntaxKind.ExtendsKeyword,
          );
        const implementsClause: ts.HeritageClause | undefined =
          node.heritageClauses?.find(
            heritageClause =>
              heritageClause.token === SyntaxKind.ImplementsKeyword,
          );

        const result = this.createNode<
          TSESTree.ClassDeclaration | TSESTree.ClassExpression
        >(node, {
          type: classNodeType,
          abstract: hasModifier(SyntaxKind.AbstractKeyword, node),
          body: this.createNode<TSESTree.ClassBody>(node, {
            type: AST_NODE_TYPES.ClassBody,
            range: [node.members.pos - 1, node.end],
            body: this.convertChildren(
              node.members.filter(isESTreeClassMember),
            ),
          }),
          declare: hasModifier(SyntaxKind.DeclareKeyword, node),
          decorators: this.convertChildren(getDecorators(node) ?? []),
          id: this.convertChild(node.name),
          implements: this.convertChildren(implementsClause?.types ?? []),
          superClass: extendsClause?.types[0]
            ? this.convertChild(extendsClause.types[0].expression)
            : null,
          superTypeArguments: undefined,
          typeParameters: this.convertTypeParameters(node),
        });

        if (extendsClause?.types[0]?.typeArguments) {
          result.superTypeArguments = this.convertTypeArguments(
            extendsClause.types[0],
          );
        }

        return this.fixExports(node, result);
      }

      // Modules
      case SyntaxKind.ModuleBlock:
        return this.createNode<TSESTree.TSModuleBlock>(node, {
          type: AST_NODE_TYPES.TSModuleBlock,
          body: this.convertBodyExpressions(node.statements, node),
        });

      case SyntaxKind.ImportDeclaration: {
        const result = this.createNode<TSESTree.ImportDeclaration>(
          node,
          this.#withDeprecatedAliasGetter(
            {
              type: AST_NODE_TYPES.ImportDeclaration,
              attributes: this.convertImportAttributes(node),
              importKind: 'value',
              phase:
                getImportClausePhaseModifier(node.importClause) === 'defer'
                  ? 'defer'
                  : null,
              source: this.convertChild(node.moduleSpecifier),
              specifiers: [],
            },
            'assertions',
            'attributes',
            true,
          ),
        );

        if (node.importClause) {
          if (getImportClausePhaseModifier(node.importClause) === 'type') {
            result.importKind = 'type';
          }

          if (node.importClause.name) {
            result.specifiers.push(
              this.convertChild(node.importClause) as TSESTree.ImportClause,
            );
          }

          if (node.importClause.namedBindings) {
            switch (node.importClause.namedBindings.kind) {
              case SyntaxKind.NamespaceImport:
                result.specifiers.push(
                  this.convertChild(
                    node.importClause.namedBindings,
                  ) as TSESTree.ImportClause,
                );
                break;
              case SyntaxKind.NamedImports:
                result.specifiers.push(
                  ...(this.convertChildren(
                    node.importClause.namedBindings.elements,
                  ) as TSESTree.ImportClause[]),
                );
                break;
            }
          }
        }
        return result;
      }

      case SyntaxKind.NamespaceImport:
        return this.createNode<TSESTree.ImportNamespaceSpecifier>(node, {
          type: AST_NODE_TYPES.ImportNamespaceSpecifier,
          local: this.convertChild(node.name),
        });

      case SyntaxKind.ImportSpecifier:
        return this.createNode<TSESTree.ImportSpecifier>(node, {
          type: AST_NODE_TYPES.ImportSpecifier,
          imported: this.convertChild(node.propertyName ?? node.name),
          importKind: node.isTypeOnly ? 'type' : 'value',
          local: this.convertChild(node.name),
        });

      case SyntaxKind.ImportClause: {
        const local = this.convertChild(node.name);
        return this.createNode<TSESTree.ImportDefaultSpecifier>(node, {
          type: AST_NODE_TYPES.ImportDefaultSpecifier,
          range: local.range,
          local,
        });
      }

      case SyntaxKind.ExportDeclaration: {
        if (node.exportClause?.kind === SyntaxKind.NamedExports) {
          return this.createNode<TSESTree.ExportNamedDeclaration>(
            node,
            this.#withDeprecatedAliasGetter(
              {
                type: AST_NODE_TYPES.ExportNamedDeclaration,
                attributes: this.convertImportAttributes(node),
                declaration: null,
                exportKind: node.isTypeOnly ? 'type' : 'value',
                source: this.convertChild(node.moduleSpecifier),
                specifiers: this.convertChildren(
                  node.exportClause.elements,
                  node,
                ),
              },
              'assertions',
              'attributes',
              true,
            ),
          );
        }
        return this.createNode<TSESTree.ExportAllDeclaration>(
          node,
          this.#withDeprecatedAliasGetter(
            {
              type: AST_NODE_TYPES.ExportAllDeclaration,
              attributes: this.convertImportAttributes(node),
              exported:
                node.exportClause?.kind === SyntaxKind.NamespaceExport
                  ? this.convertChild(node.exportClause.name)
                  : null,
              exportKind: node.isTypeOnly ? 'type' : 'value',
              source: this.convertChild(node.moduleSpecifier),
            },
            'assertions',
            'attributes',
            true,
          ),
        );
      }

      case SyntaxKind.ExportSpecifier: {
        const local = node.propertyName ?? node.name;
        return this.createNode<TSESTree.ExportSpecifier>(node, {
          type: AST_NODE_TYPES.ExportSpecifier,
          exported: this.convertChild(node.name),
          exportKind: node.isTypeOnly ? 'type' : 'value',
          local: this.convertChild(local),
        });
      }

      case SyntaxKind.ExportAssignment:
        if (node.isExportEquals) {
          return this.createNode<TSESTree.TSExportAssignment>(node, {
            type: AST_NODE_TYPES.TSExportAssignment,
            expression: this.convertChild(node.expression),
          });
        }
        return this.createNode<TSESTree.ExportDefaultDeclaration>(node, {
          type: AST_NODE_TYPES.ExportDefaultDeclaration,
          declaration: this.convertChild(node.expression),
          exportKind: 'value',
        });

      // Unary Operations

      case SyntaxKind.PrefixUnaryExpression:
      case SyntaxKind.PostfixUnaryExpression: {
        const operator = getTextForTokenKind(node.operator);
        /**
         * ESTree uses UpdateExpression for ++/--
         */
        if (operator === '++' || operator === '--') {
          return this.createNode<TSESTree.UpdateExpression>(node, {
            type: AST_NODE_TYPES.UpdateExpression,
            argument: this.convertChild(node.operand),
            operator,
            prefix: node.kind === SyntaxKind.PrefixUnaryExpression,
          });
        }
        const isPrefixUnaryExpression =
          node.kind === SyntaxKind.PrefixUnaryExpression;
        if (!isPrefixUnaryExpression) {
          this.#throwError(
            node,
            `Unexpected PrefixUnaryExpression with operator ${operator}`,
          );
        }
        return this.createNode<TSESTree.UnaryExpression>(node, {
          type: AST_NODE_TYPES.UnaryExpression,
          argument: this.convertChild(node.operand),
          operator,
          // Guaranteed to be true in a valid AST (and asserted above) but
          // technically could be false at runtime with allowInvalidAST: true.
          prefix: isPrefixUnaryExpression as true,
        });
      }

      case SyntaxKind.DeleteExpression:
        return this.createNode<TSESTree.UnaryExpression>(node, {
          type: AST_NODE_TYPES.UnaryExpression,
          argument: this.convertChild(node.expression),
          operator: 'delete',
          prefix: true,
        });

      case SyntaxKind.VoidExpression:
        return this.createNode<TSESTree.UnaryExpression>(node, {
          type: AST_NODE_TYPES.UnaryExpression,
          argument: this.convertChild(node.expression),
          operator: 'void',
          prefix: true,
        });

      case SyntaxKind.TypeOfExpression:
        return this.createNode<TSESTree.UnaryExpression>(node, {
          type: AST_NODE_TYPES.UnaryExpression,
          argument: this.convertChild(node.expression),
          operator: 'typeof',
          prefix: true,
        });

      case SyntaxKind.TypeOperator:
        return this.createNode<TSESTree.TSTypeOperator>(node, {
          type: AST_NODE_TYPES.TSTypeOperator,
          operator: getTextForTokenKind(node.operator),
          typeAnnotation: this.convertChild(node.type),
        });

      // Binary Operations

      case SyntaxKind.BinaryExpression: {
        // TypeScript uses BinaryExpression for sequences as well
        if (isComma(node.operatorToken)) {
          const result = this.createNode<TSESTree.SequenceExpression>(node, {
            type: AST_NODE_TYPES.SequenceExpression,
            expressions: [],
          });

          const left = this.convertChild(node.left) as TSESTree.Expression;
          if (
            left.type === AST_NODE_TYPES.SequenceExpression &&
            node.left.kind !== SyntaxKind.ParenthesizedExpression
          ) {
            result.expressions.push(...left.expressions);
          } else {
            result.expressions.push(left);
          }

          result.expressions.push(
            this.convertChild(node.right) as TSESTree.Expression,
          );
          return result;
        }
        const expressionType = getBinaryExpressionType(node.operatorToken);
        if (
          this.allowPattern &&
          expressionType.type === AST_NODE_TYPES.AssignmentExpression
        ) {
          return this.createNode<TSESTree.AssignmentPattern>(node, {
            type: AST_NODE_TYPES.AssignmentPattern,
            decorators: [],
            left: this.convertPattern(node.left, node),
            optional: false,
            right: this.convertChild(node.right),
            typeAnnotation: undefined,
          });
        }
        return this.createNode<
          | TSESTree.AssignmentExpression
          | TSESTree.BinaryExpression
          | TSESTree.LogicalExpression
        >(node, {
          ...expressionType,
          left: this.converter(
            node.left,
            node,
            expressionType.type === AST_NODE_TYPES.AssignmentExpression,
          ),
          right: this.convertChild(node.right),
        });
      }

      case SyntaxKind.PropertyAccessExpression: {
        const object = this.convertChild(node.expression);
        const property = this.convertChild(node.name);
        const computed = false;

        const result = this.createNode<TSESTree.MemberExpression>(node, {
          type: AST_NODE_TYPES.MemberExpression,
          computed,
          object,
          optional: node.questionDotToken != null,
          property,
        });

        return this.convertChainExpression(result, node);
      }

      case SyntaxKind.ElementAccessExpression: {
        const object = this.convertChild(node.expression);
        const property = this.convertChild(node.argumentExpression);
        const computed = true;

        const result = this.createNode<TSESTree.MemberExpression>(node, {
          type: AST_NODE_TYPES.MemberExpression,
          computed,
          object,
          optional: node.questionDotToken != null,
          property,
        });

        return this.convertChainExpression(result, node);
      }

      case SyntaxKind.CallExpression: {
        const importExpressionParams:
          Pick<TSESTree.ImportExpression, 'phase'> | undefined = (() => {
          // import(...);
          if (node.expression.kind === SyntaxKind.ImportKeyword) {
            return {
              phase: null,
            };
          }
          // import.defer(...);
          if (node.expression.kind === SyntaxKind.MetaProperty) {
            const metaPropertyNode = node.expression as ts.MetaProperty;
            const metaObject = getTextForTokenKind(
              metaPropertyNode.keywordToken,
            );
            if (metaObject === 'import') {
              const metaPropertyName = metaPropertyNode.name.text;
              if (metaPropertyName === 'defer') {
                return {
                  phase: 'defer',
                };
              }
            }
          }

          return undefined;
        })();

        if (importExpressionParams != null) {
          return this.createNode<TSESTree.ImportExpression>(
            node,
            this.#withDeprecatedAliasGetter(
              {
                type: AST_NODE_TYPES.ImportExpression,
                options: node.arguments[1]
                  ? this.convertChild(node.arguments[1])
                  : null,
                phase: importExpressionParams.phase,
                source: this.convertChild(node.arguments[0]),
              },
              'attributes',
              'options',
              true,
            ),
          );
        }

        const callee = this.convertChild(node.expression);
        const args = this.convertChildren(node.arguments);
        const typeArguments = this.convertTypeArguments(node);

        const result = this.createNode<TSESTree.CallExpression>(node, {
          type: AST_NODE_TYPES.CallExpression,
          arguments: args,
          callee,
          optional: node.questionDotToken != null,
          typeArguments,
        });

        return this.convertChainExpression(result, node);
      }

      case SyntaxKind.NewExpression: {
        const typeArguments = this.convertTypeArguments(node);

        // NOTE - NewExpression cannot have an optional chain in it
        return this.createNode<TSESTree.NewExpression>(node, {
          type: AST_NODE_TYPES.NewExpression,
          arguments: this.convertChildren(node.arguments ?? []),
          callee: this.convertChild(node.expression),
          typeArguments,
        });
      }

      case SyntaxKind.ConditionalExpression:
        return this.createNode<TSESTree.ConditionalExpression>(node, {
          type: AST_NODE_TYPES.ConditionalExpression,
          alternate: this.convertChild(node.whenFalse),
          consequent: this.convertChild(node.whenTrue),
          test: this.convertChild(node.condition),
        });

      case SyntaxKind.MetaProperty: {
        return this.createNode<TSESTree.MetaProperty>(node, {
          type: AST_NODE_TYPES.MetaProperty,
          meta: this.createNode<TSESTree.Identifier>(
            // TODO: do we really want to convert it to Token?
            node.getFirstToken()!,
            {
              type: AST_NODE_TYPES.Identifier,
              decorators: [],
              name: getTextForTokenKind(node.keywordToken),
              optional: false,
              typeAnnotation: undefined,
            },
          ),
          property: this.convertChild(node.name),
        });
      }

      case SyntaxKind.Decorator: {
        return this.createNode<TSESTree.Decorator>(node, {
          type: AST_NODE_TYPES.Decorator,
          expression: this.convertChild(node.expression),
        });
      }

      // Literals

      case SyntaxKind.StringLiteral: {
        return this.createNode<TSESTree.StringLiteral>(node, {
          type: AST_NODE_TYPES.Literal,
          raw: node.getText(),
          value:
            parent.kind === SyntaxKind.JsxAttribute
              ? unescapeStringLiteralText(node.text)
              : node.text,
        });
      }

      case SyntaxKind.NumericLiteral: {
        return this.createNode<TSESTree.NumberLiteral>(node, {
          type: AST_NODE_TYPES.Literal,
          raw: node.getText(),
          value: Number(node.text),
        });
      }

      case SyntaxKind.BigIntLiteral: {
        const range = getRange(node, this.ast);
        const rawValue = this.ast.text.slice(range[0], range[1]);
        const bigint = rawValue
          // remove suffix `n`
          .slice(0, -1)
          // `BigInt` doesn't accept numeric separator
          // and `bigint` property should not include numeric separator
          .replaceAll('_', '');
        const value = typeof BigInt !== 'undefined' ? BigInt(bigint) : null;
        return this.createNode<TSESTree.BigIntLiteral>(node, {
          type: AST_NODE_TYPES.Literal,
          range,
          bigint: value == null ? bigint : String(value),
          raw: rawValue,
          value,
        });
      }

      case SyntaxKind.RegularExpressionLiteral: {
        const pattern = node.text.slice(1, node.text.lastIndexOf('/'));
        const flags = node.text.slice(node.text.lastIndexOf('/') + 1);

        let regex = null;
        try {
          regex = new RegExp(pattern, flags);
        } catch {
          // Intentionally blank, so regex stays null
        }

        return this.createNode<TSESTree.RegExpLiteral>(node, {
          type: AST_NODE_TYPES.Literal,
          raw: node.text,
          regex: {
            flags,
            pattern,
          },
          value: regex,
        });
      }

      case SyntaxKind.TrueKeyword:
        return this.createNode<TSESTree.BooleanLiteral>(node, {
          type: AST_NODE_TYPES.Literal,
          raw: 'true',
          value: true,
        });

      case SyntaxKind.FalseKeyword:
        return this.createNode<TSESTree.BooleanLiteral>(node, {
          type: AST_NODE_TYPES.Literal,
          raw: 'false',
          value: false,
        });

      case SyntaxKind.NullKeyword: {
        return this.createNode<TSESTree.NullLiteral>(node, {
          type: AST_NODE_TYPES.Literal,
          raw: 'null',
          value: null,
        });
      }

      case SyntaxKind.EmptyStatement:
        return this.createNode<TSESTree.EmptyStatement>(node, {
          type: AST_NODE_TYPES.EmptyStatement,
        });

      case SyntaxKind.DebuggerStatement:
        return this.createNode<TSESTree.DebuggerStatement>(node, {
          type: AST_NODE_TYPES.DebuggerStatement,
        });

      // JSX

      case SyntaxKind.JsxElement:
        return this.createNode<TSESTree.JSXElement>(node, {
          type: AST_NODE_TYPES.JSXElement,
          children: this.convertChildren(node.children),
          closingElement: this.convertChild(node.closingElement),
          openingElement: this.convertChild(node.openingElement),
        });

      case SyntaxKind.JsxFragment:
        return this.createNode<TSESTree.JSXFragment>(node, {
          type: AST_NODE_TYPES.JSXFragment,
          children: this.convertChildren(node.children),
          closingFragment: this.convertChild(node.closingFragment),
          openingFragment: this.convertChild(node.openingFragment),
        });

      case SyntaxKind.JsxSelfClosingElement: {
        return this.createNode<TSESTree.JSXElement>(node, {
          type: AST_NODE_TYPES.JSXElement,
          /**
           * Convert SyntaxKind.JsxSelfClosingElement to SyntaxKind.JsxOpeningElement,
           * TypeScript does not seem to have the idea of openingElement when tag is self-closing
           */
          children: [],
          closingElement: null,
          openingElement: this.createNode<TSESTree.JSXOpeningElement>(node, {
            type: AST_NODE_TYPES.JSXOpeningElement,
            range: getRange(node, this.ast),
            attributes: this.convertChildren(node.attributes.properties),
            name: this.convertJSXTagName(node.tagName, node),
            selfClosing: true,
            typeArguments: this.convertTypeArguments(node),
          }),
        });
      }

      case SyntaxKind.JsxOpeningElement: {
        return this.createNode<TSESTree.JSXOpeningElement>(node, {
          type: AST_NODE_TYPES.JSXOpeningElement,
          attributes: this.convertChildren(node.attributes.properties),
          name: this.convertJSXTagName(node.tagName, node),
          selfClosing: false,
          typeArguments: this.convertTypeArguments(node),
        });
      }

      case SyntaxKind.JsxClosingElement:
        return this.createNode<TSESTree.JSXClosingElement>(node, {
          type: AST_NODE_TYPES.JSXClosingElement,
          name: this.convertJSXTagName(node.tagName, node),
        });

      case SyntaxKind.JsxOpeningFragment:
        return this.createNode<TSESTree.JSXOpeningFragment>(node, {
          type: AST_NODE_TYPES.JSXOpeningFragment,
        });

      case SyntaxKind.JsxClosingFragment:
        return this.createNode<TSESTree.JSXClosingFragment>(node, {
          type: AST_NODE_TYPES.JSXClosingFragment,
        });

      case SyntaxKind.JsxExpression: {
        const expression = node.expression
          ? this.convertChild(node.expression)
          : this.createNode<TSESTree.JSXEmptyExpression>(node, {
              type: AST_NODE_TYPES.JSXEmptyExpression,
              range: [node.getStart(this.ast) + 1, node.getEnd() - 1],
            });

        if (node.dotDotDotToken) {
          return this.createNode<TSESTree.JSXSpreadChild>(node, {
            type: AST_NODE_TYPES.JSXSpreadChild,
            expression,
          });
        }
        return this.createNode<TSESTree.JSXExpressionContainer>(node, {
          type: AST_NODE_TYPES.JSXExpressionContainer,
          expression,
        });
      }

      case SyntaxKind.JsxAttribute: {
        return this.createNode<TSESTree.JSXAttribute>(node, {
          type: AST_NODE_TYPES.JSXAttribute,
          name: this.convertJSXNamespaceOrIdentifier(node.name),
          value: this.convertChild(node.initializer),
        });
      }

      case SyntaxKind.JsxText: {
        const start = node.getFullStart();
        const end = node.getEnd();
        const text = this.ast.text.slice(start, end);

        return this.createNode<TSESTree.JSXText>(node, {
          type: AST_NODE_TYPES.JSXText,
          range: [start, end],
          raw: text,
          value: unescapeStringLiteralText(text),
        });
      }

      case SyntaxKind.JsxSpreadAttribute:
        return this.createNode<TSESTree.JSXSpreadAttribute>(node, {
          type: AST_NODE_TYPES.JSXSpreadAttribute,
          argument: this.convertChild(node.expression),
        });

      case SyntaxKind.QualifiedName: {
        return this.createNode<TSESTree.TSQualifiedName>(node, {
          type: AST_NODE_TYPES.TSQualifiedName,
          left: this.convertChild(node.left),
          right: this.convertChild(node.right),
        });
      }

      // TypeScript specific

      case SyntaxKind.TypeReference:
        return this.createNode<TSESTree.TSTypeReference>(node, {
          type: AST_NODE_TYPES.TSTypeReference,
          typeArguments: this.convertTypeArguments(node),
          typeName: this.convertChild(node.typeName),
        });

      case SyntaxKind.TypeParameter: {
        return this.createNode<TSESTree.TSTypeParameter>(node, {
          type: AST_NODE_TYPES.TSTypeParameter,
          const: hasModifier(SyntaxKind.ConstKeyword, node),
          constraint: node.constraint && this.convertChild(node.constraint),
          default: node.default ? this.convertChild(node.default) : undefined,
          in: hasModifier(SyntaxKind.InKeyword, node),
          name: this.convertChild(node.name),
          out: hasModifier(SyntaxKind.OutKeyword, node),
        });
      }

      case SyntaxKind.ThisType:
        return this.createNode<TSESTree.TSThisType>(node, {
          type: AST_NODE_TYPES.TSThisType,
        });

      case SyntaxKind.AnyKeyword:
      case SyntaxKind.BigIntKeyword:
      case SyntaxKind.BooleanKeyword:
      case SyntaxKind.NeverKeyword:
      case SyntaxKind.NumberKeyword:
      case SyntaxKind.ObjectKeyword:
      case SyntaxKind.StringKeyword:
      case SyntaxKind.SymbolKeyword:
      case SyntaxKind.UnknownKeyword:
      case SyntaxKind.VoidKeyword:
      case SyntaxKind.UndefinedKeyword:
      case SyntaxKind.IntrinsicKeyword: {
        return this.createNode<any>(node, {
          type: AST_NODE_TYPES[`TS${SyntaxKind[node.kind]}` as AST_NODE_TYPES],
        });
      }

      case SyntaxKind.NonNullExpression: {
        const nnExpr = this.createNode<TSESTree.TSNonNullExpression>(node, {
          type: AST_NODE_TYPES.TSNonNullExpression,
          expression: this.convertChild(node.expression),
        });

        return this.convertChainExpression(nnExpr, node);
      }

      case SyntaxKind.TypeLiteral: {
        return this.createNode<TSESTree.TSTypeLiteral>(node, {
          type: AST_NODE_TYPES.TSTypeLiteral,
          members: this.convertChildren(node.members),
        });
      }

      case SyntaxKind.ArrayType: {
        return this.createNode<TSESTree.TSArrayType>(node, {
          type: AST_NODE_TYPES.TSArrayType,
          elementType: this.convertChild(node.elementType),
        });
      }

      case SyntaxKind.IndexedAccessType: {
        return this.createNode<TSESTree.TSIndexedAccessType>(node, {
          type: AST_NODE_TYPES.TSIndexedAccessType,
          indexType: this.convertChild(node.indexType),
          objectType: this.convertChild(node.objectType),
        });
      }

      case SyntaxKind.ConditionalType: {
        return this.createNode<TSESTree.TSConditionalType>(node, {
          type: AST_NODE_TYPES.TSConditionalType,
          checkType: this.convertChild(node.checkType),
          extendsType: this.convertChild(node.extendsType),
          falseType: this.convertChild(node.falseType),
          trueType: this.convertChild(node.trueType),
        });
      }

      case SyntaxKind.TypeQuery:
        return this.createNode<TSESTree.TSTypeQuery>(node, {
          type: AST_NODE_TYPES.TSTypeQuery,
          exprName: this.convertChild(node.exprName),
          typeArguments: this.convertTypeArguments(node),
        });

      case SyntaxKind.MappedType: {
        return this.createNode<TSESTree.TSMappedType>(
          node,
          this.#withDeprecatedGetter(
            {
              type: AST_NODE_TYPES.TSMappedType,
              constraint: this.convertChild(node.typeParameter.constraint),
              key: this.convertChild(node.typeParameter.name),
              nameType: this.convertChild(node.nameType) ?? null,
              optional: node.questionToken
                ? node.questionToken.kind === SyntaxKind.QuestionToken ||
                  getTextForTokenKind(node.questionToken.kind)
                : false,
              readonly: node.readonlyToken
                ? node.readonlyToken.kind === SyntaxKind.ReadonlyKeyword ||
                  getTextForTokenKind(node.readonlyToken.kind)
                : undefined,
              typeAnnotation: node.type && this.convertChild(node.type),
            },
            'typeParameter',
            "'constraint' and 'key'",
            this.convertChild(node.typeParameter),
          ),
        );
      }

      case SyntaxKind.ParenthesizedExpression:
        return this.convertChild(node.expression, parent);

      case SyntaxKind.TypeAliasDeclaration: {
        const result = this.createNode<TSESTree.TSTypeAliasDeclaration>(node, {
          type: AST_NODE_TYPES.TSTypeAliasDeclaration,
          declare: hasModifier(SyntaxKind.DeclareKeyword, node),
          id: this.convertChild(node.name),
          typeAnnotation: this.convertChild(node.type),
          typeParameters: this.convertTypeParameters(node),
        });

        return this.fixExports(node, result);
      }

      case SyntaxKind.MethodSignature: {
        return this.convertMethodSignature(node);
      }

      case SyntaxKind.PropertySignature: {
        return this.createNode<TSESTree.TSPropertySignature>(node, {
          type: AST_NODE_TYPES.TSPropertySignature,
          accessibility: getTSNodeAccessibility(node),
          computed: isComputedProperty(node.name),
          key: this.convertChild(node.name),
          optional: isOptional(node),
          readonly: hasModifier(SyntaxKind.ReadonlyKeyword, node),
          static: hasModifier(SyntaxKind.StaticKeyword, node),
          typeAnnotation:
            node.type && this.convertTypeAnnotation(node.type, node),
        });
      }

      case SyntaxKind.IndexSignature: {
        return this.createNode<TSESTree.TSIndexSignature>(node, {
          type: AST_NODE_TYPES.TSIndexSignature,
          accessibility: getTSNodeAccessibility(node),
          parameters: this.convertChildren(node.parameters),
          readonly: hasModifier(SyntaxKind.ReadonlyKeyword, node),
          static: hasModifier(SyntaxKind.StaticKeyword, node),
          typeAnnotation:
            node.type && this.convertTypeAnnotation(node.type, node),
        });
      }

      case SyntaxKind.ConstructorType: {
        return this.createNode<TSESTree.TSConstructorType>(node, {
          type: AST_NODE_TYPES.TSConstructorType,
          abstract: hasModifier(SyntaxKind.AbstractKeyword, node),
          params: this.convertParameters(node.parameters),
          returnType: node.type && this.convertTypeAnnotation(node.type, node),
          typeParameters: this.convertTypeParameters(node),
        });
      }

      case SyntaxKind.FunctionType:
      case SyntaxKind.ConstructSignature:
      case SyntaxKind.CallSignature: {
        const type =
          node.kind === SyntaxKind.ConstructSignature
            ? AST_NODE_TYPES.TSConstructSignatureDeclaration
            : node.kind === SyntaxKind.CallSignature
              ? AST_NODE_TYPES.TSCallSignatureDeclaration
              : AST_NODE_TYPES.TSFunctionType;

        return this.createNode<
          | TSESTree.TSCallSignatureDeclaration
          | TSESTree.TSConstructSignatureDeclaration
          | TSESTree.TSFunctionType
        >(node, {
          type,
          params: this.convertParameters(node.parameters),
          returnType: node.type && this.convertTypeAnnotation(node.type, node),
          typeParameters: this.convertTypeParameters(node),
        });
      }

      case SyntaxKind.ExpressionWithTypeArguments: {
        const parentKind = parent.kind;
        const type =
          parentKind === SyntaxKind.InterfaceDeclaration
            ? AST_NODE_TYPES.TSInterfaceHeritage
            : parentKind === SyntaxKind.HeritageClause
              ? AST_NODE_TYPES.TSClassImplements
              : AST_NODE_TYPES.TSInstantiationExpression;

        return this.createNode<
          | TSESTree.TSClassImplements
          | TSESTree.TSInstantiationExpression
          | TSESTree.TSInterfaceHeritage
        >(node, {
          type,
          expression: this.convertChild(node.expression),
          typeArguments: this.convertTypeArguments(node),
        });
      }

      case SyntaxKind.InterfaceDeclaration: {
        const interfaceExtends: TSESTree.TSInterfaceHeritage[] =
          node.heritageClauses?.flatMap(heritageClause =>
            // `InterfaceDeclaration` can only have `extends`,
            // already checked in `check-syntax-errors.ts`,
            // However, `allowInvalidAST` allow to bypass the syntax check,
            // We'll just ignore clauses after other keywords
            heritageClause.token === SyntaxKind.ExtendsKeyword
              ? heritageClause.types.map(
                  heritageType =>
                    this.convertChild(
                      heritageType,
                      node,
                    ) as TSESTree.TSInterfaceHeritage,
                )
              : [],
          ) ?? [];

        const result = this.createNode<TSESTree.TSInterfaceDeclaration>(node, {
          type: AST_NODE_TYPES.TSInterfaceDeclaration,
          body: this.createNode<TSESTree.TSInterfaceBody>(node, {
            type: AST_NODE_TYPES.TSInterfaceBody,
            range: [node.members.pos - 1, node.end],
            body: this.convertChildren(node.members),
          }),
          declare: hasModifier(SyntaxKind.DeclareKeyword, node),
          extends: interfaceExtends,
          id: this.convertChild(node.name),
          typeParameters: this.convertTypeParameters(node),
        });

        return this.fixExports(node, result);
      }

      case SyntaxKind.TypePredicate: {
        const result = this.createNode<TSESTree.TSTypePredicate>(node, {
          type: AST_NODE_TYPES.TSTypePredicate,
          asserts: node.assertsModifier != null,
          parameterName: this.convertChild(node.parameterName),
          typeAnnotation: null,
        });
        /**
         * Specific fix for type-guard location data
         */
        if (node.type) {
          result.typeAnnotation = this.convertTypeAnnotation(node.type, node);
          result.typeAnnotation.loc = result.typeAnnotation.typeAnnotation.loc;
          result.typeAnnotation.range =
            result.typeAnnotation.typeAnnotation.range;
        }
        return result;
      }

      case SyntaxKind.ImportType: {
        const range = getRange(node, this.ast);
        if (node.isTypeOf) {
          const token = findNextToken(node.getFirstToken()!, node, this.ast)!;
          range[0] = token.getStart(this.ast);
        }

        let options = null;
        if (node.attributes) {
          const value = this.createNode<TSESTree.ObjectExpression>(
            node.attributes,
            {
              type: AST_NODE_TYPES.ObjectExpression,
              properties: node.attributes.elements.map(importAttribute =>
                this.createNode<TSESTree.Property>(importAttribute, {
                  type: AST_NODE_TYPES.Property,
                  computed: false,
                  key: this.convertChild(importAttribute.name),
                  kind: 'init',
                  method: false,
                  optional: false,
                  shorthand: false,
                  value: this.convertChild(importAttribute.value),
                }),
              ),
            },
          );

          const commaToken = findNextToken(node.argument, node, this.ast)!;
          const openBraceToken = findNextToken(commaToken, node, this.ast)!;
          const tokenAfterAttributes = findNextToken(
            node.attributes,
            node,
            this.ast,
          )!;
          // Since TS 5.9, there could be a trailing comma, i.e. `{ with: { ... }, }`
          const closeBraceToken =
            tokenAfterAttributes.kind === ts.SyntaxKind.CommaToken
              ? findNextToken(tokenAfterAttributes, node, this.ast)!
              : tokenAfterAttributes;
          const withOrAssertToken = findNextToken(
            openBraceToken,
            node,
            this.ast,
          )!;
          const withOrAssertTokenRange = getRange(withOrAssertToken, this.ast);
          const withOrAssertName =
            withOrAssertToken.kind === ts.SyntaxKind.AssertKeyword
              ? 'assert'
              : 'with';

          options = this.createNode<TSESTree.ObjectExpression>(node, {
            type: AST_NODE_TYPES.ObjectExpression,
            range: [openBraceToken.getStart(this.ast), closeBraceToken.end],
            properties: [
              this.createNode<TSESTree.Property>(node, {
                type: AST_NODE_TYPES.Property,
                range: [withOrAssertTokenRange[0], node.attributes.end],
                computed: false,
                key: this.createNode<TSESTree.Identifier>(node, {
                  type: AST_NODE_TYPES.Identifier,
                  range: withOrAssertTokenRange,
                  decorators: [],
                  name: withOrAssertName,
                  optional: false,
                  typeAnnotation: undefined,
                }),
                kind: 'init',
                method: false,
                optional: false,
                shorthand: false,
                value,
              }),
            ],
          });
        }

        const argument = this.convertChild(node.argument);
        const source = argument.literal;

        const result = this.createNode<TSESTree.TSImportType>(
          node,
          this.#withDeprecatedGetter(
            {
              type: AST_NODE_TYPES.TSImportType,
              range,
              options,
              qualifier: this.convertChild(node.qualifier),
              source,
              typeArguments: this.convertTypeArguments(node) ?? null,
            },
            'argument',
            'source',
            argument,
          ),
        );

        if (node.isTypeOf) {
          return this.createNode<TSESTree.TSTypeQuery>(node, {
            type: AST_NODE_TYPES.TSTypeQuery,
            exprName: result,
            typeArguments: undefined,
          });
        }
        return result;
      }

      case SyntaxKind.EnumDeclaration: {
        const members = this.convertChildren(node.members);
        const result = this.createNode<TSESTree.TSEnumDeclaration>(
          node,
          this.#withDeprecatedGetter(
            {
              type: AST_NODE_TYPES.TSEnumDeclaration,
              body: this.createNode<TSESTree.TSEnumBody>(node, {
                type: AST_NODE_TYPES.TSEnumBody,
                range: [node.members.pos - 1, node.end],
                members,
              }),
              const: hasModifier(SyntaxKind.ConstKeyword, node),
              declare: hasModifier(SyntaxKind.DeclareKeyword, node),
              id: this.convertChild(node.name),
            },
            'members',
            `'body.members'`,
            this.convertChildren(node.members),
          ),
        );

        return this.fixExports(node, result);
      }

      case SyntaxKind.EnumMember: {
        const computed = node.name.kind === ts.SyntaxKind.ComputedPropertyName;
        return this.createNode<TSESTree.TSEnumMember>(
          node,
          this.#withDeprecatedGetter(
            {
              type: AST_NODE_TYPES.TSEnumMember,
              id: this.convertChild(node.name),
              initializer:
                node.initializer && this.convertChild(node.initializer),
            },
            'computed',
            undefined,
            computed,
          ),
        );
      }

      case SyntaxKind.ModuleDeclaration: {
        let isDeclare = hasModifier(SyntaxKind.DeclareKeyword, node);

        const result = this.createNode<TSESTree.TSModuleDeclaration>(node, {
          type: AST_NODE_TYPES.TSModuleDeclaration,
          ...((): TSESTree.OptionalRangeAndLoc<
            Omit<TSESTree.TSModuleDeclaration, 'parent' | 'type'>
          > => {
            if (node.flags & ts.NodeFlags.GlobalAugmentation) {
              return {
                body: this.convertChild(node.body) as TSESTree.TSModuleBlock,
                declare: false,
                global: false,
                id: this.convertChild(node.name),
                kind: 'global',
              };
            }

            if (ts.isStringLiteral(node.name)) {
              const body: TSESTree.TSModuleBlock | null = this.convertChild(
                node.body,
              );
              return {
                kind: 'module',
                ...(body != null ? { body } : {}),
                declare: false,
                global: false,
                id: this.convertChild(node.name),
              };
            }

            // Nested module declarations are stored in TypeScript as nested tree nodes.
            // We "unravel" them here by making our own nested TSQualifiedName,
            // with the innermost node's body as the actual node body.

            let name: TSESTree.Identifier | TSESTree.TSQualifiedName =
              this.createNode<TSESTree.Identifier>(node.name, {
                type: AST_NODE_TYPES.Identifier,
                range: [node.name.getStart(this.ast), node.name.getEnd()],
                decorators: [],
                name: node.name.text,
                optional: false,
                typeAnnotation: undefined,
              });

            while (
              node.body &&
              ts.isModuleDeclaration(node.body) &&
              node.body.name
            ) {
              node = node.body;
              isDeclare ||= hasModifier(SyntaxKind.DeclareKeyword, node);

              const nextName = node.name as ts.Identifier;

              const right = this.createNode<TSESTree.Identifier>(nextName, {
                type: AST_NODE_TYPES.Identifier,
                range: [nextName.getStart(this.ast), nextName.getEnd()],
                decorators: [],
                name: nextName.text,
                optional: false,
                typeAnnotation: undefined,
              });

              name = this.createNode<TSESTree.TSQualifiedName>(nextName, {
                type: AST_NODE_TYPES.TSQualifiedName,
                range: [name.range[0], right.range[1]],
                left: name,
                right,
              });
            }

            return {
              body: this.convertChild(node.body),
              declare: false,
              global: false,
              id: name,
              kind:
                node.flags & ts.NodeFlags.Namespace ? 'namespace' : 'module',
            };
          })(),
        });

        result.declare = isDeclare;

        if (node.flags & ts.NodeFlags.GlobalAugmentation) {
          // eslint-disable-next-line @typescript-eslint/no-deprecated
          result.global = true;
        }

        return this.fixExports(node, result);
      }

      // TypeScript specific types
      case SyntaxKind.ParenthesizedType: {
        return this.convertChild(node.type);
      }
      case SyntaxKind.UnionType: {
        return this.createNode<TSESTree.TSUnionType>(node, {
          type: AST_NODE_TYPES.TSUnionType,
          types: this.convertChildren(node.types),
        });
      }
      case SyntaxKind.IntersectionType: {
        return this.createNode<TSESTree.TSIntersectionType>(node, {
          type: AST_NODE_TYPES.TSIntersectionType,
          types: this.convertChildren(node.types),
        });
      }
      case SyntaxKind.AsExpression: {
        return this.createNode<TSESTree.TSAsExpression>(node, {
          type: AST_NODE_TYPES.TSAsExpression,
          expression: this.convertChild(node.expression),
          typeAnnotation: this.convertChild(node.type),
        });
      }
      case SyntaxKind.InferType: {
        return this.createNode<TSESTree.TSInferType>(node, {
          type: AST_NODE_TYPES.TSInferType,
          typeParameter: this.convertChild(node.typeParameter),
        });
      }
      case SyntaxKind.LiteralType: {
        if (node.literal.kind === SyntaxKind.NullKeyword) {
          // 4.0 started nesting null types inside a LiteralType node
          // but our AST is designed around the old way of null being a keyword
          return this.createNode<TSESTree.TSNullKeyword>(node.literal, {
            type: AST_NODE_TYPES.TSNullKeyword,
          });
        }

        return this.createNode<TSESTree.TSLiteralType>(node, {
          type: AST_NODE_TYPES.TSLiteralType,
          literal: this.convertChild(node.literal),
        });
      }
      case SyntaxKind.TypeAssertionExpression: {
        return this.createNode<TSESTree.TSTypeAssertion>(node, {
          type: AST_NODE_TYPES.TSTypeAssertion,
          expression: this.convertChild(node.expression),
          typeAnnotation: this.convertChild(node.type),
        });
      }
      case SyntaxKind.ImportEqualsDeclaration: {
        return this.fixExports(
          node,
          this.createNode<TSESTree.TSImportEqualsDeclaration>(node, {
            type: AST_NODE_TYPES.TSImportEqualsDeclaration,
            id: this.convertChild(node.name),
            importKind: node.isTypeOnly ? 'type' : 'value',
            moduleReference: this.convertChild(node.moduleReference),
          }),
        );
      }
      case SyntaxKind.ExternalModuleReference: {
        return this.createNode<TSESTree.TSExternalModuleReference>(node, {
          type: AST_NODE_TYPES.TSExternalModuleReference,
          expression: this.convertChild(node.expression),
        });
      }
      case SyntaxKind.NamespaceExportDeclaration: {
        return this.createNode<TSESTree.TSNamespaceExportDeclaration>(node, {
          type: AST_NODE_TYPES.TSNamespaceExportDeclaration,
          id: this.convertChild(node.name),
        });
      }
      case SyntaxKind.AbstractKeyword: {
        return this.createNode<TSESTree.TSAbstractKeyword>(node, {
          type: AST_NODE_TYPES.TSAbstractKeyword,
        });
      }

      // Tuple
      case SyntaxKind.TupleType: {
        const elementTypes = this.convertChildren(node.elements);

        return this.createNode<TSESTree.TSTupleType>(node, {
          type: AST_NODE_TYPES.TSTupleType,
          elementTypes,
        });
      }
      case SyntaxKind.NamedTupleMember: {
        const member = this.createNode<TSESTree.TSNamedTupleMember>(node, {
          type: AST_NODE_TYPES.TSNamedTupleMember,
          elementType: this.convertChild(node.type, node),
          label: this.convertChild(node.name, node),
          optional: node.questionToken != null,
        });

        if (node.dotDotDotToken) {
          // adjust the start to account for the "..."
          member.range[0] = member.label.range[0];
          member.loc.start = member.label.loc.start;
          return this.createNode<TSESTree.TSRestType>(node, {
            type: AST_NODE_TYPES.TSRestType,
            typeAnnotation: member,
          });
        }

        return member;
      }
      case SyntaxKind.OptionalType: {
        return this.createNode<TSESTree.TSOptionalType>(node, {
          type: AST_NODE_TYPES.TSOptionalType,
          typeAnnotation: this.convertChild(node.type),
        });
      }
      case SyntaxKind.RestType: {
        return this.createNode<TSESTree.TSRestType>(node, {
          type: AST_NODE_TYPES.TSRestType,
          typeAnnotation: this.convertChild(node.type),
        });
      }

      // Template Literal Types
      case SyntaxKind.TemplateLiteralType: {
        const result = this.createNode<TSESTree.TSTemplateLiteralType>(node, {
          type: AST_NODE_TYPES.TSTemplateLiteralType,
          quasis: [this.convertChild(node.head)],
          types: [],
        });

        node.templateSpans.forEach(templateSpan => {
          result.types.push(
            this.convertChild(templateSpan.type) as TSESTree.TypeNode,
          );
          result.quasis.push(
            this.convertChild(templateSpan.literal) as TSESTree.TemplateElement,
          );
        });
        return result;
      }

      case SyntaxKind.ClassStaticBlockDeclaration: {
        return this.createNode<TSESTree.StaticBlock>(node, {
          type: AST_NODE_TYPES.StaticBlock,
          body: this.convertBodyExpressions(node.body.statements, node),
        });
      }

      // eslint-disable-next-line @typescript-eslint/no-deprecated
      case SyntaxKind.AssertEntry:
      case SyntaxKind.ImportAttribute: {
        return this.createNode<TSESTree.ImportAttribute>(node, {
          type: AST_NODE_TYPES.ImportAttribute,
          key: this.convertChild(node.name),
          value: this.convertChild(node.value),
        });
      }

      case SyntaxKind.SatisfiesExpression: {
        return this.createNode<TSESTree.TSSatisfiesExpression>(node, {
          type: AST_NODE_TYPES.TSSatisfiesExpression,
          expression: this.convertChild(node.expression),
          typeAnnotation: this.convertChild(node.type),
        });
      }

      default:
        return this.deeplyCopy(node);
    }
  }

Converter.createNode(node: ts.Node, data: Omit<TSESTree.OptionalRangeAndLoc<T>, '…): T

Parameters:

  • node ts.Node
  • data Omit<TSESTree.OptionalRangeAndLoc<T>, 'parent'>

Returns: T

Calls:

  • getRange (from ./node-utils)
  • getLocFor (from ./node-utils)
  • this.esTreeNodeToTSNodeMap.set
Code
private createNode<T extends TSESTree.Node = TSESTree.Node>(
    node: ts.Node,
    data: Omit<TSESTree.OptionalRangeAndLoc<T>, 'parent'>,
  ): T {
    const result = data;
    result.range ??= getRange(node, this.ast);
    result.loc ??= getLocFor(result.range, this.ast);

    if (result && this.options.shouldPreserveNodeMaps) {
      this.esTreeNodeToTSNodeMap.set(result, node);
    }
    return result as T;
  }

Converter.convertProgram(): TSESTree.Program

Returns: TSESTree.Program

Calls:

  • this.converter
Code
convertProgram(): TSESTree.Program {
    return this.converter(this.ast) as TSESTree.Program;
  }

Converter.deeplyCopy(node: TSNode): any

For nodes that are copied directly from the TypeScript AST into ESTree mostly as-is. The only difference is the addition of a type property instead of a kind property. Recursively copies all children.

Raw JSDoc
/**
   * For nodes that are copied directly from the TypeScript AST into
   * ESTree mostly as-is. The only difference is the addition of a type
   * property instead of a kind property. Recursively copies all children.
   */

Calls:

  • this.#throwError
  • this.createNode
  • ts.isTypeNode
  • this.convertTypeAnnotation
  • this.convertTypeArguments
  • this.convertTypeParameters
  • getDecorators (from ./getModifiers)
  • this.convertChildren
  • Object.entries<any>(node) .filter(([key]) => !KEYS_TO_NOT_COPY.has(key)) .forEach
  • Array.isArray
  • this.convertChild

Internal Comments:

/**
     * If the "errorOnUnknownASTType" option is set to true, throw an error,
     * otherwise fallback to just including the unknown type as-is.
     */
// keys we never want to clone from the base typescript node as they (x2)
// introduce garbage into our AST (x2)
// need to check node[key].kind to ensure we don't try to convert a symbol (x4)

Code
private deeplyCopy(node: TSNode): any {
    if (node.kind === ts.SyntaxKind.JSDocFunctionType) {
      this.#throwError(
        node,
        'JSDoc types can only be used inside documentation comments.',
      );
    }

    const customType = `TS${SyntaxKind[node.kind]}` as AST_NODE_TYPES;

    /**
     * If the "errorOnUnknownASTType" option is set to true, throw an error,
     * otherwise fallback to just including the unknown type as-is.
     */
    if (this.options.errorOnUnknownASTType && !AST_NODE_TYPES[customType]) {
      throw new Error(`Unknown AST_NODE_TYPE: "${customType}"`);
    }

    const result = this.createNode<any>(node, {
      type: customType,
    });

    if ('type' in node) {
      result.typeAnnotation =
        node.type && 'kind' in node.type && ts.isTypeNode(node.type)
          ? this.convertTypeAnnotation(node.type, node)
          : null;
    }
    if ('typeArguments' in node) {
      result.typeArguments =
        node.typeArguments && 'pos' in node.typeArguments
          ? this.convertTypeArguments(node)
          : null;
    }
    if ('typeParameters' in node) {
      result.typeParameters =
        node.typeParameters && 'pos' in node.typeParameters
          ? // @ts-expect-error -- Fix me
            this.convertTypeParameters(node)
          : null;
    }
    const decorators = getDecorators(node);
    if (decorators?.length) {
      result.decorators = this.convertChildren(decorators);
    }

    // keys we never want to clone from the base typescript node as they
    // introduce garbage into our AST
    const KEYS_TO_NOT_COPY = new Set([
      '_children',
      'decorators',
      'end',
      'flags',
      'heritageClauses',
      'illegalDecorators',
      'jsDoc',
      'kind',
      'locals',
      'localSymbol',
      'modifierFlagsCache',
      'modifiers',
      'nextContainer',
      'parent',
      'pos',
      'symbol',
      'transformFlags',
      'type',
      'typeArguments',
      'typeParameters',
    ]);

    Object.entries<any>(node)
      .filter(([key]) => !KEYS_TO_NOT_COPY.has(key))
      .forEach(([key, value]) => {
        if (Array.isArray(value)) {
          result[key] = this.convertChildren(value);
        } else if (value && typeof value === 'object' && value.kind) {
          // need to check node[key].kind to ensure we don't try to convert a symbol
          result[key] = this.convertChild(value as TSNode);
        } else {
          result[key] = value;
        }
      });
    return result;
  }

Converter.fixExports(node: | ts.ClassDeclaration | ts.ClassExpress…, result: T): T | TSESTree.ExportDefaultDeclaration | TSESTree.ExportName…

Fixes the exports of the given ts.Node

Returns: undefined the ESTreeNode with fixed exports

Raw JSDoc
/**
   * Fixes the exports of the given ts.Node
   * @returns the ESTreeNode with fixed exports
   */

Calls:

  • ts.isModuleDeclaration
  • ts.isStringLiteral
  • getNamespaceModifiers (from ./node-utils)
  • getModifiers (from ./getModifiers)
  • this.registerTSNodeInNodeMap
  • findNextToken (from ./node-utils)
  • varToken!.getStart
  • getLocFor (from ./node-utils)
  • this.createNode
  • exportKeyword.getStart
  • this.#withDeprecatedAliasGetter

Internal Comments:

/**
       * Make sure that original node is registered instead of export
       */ (x4)
// @ts-expect-error - TODO, narrow the types here (x3)

Code
private fixExports<
    T extends
      TSESTree.DefaultExportDeclarations | TSESTree.NamedExportDeclarations,
  >(
    node:
      | ts.ClassDeclaration
      | ts.ClassExpression
      | ts.EnumDeclaration
      | ts.FunctionDeclaration
      | ts.ImportEqualsDeclaration
      | ts.InterfaceDeclaration
      | ts.ModuleDeclaration
      | ts.TypeAliasDeclaration
      | ts.VariableStatement,
    result: T,
  ): T | TSESTree.ExportDefaultDeclaration | TSESTree.ExportNamedDeclaration {
    const isNamespaceNode =
      ts.isModuleDeclaration(node) && !ts.isStringLiteral(node.name);

    const modifiers = isNamespaceNode
      ? getNamespaceModifiers(node)
      : getModifiers(node);

    if (modifiers?.[0].kind === SyntaxKind.ExportKeyword) {
      /**
       * Make sure that original node is registered instead of export
       */
      this.registerTSNodeInNodeMap(node, result);

      const exportKeyword = modifiers[0];
      const nextModifier = modifiers[1];
      const declarationIsDefault =
        nextModifier?.kind === SyntaxKind.DefaultKeyword;

      const varToken = declarationIsDefault
        ? findNextToken(nextModifier, this.ast, this.ast)
        : findNextToken(exportKeyword, this.ast, this.ast);

      result.range[0] = varToken!.getStart(this.ast);
      result.loc = getLocFor(result.range, this.ast);

      if (declarationIsDefault) {
        return this.createNode<TSESTree.ExportDefaultDeclaration>(node, {
          type: AST_NODE_TYPES.ExportDefaultDeclaration,
          range: [exportKeyword.getStart(this.ast), result.range[1]],
          declaration: result as TSESTree.DefaultExportDeclarations,
          exportKind: 'value',
        });
      }
      const isType =
        result.type === AST_NODE_TYPES.TSInterfaceDeclaration ||
        result.type === AST_NODE_TYPES.TSTypeAliasDeclaration;
      const isDeclare = 'declare' in result && result.declare;
      return this.createNode<TSESTree.ExportNamedDeclaration>(
        node,
        // @ts-expect-error - TODO, narrow the types here
        this.#withDeprecatedAliasGetter(
          {
            type: AST_NODE_TYPES.ExportNamedDeclaration,
            range: [exportKeyword.getStart(this.ast), result.range[1]],
            attributes: [],
            declaration: result,
            exportKind: isType || isDeclare ? 'type' : 'value',
            source: null,
            specifiers: [],
          },
          'assertions',
          'attributes',
          true,
        ),
      );
    }

    return result;
  }

Converter.getASTMaps(): ASTMaps

Returns: ASTMaps

Code
getASTMaps(): ASTMaps {
    return {
      esTreeNodeToTSNodeMap: this.esTreeNodeToTSNodeMap,
      tsNodeToESTreeNodeMap: this.tsNodeToESTreeNodeMap,
    };
  }

Converter.registerTSNodeInNodeMap(node: ts.Node, result: TSESTree.Node | null): void

Register specific TypeScript node into map with first ESTree node provided

Raw JSDoc
/**
   * Register specific TypeScript node into map with first ESTree node provided
   */

Calls:

  • this.tsNodeToESTreeNodeMap.has
  • this.tsNodeToESTreeNodeMap.set
Code
private registerTSNodeInNodeMap(
    node: ts.Node,
    result: TSESTree.Node | null,
  ): void {
    if (
      result &&
      this.options.shouldPreserveNodeMaps &&
      !this.tsNodeToESTreeNodeMap.has(node)
    ) {
      this.tsNodeToESTreeNodeMap.set(node, result);
    }
  }

Classes

Converter

Methods (29) β€” full entries under Functions

Method Signature
#checkSyntaxError (node: ts.Node, parent: TSNode): void
#isValidEscape (text: string): boolean
#throwError (node: number \| ts.Node \| TSESTree.Range, message: string): asserts node is never
#withDeprecatedAliasGetter (node: Properties, aliasKey: AliasKey, valueKey: ValueKey, suppressWarnings: boolean): Properties...
#withDeprecatedGetter (node: Properties, deprecatedKey: Key, preferredKey: string \| undefined, value: Value): Properti...
convertBindingNameWithTypeAnnotation (name: ts.BindingName, tsType: ts.TypeNode \| undefined, parent: ts.Node): TSESTree.BindingName
convertBodyExpressions (nodes: ts.NodeArray<ts.Statement>, parent: \| ts.Block \| ts.ClassStaticBlockDeclaration \| ts.M...
convertChainExpression (node: TSESTree.ChainElement, tsNode: \| ts.CallExpression \| ts.ElementAccessExpression \| ts.No...
convertChild (child: ts.Node, parent: ts.Node): any
convertChildren (children: ts.Node[] \| ts.NodeArray<ts.Node>, parent: ts.Node): any[]
convertPattern (child: ts.Node, parent: ts.Node): any
convertTypeAnnotation (child: ts.TypeNode, parent: ts.Node \| undefined): TSESTree.TSTypeAnnotation
convertTypeArguments (node: TSESTreeToTSNode<TSESTree.TSTypeParameterInstantiation>): TSESTree.TSTypeParameterInstanti...
convertTypeParameters (node: \| ts.ArrowFunction \| ts.CallSignatureDeclaration \| ts.ClassDeclaration \| ts.ClassExpre...
convertParameters (parameters: ts.NodeArray<ts.ParameterDeclaration>): TSESTree.Parameter[]
converter (node: ts.Node, parent: ts.Node, allowPattern: boolean): any
convertImportAttributes (node: ts.ExportDeclaration \| ts.ImportDeclaration): TSESTree.ImportAttribute[]
convertJSXIdentifier (node: ts.Identifier \| ts.ThisExpression): TSESTree.JSXIdentifier
convertJSXNamespaceOrIdentifier (node: ts.Identifier \| ts.JsxNamespacedName \| ts.ThisExpression): TSESTree.JSXIdentifier \| TSE...
convertJSXTagName (node: ts.JsxTagNameExpression, parent: ts.Node): TSESTree.JSXTagNameExpression
convertMethodSignature (node: \| ts.GetAccessorDeclaration \| ts.MethodSignature \| ts.SetAccessorDeclaration): TSESTree...
fixParentLocation (result: TSESTree.BaseNode, childRange: [number, number]): void
convertNode (node: TSNode, parent: TSNode): TSESTree.Node \| null
createNode (node: ts.Node, data: Omit<TSESTree.OptionalRangeAndLoc<T>, 'parent'>): T
convertProgram (): TSESTree.Program
deeplyCopy (node: TSNode): any
fixExports (node: \| ts.ClassDeclaration \| ts.ClassExpression \| ts.EnumDeclaration \| ts.FunctionDeclarati...
getASTMaps (): ASTMaps
registerTSNodeInNodeMap (node: ts.Node, result: TSESTree.Node \| null): void

Interfaces

ConverterOptions

Interface Code
export interface ConverterOptions {
  allowInvalidAST?: boolean;
  errorOnUnknownASTType?: boolean;
  shouldPreserveNodeMaps?: boolean;
  suppressDeprecatedPropertyWarnings?: boolean;
}

Properties

Name Type Optional Description
allowInvalidAST boolean βœ“ not shown
errorOnUnknownASTType boolean βœ“ not shown
shouldPreserveNodeMaps boolean βœ“ not shown
suppressDeprecatedPropertyWarnings boolean βœ“ not shown

ASTMaps

Interface Code
export interface ASTMaps {
  esTreeNodeToTSNodeMap: ParserWeakMapESTreeToTSNode;
  tsNodeToESTreeNodeMap: ParserWeakMap<TSNode, TSESTree.Node>;
}

Properties

Name Type Optional Description
esTreeNodeToTSNodeMap ParserWeakMapESTreeToTSNode βœ— not shown
tsNodeToESTreeNodeMap ParserWeakMap<TSNode, TSESTree.Node> βœ— not shown

Generated by Syntax Scribe