UNPKG

ts-json-schema-generator

Version:

Generate JSON schema from your Typescript sources

104 lines (88 loc) 4.04 kB
import type { MethodSignature, PropertySignature } from "typescript"; import ts from "typescript"; import type { Context, NodeParser } from "../NodeParser.js"; import type { SubNodeParser } from "../SubNodeParser.js"; import type { BaseType } from "../Type/BaseType.js"; import { NeverType } from "../Type/NeverType.js"; import { ObjectProperty, ObjectType } from "../Type/ObjectType.js"; import type { ReferenceType } from "../Type/ReferenceType.js"; import { isNodeHidden } from "../Utils/isHidden.js"; import { getKey } from "../Utils/nodeKey.js"; export class TypeLiteralNodeParser implements SubNodeParser { public constructor( protected typeChecker: ts.TypeChecker, protected childNodeParser: NodeParser, protected readonly additionalProperties: boolean, ) {} public supportsNode(node: ts.TypeLiteralNode): boolean { return node.kind === ts.SyntaxKind.TypeLiteral; } public createType(node: ts.TypeLiteralNode, context: Context, reference?: ReferenceType): BaseType { const id = this.getTypeId(node, context); if (reference) { reference.setId(id); reference.setName(id); } const properties = this.getProperties(node, context); if (properties === undefined) { return new NeverType(); } return new ObjectType(id, [], properties, this.getAdditionalProperties(node, context)); } protected getProperties(node: ts.TypeLiteralNode, context: Context): ObjectProperty[] | undefined { let hasRequiredNever = false; const properties = node.members .filter( (element): element is PropertySignature | MethodSignature => ts.isPropertySignature(element) || ts.isMethodSignature(element), ) .filter((propertyNode) => !isNodeHidden(propertyNode)) .map( (propertyNode) => new ObjectProperty( this.getPropertyName(propertyNode.name), this.childNodeParser.createType(propertyNode.type!, context), !propertyNode.questionToken, ), ) .filter((prop) => { const type = prop.getType(); if (prop.isRequired() && type instanceof NeverType) { hasRequiredNever = true; } return !(type instanceof NeverType); }); if (hasRequiredNever) { return undefined; } return properties; } protected getAdditionalProperties(node: ts.TypeLiteralNode, context: Context): BaseType | boolean { const indexSignature = node.members.find(ts.isIndexSignatureDeclaration); if (!indexSignature) { return this.additionalProperties; } return this.childNodeParser.createType(indexSignature.type, context) ?? this.additionalProperties; } protected getTypeId(node: ts.Node, context: Context): string { return `structure-${getKey(node, context)}`; } protected getPropertyName(propertyName: ts.PropertyName): string { if (propertyName.kind === ts.SyntaxKind.ComputedPropertyName) { const symbol = this.typeChecker.getSymbolAtLocation(propertyName); if (symbol) { return symbol.getName(); } } try { return propertyName.getText(); } catch { // When propertyName was programmatically created, it doesn't have a source file. // Then, getText() will throw an error. But, for programmatically created nodes,` // `escapedText` or `text` is available. // Only `text` will be available when propertyName contains strange characters and it cannot be escaped // or if it is a number. return ((propertyName as ts.Identifier).escapedText as string) ?? (propertyName as ts.StringLiteral).text; } } }