UNPKG

ts-json-schema-generator

Version:

Generate JSON schema from your Typescript sources

102 lines (90 loc) 4.38 kB
import ts from "typescript"; import { Context, NodeParser } from "../NodeParser"; import { SubNodeParser } from "../SubNodeParser"; import { BaseType } from "../Type/BaseType"; import { isAssignableTo } from "../Utils/isAssignableTo"; import { narrowType } from "../Utils/narrowType"; import { UnionType } from "../Type/UnionType"; export class ConditionalTypeNodeParser implements SubNodeParser { public constructor(private typeChecker: ts.TypeChecker, private childNodeParser: NodeParser) {} public supportsNode(node: ts.ConditionalTypeNode): boolean { return node.kind === ts.SyntaxKind.ConditionalType; } public createType(node: ts.ConditionalTypeNode, context: Context): BaseType | undefined { const checkType = this.childNodeParser.createType(node.checkType, context); const extendsType = this.childNodeParser.createType(node.extendsType, context); const checkTypeParameterName = this.getTypeParameterName(node.checkType); // If check-type is not a type parameter then condition is very simple, no type narrowing needed if (checkTypeParameterName == null) { const result = isAssignableTo(extendsType, checkType); return this.childNodeParser.createType(result ? node.trueType : node.falseType, context); } // Narrow down check type for both condition branches const trueCheckType = narrowType(checkType, (type) => isAssignableTo(extendsType, type)); const falseCheckType = narrowType(checkType, (type) => !isAssignableTo(extendsType, type)); // Follow the relevant branches and return the results from them const results: BaseType[] = []; if (trueCheckType !== undefined) { const result = this.childNodeParser.createType( node.trueType, this.createSubContext(node, checkTypeParameterName, trueCheckType, context) ); if (result) { results.push(result); } } if (falseCheckType !== undefined) { const result = this.childNodeParser.createType( node.falseType, this.createSubContext(node, checkTypeParameterName, falseCheckType, context) ); if (result) { results.push(result); } } return new UnionType(results).normalize(); } /** * Returns the type parameter name of the given type node if any. * * @param node - The type node for which to return the type parameter name. * @return The type parameter name or null if specified type node is not a type parameter. */ private getTypeParameterName(node: ts.TypeNode): string | null { if (ts.isTypeReferenceNode(node)) { const typeSymbol = this.typeChecker.getSymbolAtLocation(node.typeName)!; if (typeSymbol.flags & ts.SymbolFlags.TypeParameter) { return typeSymbol.name; } } return null; } /** * Creates a sub context for evaluating the sub types of the conditional type. A sub context is needed in case * the check-type is a type parameter which is then narrowed down by the extends-type. * * @param node - The reference node for the new context. * @param checkTypeParameterName - The type parameter name of the check-type. * @param narrowedCheckType - The narrowed down check type to use for the type parameter in sub parsers. * @return The created sub context. */ private createSubContext( node: ts.ConditionalTypeNode, checkTypeParameterName: string, narrowedCheckType: BaseType, parentContext: Context ): Context { const subContext = new Context(node); // Set new narrowed type for check type parameter subContext.pushParameter(checkTypeParameterName); subContext.pushArgument(narrowedCheckType); // Copy all other type parameters from parent context parentContext.getParameters().forEach((parentParameter) => { if (parentParameter !== checkTypeParameterName) { subContext.pushParameter(parentParameter); subContext.pushArgument(parentContext.getArgument(parentParameter)); } }); return subContext; } }