UNPKG

tsbuffer-proto-generator

Version:

TSBuffer Proto Generator ---

1,194 lines (1,186 loc) 69.9 kB
/*! * TSBuffer Proto Generator v1.7.2 * ----------------------------------------- * MIT LICENSE * KingWorks (C) Copyright 2022 * https://github.com/k8w/tsbuffer */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); require('k8w-extend-native'); var fs = require('fs'); var path = require('path'); var tsbufferSchema = require('tsbuffer-schema'); var ts = require('typescript'); var k8wCrypto = require('k8w-crypto'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); var path__default = /*#__PURE__*/_interopDefaultLegacy(path); var ts__default = /*#__PURE__*/_interopDefaultLegacy(ts); const SCALAR_TYPES = [ 'int', 'uint', 'double', 'bigint', 'bigint64', 'biguint64', ].sort(); const BUFFER_TYPES = [ 'ArrayBuffer', 'Int8Array', 'Int16Array', 'Int32Array', 'BigInt64Array', 'Uint8Array', 'Uint16Array', 'Uint32Array', 'BigUint64Array', 'Float32Array', 'Float64Array', ].sort(); /** * 提取出有用的AST */ class AstParser { constructor(options) { var _a; this.keepComment = false; this.keepComment = (_a = options === null || options === void 0 ? void 0 : options.keepComment) !== null && _a !== void 0 ? _a : false; } /** * 解析整个文件 * @param content */ parseScript(content, logger) { let output = {}; // 1. get flatten nodes let src = ts__default["default"].createSourceFile('', content, ts__default["default"].ScriptTarget.ES3, true, ts__default["default"].ScriptKind.TS); let nodes = this.getFlattenNodes(src, true); // 2. parse imports let imports = this.getScriptImports(src); // 3. node2schema for (let name in nodes) { output[name] = { isExport: nodes[name].isExport, schema: this.node2schema(nodes[name].node, imports, logger, undefined, nodes[name].comment) }; } return output; } /** 解析顶层imports */ getScriptImports(src) { let output = {}; src.forEachChild(v => { if (v.kind !== ts__default["default"].SyntaxKind.ImportDeclaration) { return; } let node = v; // 仅支持从字符串路径import if (node.moduleSpecifier.kind !== ts__default["default"].SyntaxKind.StringLiteral) { return; } let importPath = node.moduleSpecifier.text; // import from 'xxx' if (!node.importClause) { return; } // default: import A from 'xxx' if (node.importClause.name) { output[node.importClause.name.text] = { path: importPath, targetName: 'default' }; } // elements if (node.importClause.namedBindings && node.importClause.namedBindings.kind === ts__default["default"].SyntaxKind.NamedImports && node.importClause.namedBindings.elements) { node.importClause.namedBindings.elements.forEach(elem => { // import { A as B } from 'xxx' if (elem.propertyName) { output[elem.name.text] = { path: importPath, targetName: elem.propertyName.text }; } // import { A } from 'xxx' else { output[elem.name.text] = { path: importPath, targetName: elem.name.text }; } }); // 暂不支持:import * as A from 'xxx' } }); return output; } /** * 将Node展平(包括Namespace里的) * @param node * @param isExport 当node是Namespace时,其外层是否处于export */ getFlattenNodes(node, isExport = false) { let output = {}; // 检测到ExportDeclaration的项目,会在最后统一设为isExport let exportNames = {}; // 检测到的顶级Modules(namespace) let namespaceExports = {}; node.forEachChild(v => { var _a, _b; // 类型定义 if (ts__default["default"].isInterfaceDeclaration(v) || ts__default["default"].isTypeAliasDeclaration(v) || ts__default["default"].isEnumDeclaration(v)) { // 外层允许export,且自身有被export let _isExport = Boolean(isExport && v.modifiers && v.modifiers.findIndex(v1 => v1.kind === ts__default["default"].SyntaxKind.ExportKeyword) > -1); // 是否为export default let _isExportDefault = _isExport && v.modifiers.findIndex(v1 => v1.kind === ts__default["default"].SyntaxKind.DefaultKeyword) > -1; output[v.name.text] = { node: v.kind === ts__default["default"].SyntaxKind.TypeAliasDeclaration ? v.type : v, comment: (_b = (_a = v.jsDoc) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.comment, // export default的情况,本体作为不isExport,取而代之生成一个名为default的TypeReference来export isExport: _isExport && !_isExportDefault }; // 生成TypeReference if (_isExportDefault) { output['default'] = { node: ts__default["default"].createTypeReferenceNode(v.name, undefined), isExport: true }; } } // namespace else if (ts__default["default"].isModuleDeclaration(v) && (v.flags & ts__default["default"].NodeFlags.Namespace)) { if (v.body && v.body.kind === ts__default["default"].SyntaxKind.ModuleBlock) { // 外层允许export,且自身有被export let _isExport = Boolean(isExport && v.modifiers && v.modifiers.findIndex(v1 => v1.kind === ts__default["default"].SyntaxKind.ExportKeyword) > -1); // 递归生成子树 let children = this.getFlattenNodes(v.body, true); namespaceExports[v.name.text] = {}; for (let item of Object.entries(children)) { // 临时存储内部export namespaceExports[v.name.text][item[0]] = item[1].isExport; // 实际export还要考虑外部(_isExport) item[1].isExport = item[1].isExport && _isExport; } // 展平子树 Object.entries(children).forEach(v1 => { // 转换name为 A.B.C 的形式 output[v.name.text + '.' + v1[0]] = v1[1]; }); } } // export else if (ts__default["default"].isExportDeclaration(v)) { if (!v.exportClause) { return; } if ('elements' in v.exportClause) { v.exportClause && v.exportClause.elements.forEach(elem => { // export { A as B } if (elem.propertyName) { output[elem.name.text] = { node: ts__default["default"].createTypeReferenceNode(elem.propertyName.text, undefined), isExport: true }; } // export { A } else { exportNames[elem.name.text] = true; } }); } } // export default else if (ts__default["default"].isExportAssignment(v)) { // 暂不支持 export = XXX if (v.isExportEquals) { return; } output['default'] = { node: ts__default["default"].createTypeReferenceNode(v.expression.getText(), undefined), isExport: true }; } }); // exportNames // 后续export declaration的 Object.keys(exportNames).forEach(v => { if (output[v]) { output[v].isExport = true; } }); // export default namespace 的情况 if (output['default'] && ts__default["default"].isTypeReferenceNode(output['default'].node)) { let typeName = this._typeNameToString(output['default'].node.typeName); // 确实是export default namespace if (namespaceExports[typeName]) { delete output['default']; // 遍历所有 typeName.XXX for (let key in namespaceExports[typeName]) { // 内部也export的 if (namespaceExports[typeName][key]) { // 增加 default.XXX 到 typeName.XXX 的引用 output['default.' + key] = { node: ts__default["default"].createTypeReferenceNode(typeName + '.' + key, undefined), isExport: true }; } } } } return output; } node2schema(node, imports, logger, fullText, comment) { let schema = this._node2schema(node, imports, logger); if (this.keepComment) { if (comment) { schema.comment = comment; } else { if (fullText === undefined) { fullText = node.getFullText(); } fullText = fullText.trim(); if (fullText.startsWith('/**')) { let endPos = fullText.indexOf('*/'); if (endPos > -1) { let comment = fullText.substr(3, endPos - 3).trim().split('\n') .map(v => v.trim().replace(/^\* ?/, '')).filter(v => !!v).join('\n'); schema.comment = comment; } } } } return schema; } _node2schema(node, imports, logger) { var _a, _b; // 去除外层括弧 while (ts__default["default"].isParenthesizedTypeNode(node)) { node = node.type; } // AnyType if (node.kind === ts__default["default"].SyntaxKind.AnyKeyword || node.kind === ts__default["default"].SyntaxKind.UnknownKeyword) { return { type: tsbufferSchema.SchemaType.Any }; } // BufferType if (ts__default["default"].isTypeReferenceNode(node)) { let ref = this._getReferenceTypeSchema(node.typeName, imports); if (BUFFER_TYPES.binarySearch(ref.target) > -1) { let output = { type: tsbufferSchema.SchemaType.Buffer }; let target = ref.target; if (target !== 'ArrayBuffer') { output.arrayType = target; } return output; } } // BooleanType if (node.kind === ts__default["default"].SyntaxKind.BooleanKeyword) { return { type: tsbufferSchema.SchemaType.Boolean }; } // ObjectType if (node.kind === ts__default["default"].SyntaxKind.ObjectKeyword) { return { type: tsbufferSchema.SchemaType.Object }; } // NumberType if (node.kind === ts__default["default"].SyntaxKind.NumberKeyword) { return { type: tsbufferSchema.SchemaType.Number }; } else if (node.kind === ts__default["default"].SyntaxKind.BigIntKeyword) { return { type: tsbufferSchema.SchemaType.Number, scalarType: 'bigint' }; } // Scalar value types if (ts__default["default"].isTypeReferenceNode(node) && ts__default["default"].isIdentifier(node.typeName) && SCALAR_TYPES.binarySearch(node.typeName.text) > -1) { return { type: tsbufferSchema.SchemaType.Number, scalarType: node.typeName.text }; } // StringType if (node.kind === ts__default["default"].SyntaxKind.StringKeyword) { return { type: tsbufferSchema.SchemaType.String }; } // ArrayType: xxx[] if (ts__default["default"].isArrayTypeNode(node)) { return { type: tsbufferSchema.SchemaType.Array, elementType: this.node2schema(node.elementType, imports, logger, node.getFullText()) }; } // ArrayType: Array<T> if (this._isLocalReference(node, imports, 'Array') && node.typeArguments) { return { type: tsbufferSchema.SchemaType.Array, elementType: this.node2schema(node.typeArguments[0], imports, logger, node.getFullText()) }; } // TupleType if (ts__default["default"].isTupleTypeNode(node)) { let optionalStartIndex; let output = { type: tsbufferSchema.SchemaType.Tuple, elementTypes: node.elements.map((v, i) => { if (v.kind === ts__default["default"].SyntaxKind.OptionalType) { if (optionalStartIndex === undefined) { optionalStartIndex = i; } return this.node2schema(v.type, imports, logger, v.getFullText()); } else { return this.node2schema(v, imports, logger, v.getFullText()); } }) }; if (optionalStartIndex !== undefined) { output.optionalStartIndex = optionalStartIndex; } return output; } // LiteralType // LiteralType: string | number | boolean if (ts__default["default"].isLiteralTypeNode(node)) { if (ts__default["default"].isStringLiteral(node.literal)) { return { type: tsbufferSchema.SchemaType.Literal, literal: node.literal.text }; } else if (ts__default["default"].isNumericLiteral(node.literal)) { return { type: tsbufferSchema.SchemaType.Literal, literal: parseFloat(node.literal.text) }; } else if (node.literal.kind === ts__default["default"].SyntaxKind.TrueKeyword) { return { type: tsbufferSchema.SchemaType.Literal, literal: true }; } else if (node.literal.kind === ts__default["default"].SyntaxKind.FalseKeyword) { return { type: tsbufferSchema.SchemaType.Literal, literal: false }; } else if (node.literal.kind === ts__default["default"].SyntaxKind.NullKeyword) { return { type: tsbufferSchema.SchemaType.Literal, literal: null }; } } // Literal: undefined else if (node.kind === ts__default["default"].SyntaxKind.UndefinedKeyword) { return { type: tsbufferSchema.SchemaType.Literal, literal: undefined }; } // EnumType if (ts__default["default"].isEnumDeclaration(node)) { let initializer = 0; let schema = { type: tsbufferSchema.SchemaType.Enum, members: node.members.map((v, i) => { if (v.initializer) { if (ts__default["default"].isStringLiteral(v.initializer)) { initializer = NaN; return { id: i, name: v.name.getText(), value: v.initializer.text }; } else if (ts__default["default"].isNumericLiteral(v.initializer)) { initializer = parseFloat(v.initializer.text); return { id: i, name: v.name.getText(), value: initializer++ }; } // 负数 else if (ts__default["default"].isPrefixUnaryExpression(v.initializer) && v.initializer.operator === ts__default["default"].SyntaxKind.MinusToken) { initializer = parseFloat(v.initializer.operand.getText()) * -1; return { id: i, name: v.name.getText(), value: initializer++ }; } else { logger === null || logger === void 0 ? void 0 : logger.log('initializer', v.initializer); throw new Error('Enum initializer type error: ' + ts__default["default"].SyntaxKind[v.initializer.kind]); } } else { return { id: i, name: v.name.getText(), value: initializer++ }; } }) }; (_a = this.pre) === null || _a === void 0 ? void 0 : _a.preEnumSchemas.push(schema); return schema; } // InterfaceType if (ts__default["default"].isInterfaceDeclaration(node) || ts__default["default"].isTypeLiteralNode(node)) { // extends let extendsInterface; if (ts__default["default"].isInterfaceDeclaration(node) && node.heritageClauses) { extendsInterface = []; node.heritageClauses.forEach(v => { v.types.forEach(type => { let extendsType = this._node2schema(type, imports, logger); extendsInterface.push(extendsType); }); }); } let properties = []; let indexSignature; node.members.forEach((member, i) => { // properties if (ts__default["default"].isPropertySignature(member)) { if (ts__default["default"].isComputedPropertyName(member.name)) { throw new Error('ComputedPropertyName is not supported at now'); } if (!member.type) { throw new Error(`Field must have a type: ${member.name.text}`); } let property = { id: i, name: member.name.text, type: this.node2schema(member.type, imports, logger, member.getFullText()) }; // optional if (member.questionToken) { property.optional = true; } properties.push(property); } // indexSignature else if (ts__default["default"].isIndexSignatureDeclaration(member)) { if (!member.type || !member.parameters[0].type) { throw new Error('Error index signature: ' + member.getText()); } let keyType; if (member.parameters[0].type.kind === ts__default["default"].SyntaxKind.NumberKeyword) { keyType = 'Number'; } else { keyType = 'String'; } indexSignature = { keyType: keyType, type: this.node2schema(member.type, imports, logger, member.getFullText()) }; } }); // output let output = { type: tsbufferSchema.SchemaType.Interface }; if (extendsInterface) { output.extends = extendsInterface.map((v, i) => ({ id: i, type: v })); } if (properties.length) { output.properties = properties; } if (indexSignature) { output.indexSignature = indexSignature; } return output; } // IndexedAccessType if (ts__default["default"].isIndexedAccessTypeNode(node)) { // A['a'] if (ts__default["default"].isLiteralTypeNode(node.indexType)) { let index; if (ts__default["default"].isStringLiteral(node.indexType.literal) || ts__default["default"].isNumericLiteral(node.indexType.literal)) { index = node.indexType.literal.text; } else if (node.indexType.literal.kind === ts__default["default"].SyntaxKind.TrueKeyword || node.indexType.literal.kind === ts__default["default"].SyntaxKind.FalseKeyword || node.indexType.literal.kind === ts__default["default"].SyntaxKind.NullKeyword || node.indexType.literal.kind === ts__default["default"].SyntaxKind.UndefinedKeyword) { index = node.indexType.literal.getText(); } else { throw new Error(`Error indexType literal: ${node.getText()}`); } let objectType = this.node2schema(node.objectType, imports, logger, node.getFullText()); if (!this._isInterfaceReference(objectType)) { throw new Error(`ObjectType for IndexedAccess must be interface or interface reference`); } return { type: tsbufferSchema.SchemaType.IndexedAccess, index: index, objectType: objectType }; } // A['a' | 'b'] else if (ts__default["default"].isUnionTypeNode(node.indexType)) ; else { throw new Error(`Error IndexedAccessType indexType: ${node.getText()}`); } } // UnionType if (ts__default["default"].isUnionTypeNode(node)) { return { type: tsbufferSchema.SchemaType.Union, members: node.types.map((v, i) => ({ id: i, type: this.node2schema(v, imports, logger, v.getFullText()) })) }; } // IntersectionType if (ts__default["default"].isIntersectionTypeNode(node)) { return { type: tsbufferSchema.SchemaType.Intersection, members: node.types.map((v, i) => ({ id: i, type: this.node2schema(v, imports, logger, v.getFullText()) })) }; } // PickType & OmitType if (this._isLocalReference(node, imports, ['Pick', 'Omit'])) { let nodeName; if (ts__default["default"].isTypeReferenceNode(node)) { nodeName = node.typeName.getText(); } else if (ts__default["default"].isExpressionWithTypeArguments(node)) { nodeName = node.expression.getText(); } else { // @ts-expect-error throw new Error(`Invalid ts.Node kind for Pick/Omit: ${node.kind}`); } if (!node.typeArguments || node.typeArguments.length != 2) { throw new Error(`Illeagle ${nodeName}Type: ` + node.getText()); } let target = this.node2schema(node.typeArguments[0], imports, logger, node.getFullText()); if (!this._isInterfaceReference(target) && target.type !== tsbufferSchema.SchemaType.Union && target.type !== tsbufferSchema.SchemaType.Intersection) { throw new Error(`Illeagle ${nodeName}Type: ` + node.getText()); } let preKey = this._getPreKey(this.node2schema(node.typeArguments[1], imports, logger, node.getFullText()), logger); let output = Object.assign({ target: target, keys: [], pre: { key: preKey } }, nodeName === 'Pick' ? { type: tsbufferSchema.SchemaType.Pick } : { type: tsbufferSchema.SchemaType.Omit }); (_b = this.pre) === null || _b === void 0 ? void 0 : _b.prePickOmitSchemas.push(output); return output; } // PartialType if (this._isLocalReference(node, imports, 'Partial')) { if (!node.typeArguments || node.typeArguments.length != 1) { throw new Error('Illeagle PartialType: ' + node.getText()); } let target = this.node2schema(node.typeArguments[0], imports, logger, node.getFullText()); if (!this._isInterfaceReference(target)) { throw new Error('Illeagle PartialType: ' + node.getText()); } return { type: tsbufferSchema.SchemaType.Partial, target: target }; } // OverwriteType if (ts__default["default"].isTypeReferenceNode(node) && this._typeNameToString(node.typeName) === 'Overwrite') { if (!node.typeArguments || node.typeArguments.length != 2) { throw new Error(`Illeagle OverwriteType: ` + node.getText()); } let target = this.node2schema(node.typeArguments[0], imports, logger, node.getFullText()); if (!this._isInterfaceReference(target)) { throw new Error(`Illeagle OverwriteType: ` + node.getText()); } let overwrite = this.node2schema(node.typeArguments[1], imports, logger, node.getFullText()); if (!this._isInterfaceReference(overwrite)) { throw new Error(`Illeagle OverwriteType: ` + node.getText()); } return { type: tsbufferSchema.SchemaType.Overwrite, target: target, overwrite: overwrite }; } // DateType if (ts__default["default"].isTypeReferenceNode(node) && this._typeNameToString(node.typeName) === 'Date' && !imports['Date']) { return { type: tsbufferSchema.SchemaType.Date }; } // NonNullableType if (ts__default["default"].isTypeReferenceNode(node) && this._typeNameToString(node.typeName) === 'NonNullable' && !imports['NonNullable']) { let target = this.node2schema(node.typeArguments[0], imports, logger, node.getFullText()); return { type: tsbufferSchema.SchemaType.NonNullable, target: target }; } // Keyof if (ts__default["default"].isTypeOperatorNode(node) && node.operator === ts__default["default"].SyntaxKind.KeyOfKeyword) { return { type: tsbufferSchema.SchemaType.Keyof, target: this.node2schema(node.type, imports, logger), }; } // ReferenceType放最后(因为很多其它类型,如Pick等,都解析为ReferenceNode) if (ts__default["default"].isTypeReferenceNode(node)) { return this._getReferenceTypeSchema(node.typeName, imports); } if (ts__default["default"].isExpressionWithTypeArguments(node)) { return this._getReferenceTypeSchema(node.expression.getText(), imports); } logger === null || logger === void 0 ? void 0 : logger.debug(node); throw new Error('Cannot resolve type: ' + node.getText()); } /** * A -> A * A.B -> A.B * @param name */ _typeNameToString(name) { if (ts__default["default"].isIdentifier(name)) { return name.text; } else { let left = ts__default["default"].isIdentifier(name.left) ? name.left.text : this._typeNameToString(name.left); return left + '.' + name.right.text; } } _getReferenceTypeSchema(name, imports) { if (typeof name !== 'string') { name = this._typeNameToString(name); } let arrName = name.split('.'); let importItem = imports[arrName[0]]; if (importItem) { let importName = arrName.slice(); importName[0] = importItem.targetName; return { type: tsbufferSchema.SchemaType.Reference, target: importItem.path + '/' + importName.join('.') }; } else { let ref = { type: tsbufferSchema.SchemaType.Reference, target: name }; return ref; } } _isLocalReference(node, imports, referenceName) { let ref; if (ts__default["default"].isTypeReferenceNode(node)) { ref = this._getReferenceTypeSchema(node.typeName, imports); } else if (ts__default["default"].isExpressionWithTypeArguments(node)) { ref = this._getReferenceTypeSchema(node.expression.getText(), imports); } else { return false; } if (typeof referenceName === 'string') { referenceName = [referenceName]; } for (let name of referenceName) { if (ref.target.indexOf('/') === -1 && ref.target === name) { return name; } } return false; } _getPreKey(schema, logger) { if (schema.type === 'Union' || schema.type === 'Intersection' || schema.type === 'Literal' || this._isTypeReference(schema)) { return schema; } else { logger === null || logger === void 0 ? void 0 : logger.log('Illeagle Pick keys:', schema); throw new Error('Illeagle Pick keys: ' + JSON.stringify(schema, null, 2)); } } _isInterfaceReference(schema) { return this._isTypeReference(schema) || schema.type === 'Interface' || schema.type === 'Pick' || schema.type === 'Partial' || schema.type === 'Omit' || schema.type === 'Overwrite'; } _isTypeReference(schema) { return schema.type === 'Reference' || schema.type === 'IndexedAccess' || schema.type === 'Keyof'; } } class EncodeIdUtil { /** * 将字符串映射为从0开始的自增数字,支持向后兼容 * @param values object将视为 md5(JSON.stringify(obj)) * @param compatible 需要向后兼容的结果集(新字段用新数字,旧字段ID不变) * @returns 返回的顺序必定与values传入的顺序相同 */ static genEncodeIds(values, compatible) { var _a, _b; // 新元素的起始ID,有compatible则从其下一个开始,全新模式从0开始 let nextId = 0; let existKeyId = compatible ? compatible.reduce((prev, next) => { prev[next.key] = next.id; nextId = Math.max(nextId, next.id + 1); return prev; }, {}) : {}; let output = []; let keys = values.map(v => this.getKey(v)); for (let key of keys) { let id = (_a = existKeyId[key]) !== null && _a !== void 0 ? _a : nextId++; existKeyId[key] = id; output.push({ key: key, id: id }); } // 可优化节点>=32,4096 let uniqueKeyLength = keys.distinct().length; if (nextId > 32 && uniqueKeyLength <= 32 || nextId > 4096 && uniqueKeyLength <= 4096) { (_b = this.onGenCanOptimized) === null || _b === void 0 ? void 0 : _b.call(this); } return output; } static getSchemaEncodeKeys(schema) { switch (schema.type) { case 'Enum': { return schema.members.map(v => '' + v.value); } case 'Interface': { return schema.properties ? schema.properties.map(v => v.name) : []; } case 'Intersection': case 'Union': return schema.members.map(v => k8wCrypto.Crypto.md5(JSON.stringify(v.type))); default: return []; } } static getKey(value) { return typeof (value) === 'object' ? k8wCrypto.Crypto.md5(JSON.stringify(value)) : '' + value; } static getSchemaEncodeIds(schema) { if (!schema) { return undefined; } switch (schema.type) { case 'Enum': { return schema.members.map(v => ({ key: EncodeIdUtil.getKey(v.value), id: v.id })); } case 'Interface': { return schema.properties ? schema.properties.map(v => ({ key: EncodeIdUtil.getKey(v.name), id: v.id })) : undefined; } case 'Intersection': case 'Union': return schema.members.map(v => ({ key: k8wCrypto.Crypto.md5(JSON.stringify(v.type)), id: v.id })); default: return undefined; } } } class ProtoHelper { constructor(proto) { this.proto = proto; } parseReference(schema) { // Reference if (schema.type === tsbufferSchema.SchemaType.Reference) { let parsedSchema = this.proto[schema.target]; if (!parsedSchema) { throw new Error(`Cannot find reference target: ${schema.target}`); } if (this.isTypeReference(parsedSchema)) { return this.parseReference(parsedSchema); } else { return parsedSchema; } } // IndexedAccess else if (schema.type === tsbufferSchema.SchemaType.IndexedAccess) { if (!this.isInterface(schema.objectType)) { throw new Error(`Error objectType: ${schema.objectType.type}`); } // find prop item let flat = this.getFlatInterfaceSchema(schema.objectType); let propItem = flat.properties.find(v => v.name === schema.index); let propType; if (propItem) { propType = propItem.type; } else { if (flat.indexSignature) { propType = flat.indexSignature.type; } else { throw new Error(`Error index: ${schema.index}`); } } // optional -> | undefined if (propItem && propItem.optional && // 引用的字段是optional (propItem.type.type !== tsbufferSchema.SchemaType.Union // 自身不为Union // 或自身为Union,但没有undefined成员条件 || propItem.type.members.findIndex(v => v.type.type === tsbufferSchema.SchemaType.Literal && v.type.literal === undefined) === -1)) { propType = { type: tsbufferSchema.SchemaType.Union, members: [ { id: 0, type: propType }, { id: 1, type: { type: tsbufferSchema.SchemaType.Literal, literal: undefined } } ] }; } return this.isTypeReference(propType) ? this.parseReference(propType) : propType; } else if (schema.type === tsbufferSchema.SchemaType.Keyof) { if (!this.isInterface(schema.target)) { throw new Error(('Invalid keyof target type: ' + schema.target.type)); } let flatInterface = this.getFlatInterfaceSchema(schema.target); return { type: tsbufferSchema.SchemaType.Union, members: flatInterface.properties.map((v, i) => ({ id: i, type: { type: tsbufferSchema.SchemaType.Literal, literal: v.name } })) }; } else { return schema; } } isInterface(schema, excludeReference = false) { if (!excludeReference && this.isTypeReference(schema)) { let parsed = this.parseReference(schema); return this.isInterface(parsed, excludeReference); } else { return schema.type === tsbufferSchema.SchemaType.Interface || schema.type === tsbufferSchema.SchemaType.Pick || schema.type === tsbufferSchema.SchemaType.Partial || schema.type === tsbufferSchema.SchemaType.Omit || schema.type === tsbufferSchema.SchemaType.Overwrite; } } isTypeReference(schema) { return schema.type === tsbufferSchema.SchemaType.Reference || schema.type === tsbufferSchema.SchemaType.IndexedAccess; } getUnionProperties(schema) { return this._addUnionProperties([], schema.members.map(v => v.type)); } /** * unionProperties: 在Union或Intersection类型中,出现在任意member中的字段 */ _addUnionProperties(unionProperties, schemas) { for (let i = 0, len = schemas.length; i < len; ++i) { let schema = this.parseReference(schemas[i]); // Interface及其Ref 加入interfaces if (this.isInterface(schema)) { let flat = this.getFlatInterfaceSchema(schema); flat.properties.forEach(v => { unionProperties.binaryInsert(v.name, true); }); if (flat.indexSignature) { let key = `[[${flat.indexSignature.keyType}]]`; unionProperties.binaryInsert(key, true); } } // Intersection/Union 递归合并unionProperties else if (schema.type === tsbufferSchema.SchemaType.Intersection || schema.type === tsbufferSchema.SchemaType.Union) { this._addUnionProperties(unionProperties, schema.members.map(v => v.type)); } } return unionProperties; } /** * 将unionProperties 扩展到 InterfaceTypeSchema中(optional的any类型) * 以此来跳过对它们的检查(用于Intersection/Union) */ applyUnionProperties(schema, unionProperties) { let newSchema = { ...schema, properties: schema.properties.slice() }; for (let prop of unionProperties) { if (prop === '[[String]]') { newSchema.indexSignature = newSchema.indexSignature || { keyType: tsbufferSchema.SchemaType.String, type: { type: tsbufferSchema.SchemaType.Any } }; } else if (prop === '[[Number]]') { newSchema.indexSignature = newSchema.indexSignature || { keyType: tsbufferSchema.SchemaType.Number, type: { type: tsbufferSchema.SchemaType.Any } }; } else if (!schema.properties.find(v => v.name === prop)) { newSchema.properties.push({ id: -1, name: prop, optional: true, type: { type: tsbufferSchema.SchemaType.Any } }); } } return newSchema; } /** * 将interface及其引用转换为展平的schema */ getFlatInterfaceSchema(schema) { if (this.isTypeReference(schema)) { let parsed = this.parseReference(schema); if (parsed.type !== tsbufferSchema.SchemaType.Interface) { throw new Error(`Cannot flatten non interface type: ${parsed.type}`); } return this.getFlatInterfaceSchema(parsed); } else if (schema.type === tsbufferSchema.SchemaType.Interface) { return this._flattenInterface(schema); } else { return this._flattenMappedType(schema); } } /** * 展平interface */ _flattenInterface(schema) { let properties = {}; let indexSignature; // 自身定义的properties和indexSignature优先级最高 if (schema.properties) { for (let prop of schema.properties) { properties[prop.name] = { optional: prop.optional, type: prop.type }; } } if (schema.indexSignature) { indexSignature = schema.indexSignature; } // extends的优先级次之,补全没有定义的字段 if (schema.extends) { for (let extend of schema.extends) { // 解引用 let parsedExtRef = this.parseReference(extend.type); if (parsedExtRef.type !== tsbufferSchema.SchemaType.Interface) { throw new Error('SchemaError: extends must from interface but from ' + parsedExtRef.type); } // 递归展平extends let flatenExtendsSchema = this.getFlatInterfaceSchema(parsedExtRef); // properties if (flatenExtendsSchema.properties) { for (let prop of flatenExtendsSchema.properties) { if (!properties[prop.name]) { properties[prop.name] = { optional: prop.optional, type: prop.type }; } } } // indexSignature if (flatenExtendsSchema.indexSignature && !indexSignature) { indexSignature = flatenExtendsSchema.indexSignature; } } } return { type: tsbufferSchema.SchemaType.Interface, properties: Object.entries(properties).map((v, i) => ({ id: i, name: v[0], optional: v[1].optional, type: v[1].type })), indexSignature: indexSignature }; } /** 将MappedTypeSchema转换为展平的Interface */ _flattenMappedType(schema) { // target 解引用 let target; if (this.isTypeReference(schema.target)) { let parsed = this.parseReference(schema.target); target = parsed; } else { target = schema.target; } let flatTarget; // 内层仍然为MappedType 递归之 if (target.type === tsbufferSchema.SchemaType.Pick || target.type === tsbufferSchema.SchemaType.Partial || target.type === tsbufferSchema.SchemaType.Omit || target.type === tsbufferSchema.SchemaType.Overwrite) { flatTarget = this._flattenMappedType(target); } else if (target.type === tsbufferSchema.SchemaType.Interface) { flatTarget = this._flattenInterface(target); } else { throw new Error(`Invalid target.type: ${target.type}`); } // 开始执行Mapped逻辑 if (schema.type === tsbufferSchema.SchemaType.Pick) { let properties = []; for (let key of schema.keys) { let propItem = flatTarget.properties.find(v => v.name === key); if (propItem) { properties.push({ id: properties.length, name: key, optional: propItem.optional, type: propItem.type }); } else if (flatTarget.indexSignature) { properties.push({ id: properties.length, name: key, type: flatTarget.indexSignature.type }); } else { throw new Error(`Cannot find pick key [${key}]`); } } return { type: tsbufferSchema.SchemaType.Interface, properties: properties }; } else if (schema.type === tsbufferSchema.SchemaType.Partial) { for (let v of flatTarget.properties) { v.optional = true; } return flatTarget; } else if (schema.type === tsbufferSchema.SchemaType.Omit) { for (let key of schema.keys) { flatTarget.properties.removeOne(v => v.name === key); } return flatTarget; } else if (schema.type === tsbufferSchema.SchemaType.Overwrite) { let overwrite = this.getFlatInterfaceSchema(schema.overwrite); if (overwrite.indexSignature) { flatTarget.indexSignature = overwrite.indexSignature; } for (let prop of overwrite.properties) { flatTarget.properties.removeOne(v => v.name === prop.name); flatTarget.properties.push(prop); } return flatTarget; } else { throw new Error(`Unknown type: ${schema.type}`); } } parseMappedType(schema) { let parents = []; let child = schema; do { parents.push(child); child = this.parseReference(child.target); } while (child.type === tsbufferSchema.SchemaType.Pick || child.type === tsbufferSchema.SchemaType.Omit || child.type === tsbufferSchema.SchemaType.Partial || child.type === tsbufferSchema.SchemaType.Overwrite); // Final if (child.type === tsbufferSchema.SchemaType.Interface) { return child; } // PickOmit<A|B> === PickOmit<A> | PickOmit<B> else if (child.type === tsbufferSchema.SchemaType.Union) { let newSchema = { type: tsbufferSchema.SchemaType.Union, members: child.members.map(v => { // 从里面往外装 let type = v.type; for (let i = parents.length - 1; i > -1; --i) { let parent = parents[i]; type = { ...parent, target: type }; } return { id: v.id, type: type }; }) }; return newSchema; } else { throw new Error(`Unsupported pattern ${schema.type}<${child.type}>`); } } } class SchemaUtil { /** * 解析一个Schema引用到的其它类型 * @param schema */ static getUsedReferences(schemas) { var _a; if (!Array.isArray(schemas)) { schemas = [schemas]; } let output = []; for (let schema of schemas) { switch (schema.type) { case tsbufferSchema.SchemaType.Array: output = output.concat(this.getUsedReferences(schema.elementType)); break; case tsbufferSchema.SchemaType.Tuple: output = output.concat(this.getUsedReferences(schema.elementTypes)); break; case tsbufferSchema.SchemaType.Interface: if (schema.extends) { output = output.concat(this.getUsedReferences(schema.extends.map(v => v.type))); } if (schema.properties) { output = output.concat(this.getUsedReferences(schema.properties.map(v => v.type))); } if (schema.indexSignature) { output = output.concat(this.getUsedReferences(schema.indexSignature.type)); } break; case tsbufferSchema.SchemaType.IndexedAccess: output = output.concat(this.getUsedReferences(schema.objectType)); break; case tsbufferSchema.SchemaType.Reference: output.push(schema)