UNPKG

ts-json-schema-generator

Version:

Generate JSON schema from your Typescript sources

167 lines 7.65 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaGenerator = void 0; const typescript_1 = __importDefault(require("typescript")); const NoRootTypeError_1 = require("./Error/NoRootTypeError"); const NodeParser_1 = require("./NodeParser"); const DefinitionType_1 = require("./Type/DefinitionType"); const symbolAtNode_1 = require("./Utils/symbolAtNode"); const notUndefined_1 = require("./Utils/notUndefined"); const removeUnreachable_1 = require("./Utils/removeUnreachable"); const hasJsDocTag_1 = require("./Utils/hasJsDocTag"); class SchemaGenerator { 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) { var _a; const rootTypes = rootNodes .map((rootNode) => { return this.nodeParser.createType(rootNode, new NodeParser_1.Context()); }) .filter(notUndefined_1.notUndefined); const rootTypeDefinition = rootTypes.length === 1 ? this.getRootTypeDefinition(rootTypes[0]) : undefined; const definitions = {}; rootTypes.forEach((rootType) => this.appendRootChildDefinitions(rootType, definitions)); const reachableDefinitions = (0, removeUnreachable_1.removeUnreachable)(rootTypeDefinition, definitions); return { ...(((_a = this.config) === null || _a === void 0 ? void 0 : _a.schemaId) ? { $id: this.config.schemaId } : {}), $schema: "http://json-schema.org/draft-07/schema#", ...(rootTypeDefinition !== null && rootTypeDefinition !== void 0 ? rootTypeDefinition : {}), definitions: reachableDefinitions, }; } getRootNodes(fullName) { if (fullName && fullName !== "*") { return [this.findNamedNode(fullName)]; } else { 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 NoRootTypeError_1.NoRootTypeError(fullName); } getRootTypeDefinition(rootType) { return this.typeFormatter.getDefinition(rootType); } appendRootChildDefinitions(rootType, childDefinitions) { const seen = new Set(); const children = this.typeFormatter .getChildren(rootType) .filter((child) => child instanceof DefinitionType_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 Error(`Type "${name}" has multiple definitions.`); } 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) { var _a, _b, _c; switch (node.kind) { case typescript_1.default.SyntaxKind.VariableDeclaration: { const variableDeclarationNode = node; if (((_a = variableDeclarationNode.initializer) === null || _a === void 0 ? void 0 : _a.kind) === typescript_1.default.SyntaxKind.ArrowFunction || ((_b = variableDeclarationNode.initializer) === null || _b === void 0 ? void 0 : _b.kind) === typescript_1.default.SyntaxKind.FunctionExpression) { this.inspectNode(variableDeclarationNode.initializer, typeChecker, allTypes); } return; } case typescript_1.default.SyntaxKind.InterfaceDeclaration: case typescript_1.default.SyntaxKind.ClassDeclaration: case typescript_1.default.SyntaxKind.EnumDeclaration: case typescript_1.default.SyntaxKind.TypeAliasDeclaration: if (((_c = this.config) === null || _c === void 0 ? void 0 : _c.expose) === "all" || (this.isExportType(node) && !this.isGenericType(node))) { allTypes.set(this.getFullName(node, typeChecker), node); return; } return; case typescript_1.default.SyntaxKind.FunctionDeclaration: case typescript_1.default.SyntaxKind.FunctionExpression: case typescript_1.default.SyntaxKind.ArrowFunction: allTypes.set(`NamedParameters<typeof ${this.getFullName(node, typeChecker)}>`, node); return; default: typescript_1.default.forEachChild(node, (subnode) => this.inspectNode(subnode, typeChecker, allTypes)); return; } } isExportType(node) { var _a; if (((_a = this.config) === null || _a === void 0 ? void 0 : _a.jsDoc) !== "none" && (0, hasJsDocTag_1.hasJsDocTag)(node, "internal")) { return false; } const localSymbol = (0, symbolAtNode_1.localSymbolAtNode)(node); return localSymbol ? "exportSymbol" in localSymbol : false; } isGenericType(node) { return !!(node.typeParameters && node.typeParameters.length > 0); } getFullName(node, typeChecker) { const symbol = (0, symbolAtNode_1.symbolAtNode)(node); return typeChecker.getFullyQualifiedName(symbol).replace(/".*"\./, ""); } } exports.SchemaGenerator = SchemaGenerator; //# sourceMappingURL=SchemaGenerator.js.map