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