UNPKG

ts-json-schema-generator

Version:

Generate JSON schema from your Typescript sources

216 lines 9.27 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaGenerator = void 0; const tslib_1 = require("tslib"); const typescript_1 = tslib_1.__importDefault(require("typescript")); const Errors_js_1 = require("./Error/Errors.js"); const NodeParser_js_1 = require("./NodeParser.js"); const DefinitionType_js_1 = require("./Type/DefinitionType.js"); const hasJsDocTag_js_1 = require("./Utils/hasJsDocTag.js"); const removeUnreachable_js_1 = require("./Utils/removeUnreachable.js"); const symbolAtNode_js_1 = require("./Utils/symbolAtNode.js"); class SchemaGenerator { program; nodeParser; typeFormatter; config; constructor(program, nodeParser, typeFormatter, config) { this.program = program; this.nodeParser = nodeParser; this.typeFormatter = typeFormatter; this.config = config; } createSchema(fullName) { const rootNodes = this.getRootNodes(fullName); return this.createSchemaFromNodes(rootNodes); } createSchemaFromNodes(rootNodes) { const roots = rootNodes.map((rootNode) => ({ rootNode: rootNode, rootType: this.nodeParser.createType(rootNode, new NodeParser_js_1.Context()), })); const rootTypeDefinition = roots.length === 1 ? this.getRootTypeDefinition(roots[0].rootType, roots[0].rootNode) : undefined; const definitions = {}; for (const root of roots) { try { this.appendRootChildDefinitions(root.rootType, definitions); } catch (error) { throw Errors_js_1.UnhandledError.from("Unhandled error while appending Child Type Definition.", root.rootNode, error); } } const reachableDefinitions = (0, removeUnreachable_js_1.removeUnreachable)(rootTypeDefinition, definitions); return { ...(this.config?.schemaId ? { $id: this.config.schemaId } : {}), $schema: "http://json-schema.org/draft-07/schema#", ...(rootTypeDefinition ?? {}), definitions: reachableDefinitions, }; } getRootNodes(fullName) { if (fullName && fullName !== "*") { return [this.findNamedNode(fullName)]; } const rootFileNames = this.program.getRootFileNames(); const rootSourceFiles = this.program .getSourceFiles() .filter((sourceFile) => rootFileNames.includes(sourceFile.fileName)); const rootNodes = new Map(); this.appendTypes(rootSourceFiles, this.program.getTypeChecker(), rootNodes); return [...rootNodes.values()]; } findNamedNode(fullName) { const typeChecker = this.program.getTypeChecker(); const allTypes = new Map(); const { projectFiles, externalFiles } = this.partitionFiles(); this.appendTypes(projectFiles, typeChecker, allTypes); if (allTypes.has(fullName)) { return allTypes.get(fullName); } this.appendTypes(externalFiles, typeChecker, allTypes); if (allTypes.has(fullName)) { return allTypes.get(fullName); } throw new Errors_js_1.RootlessError(fullName); } getRootTypeDefinition(rootType, rootNode) { try { return this.typeFormatter.getDefinition(rootType); } catch (error) { throw Errors_js_1.UnhandledError.from("Unhandled error while creating Root Type Definition.", rootNode, error); } } appendRootChildDefinitions(rootType, childDefinitions) { const seen = new Set(); const children = this.typeFormatter .getChildren(rootType) .filter((child) => child instanceof DefinitionType_js_1.DefinitionType) .filter((child) => { if (!seen.has(child.getId())) { seen.add(child.getId()); return true; } return false; }); const ids = new Map(); for (const child of children) { const name = child.getName(); const previousId = ids.get(name); const childId = child.getId().replace(/def-/g, ""); if (previousId && childId !== previousId) { throw new Errors_js_1.MultipleDefinitionsError(name, child, children.find((c) => c.getId() === previousId)); } ids.set(name, childId); } children.reduce((definitions, child) => { const name = child.getName(); if (!(name in definitions)) { definitions[name] = this.typeFormatter.getDefinition(child.getType()); } return definitions; }, childDefinitions); } partitionFiles() { const projectFiles = new Array(); const externalFiles = new Array(); for (const sourceFile of this.program.getSourceFiles()) { const destination = sourceFile.fileName.includes("/node_modules/") ? externalFiles : projectFiles; destination.push(sourceFile); } return { projectFiles, externalFiles }; } appendTypes(sourceFiles, typeChecker, types) { for (const sourceFile of sourceFiles) { this.inspectNode(sourceFile, typeChecker, types); } } inspectNode(node, typeChecker, allTypes) { if (typescript_1.default.isVariableDeclaration(node)) { if (node.initializer?.kind === typescript_1.default.SyntaxKind.ArrowFunction || node.initializer?.kind === typescript_1.default.SyntaxKind.FunctionExpression) { this.inspectNode(node.initializer, typeChecker, allTypes); } return; } if (typescript_1.default.isInterfaceDeclaration(node) || typescript_1.default.isClassDeclaration(node) || typescript_1.default.isEnumDeclaration(node) || typescript_1.default.isTypeAliasDeclaration(node)) { if ((this.config?.expose === "all" || this.isExportType(node)) && !this.isGenericType(node)) { allTypes.set(this.getFullName(node, typeChecker), node); return; } return; } if (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isFunctionExpression(node) || typescript_1.default.isArrowFunction(node) || typescript_1.default.isConstructorTypeNode(node)) { allTypes.set(this.getFullName(node, typeChecker), node); return; } if (typescript_1.default.isExportSpecifier(node)) { const symbol = typeChecker.getExportSpecifierLocalTargetSymbol(node); if (symbol?.declarations?.length === 1) { const declaration = symbol.declarations[0]; if (typescript_1.default.isImportSpecifier(declaration)) { const type = typeChecker.getTypeAtLocation(declaration); if (type.symbol?.declarations?.length === 1) { this.inspectNode(type.symbol.declarations[0], typeChecker, allTypes); } } else { this.inspectNode(declaration, typeChecker, allTypes); } } return; } if (typescript_1.default.isExportDeclaration(node)) { if (!typescript_1.default.isExportDeclaration(node)) { return; } if (!node.moduleSpecifier) { return; } const symbol = typeChecker.getSymbolAtLocation(node.moduleSpecifier); if (!symbol || !symbol.declarations) { return; } for (const source of symbol.declarations) { const sourceSymbol = typeChecker.getSymbolAtLocation(source); if (!sourceSymbol) { return; } const moduleExports = typeChecker.getExportsOfModule(sourceSymbol); for (const moduleExport of moduleExports) { const nodes = moduleExport.declarations || (!!moduleExport.valueDeclaration && [moduleExport.valueDeclaration]); if (!nodes) { return; } for (const subnodes of nodes) { this.inspectNode(subnodes, typeChecker, allTypes); } } } return; } typescript_1.default.forEachChild(node, (subnode) => this.inspectNode(subnode, typeChecker, allTypes)); } isExportType(node) { if (this.config?.jsDoc !== "none" && (0, hasJsDocTag_js_1.hasJsDocTag)(node, "internal")) { return false; } return !!node.localSymbol?.exportSymbol; } isGenericType(node) { return !!(node.typeParameters && node.typeParameters.length > 0); } getFullName(node, typeChecker) { return typeChecker.getFullyQualifiedName((0, symbolAtNode_js_1.symbolAtNode)(node)).replace(/".*"\./, ""); } } exports.SchemaGenerator = SchemaGenerator; //# sourceMappingURL=SchemaGenerator.js.map