export function getDescriptor()

in src/platform/packages/private/kbn-telemetry-tools/src/tools/serializer.ts [141:333]


export function getDescriptor(node: ts.Node, program: ts.Program): Descriptor | DescriptorValue {
  if (ts.isMethodSignature(node) || ts.isPropertySignature(node)) {
    if (node.type) {
      return getDescriptor(node.type, program);
    }
  }

  /**
   * Supported interface keys:
   * inteface T { [computed_value]: ANY_VALUE };
   * inteface T { hardcoded_string: ANY_VALUE };
   */
  if (ts.isTypeLiteralNode(node) || ts.isInterfaceDeclaration(node)) {
    return node.members.reduce((acc, m) => {
      const { name: nameNode } = m;
      if (nameNode) {
        const nodeText = nameNode.getText();
        if (ts.isComputedPropertyName(nameNode)) {
          const typeChecker = program.getTypeChecker();
          const symbol = typeChecker.getSymbolAtLocation(nameNode);
          const key = symbol?.getName();
          if (!key) {
            throw Error(`Unable to parse computed value of ${nodeText}.`);
          }
          return { ...acc, [key]: getDescriptor(m, program) };
        }

        return { ...acc, [nodeText]: getDescriptor(m, program) };
      }

      return { ...acc, ...getDescriptor(m, program) };
    }, {});
  }

  /**
   * Supported signature constraints of `string`:
   * { [key in 'prop1' | 'prop2']: value }
   * { [key in Enum]: value }
   */
  if ((ts.isIndexSignatureDeclaration(node) || ts.isMappedTypeNode(node)) && node.type) {
    const descriptor = getDescriptor(node.type, program);
    const constraint = (node as ts.MappedTypeNode).typeParameter?.constraint;
    if (constraint) {
      const constraints = getConstraints(constraint, program);
      const constraintsArray = Array.isArray(constraints) ? constraints : [constraints];
      if (typeof constraintsArray[0] === 'string') {
        return constraintsArray.reduce((acc, c) => {
          acc[c] = descriptor;
          return acc;
        }, {} as Record<string, unknown>);
      }
    }
    return { '@@INDEX@@': descriptor };
  }

  if (ts.SyntaxKind.FirstNode === node.kind) {
    return getDescriptor((node as any).right, program);
  }

  if (ts.isIdentifier(node)) {
    const identifierName = node.getText();
    if (identifierName === 'Date') {
      return { kind: TelemetryKinds.Date, type: 'Date' };
    }
    if (identifierName === 'Moment') {
      return { kind: TelemetryKinds.MomentDate, type: 'MomentDate' };
    }
    throw new Error(`Unsupported Identifier ${identifierName}.`);
  }

  if (ts.isTypeReferenceNode(node)) {
    const typeChecker = program.getTypeChecker();
    const symbol = typeChecker.getSymbolAtLocation(node.typeName);
    const symbolName = symbol?.getName();
    if (symbolName === 'Moment') {
      return { kind: TelemetryKinds.MomentDate, type: 'MomentDate' };
    }
    if (symbolName === 'Date') {
      return { kind: TelemetryKinds.Date, type: 'Date' };
    }

    // Support Array<T>
    if (symbolName === 'Array') {
      if (node.typeArguments?.length !== 1) {
        throw Error('Array type only supports 1 type parameter Array<T>');
      }
      const typeArgument = node.typeArguments[0];
      return { items: getDescriptor(typeArgument, program) };
    }

    // Support `Record<string, SOMETHING>`
    if (symbolName === 'Record') {
      // Special use case `Record<string, unknown>`
      if (
        node.typeArguments![0].kind === ts.SyntaxKind.StringKeyword &&
        node.typeArguments![1].kind === ts.SyntaxKind.UnknownKeyword
      ) {
        const kind = node.typeArguments![1].kind;
        return { kind, type: ts.SyntaxKind[kind] as keyof typeof ts.SyntaxKind };
      }

      const descriptor = getDescriptor(node.typeArguments![1], program);
      if (node.typeArguments![0].kind === ts.SyntaxKind.StringKeyword) {
        return { '@@INDEX@@': descriptor };
      }
      const constraints = getConstraints(node.typeArguments![0], program);
      const constraintsArray = Array.isArray(constraints) ? constraints : [constraints];
      if (typeof constraintsArray[0] === 'string') {
        return constraintsArray.reduce((acc, c) => ({ ...acc, [c]: descriptor }), {});
      }
    }

    // Support `Pick<SOMETHING, 'prop1' | 'prop2'>`
    if (symbolName === 'Pick') {
      const parentDescriptor = getDescriptor(node.typeArguments![0], program);
      const pickPropNames = getConstraints(node.typeArguments![1], program);
      return pick(parentDescriptor, pickPropNames);
    }
    // Support `Omit<SOMETHING, 'prop1' | 'prop2'>`
    if (symbolName === 'Omit') {
      const parentDescriptor = getDescriptor(node.typeArguments![0], program);
      const omitPropNames = getConstraints(node.typeArguments![1], program);
      return omit(parentDescriptor, omitPropNames);
    }

    const declaration = (symbol?.getDeclarations() || [])[0];
    if (declaration) {
      return getDescriptor(declaration, program);
    }
    return getDescriptor(node.typeName, program);
  }

  if (ts.isImportSpecifier(node) || ts.isExportSpecifier(node)) {
    const source = node.getSourceFile();
    const importedModuleName = getModuleSpecifier(node);

    const declarationSource = getResolvedModuleSourceFile(source, program, importedModuleName);
    const declarationNode = getIdentifierDeclarationFromSource(node.name, declarationSource);
    return getDescriptor(declarationNode, program);
  }

  if (ts.isArrayTypeNode(node)) {
    return { items: getDescriptor(node.elementType, program) };
  }

  if (ts.isLiteralTypeNode(node)) {
    return {
      kind: node.literal.kind,
      type: ts.SyntaxKind[node.literal.kind] as keyof typeof ts.SyntaxKind,
    };
  }

  if (ts.isUnionTypeNode(node)) {
    const types = node.types.filter(discardNullOrUndefined);

    const kinds = types
      .map((typeNode) => getDescriptor(typeNode, program))
      .filter(discardNullOrUndefined);

    const uniqueKinds = uniqBy(kinds, 'kind');

    if (uniqueKinds.length !== 1) {
      throw Error('Mapping does not support conflicting union types.');
    }

    return uniqueKinds[0];
  }

  // Support `type MyUsageType = SomethingElse`
  if (ts.isTypeAliasDeclaration(node)) {
    return getDescriptor(node.type, program);
  }

  // Support `&` unions
  if (ts.isIntersectionTypeNode(node)) {
    return node.types.reduce(
      (acc, unionNode) => ({ ...acc, ...getDescriptor(unionNode, program) }),
      {}
    );
  }

  switch (node.kind) {
    case ts.SyntaxKind.NumberKeyword:
    case ts.SyntaxKind.BooleanKeyword:
    case ts.SyntaxKind.StringKeyword:
    case ts.SyntaxKind.SetKeyword:
      return { kind: node.kind, type: ts.SyntaxKind[node.kind] as keyof typeof ts.SyntaxKind };
    case ts.SyntaxKind.UnionType:
    case ts.SyntaxKind.AnyKeyword:
    default:
      throw new Error(`Unknown type ${ts.SyntaxKind[node.kind]}`);
  }
}