tsbuffer-proto-generator
Version:
TSBuffer Proto Generator ---
1,260 lines (1,255 loc) • 66.2 kB
JavaScript
/*!
* TSBuffer Proto Generator v1.7.2
* -----------------------------------------
* MIT LICENSE
* KingWorks (C) Copyright 2022
* https://github.com/k8w/tsbuffer
*/
import 'k8w-extend-native';
import fs from 'fs';
import path from 'path';
import { SchemaType } from 'tsbuffer-schema';
import ts from 'typescript';
import { Crypto } from 'k8w-crypto';
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.createSourceFile('', content, ts.ScriptTarget.ES3, true, ts.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.SyntaxKind.ImportDeclaration) {
return;
}
let node = v;
// 仅支持从字符串路径import
if (node.moduleSpecifier.kind !== ts.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.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.isInterfaceDeclaration(v) || ts.isTypeAliasDeclaration(v) || ts.isEnumDeclaration(v)) {
// 外层允许export,且自身有被export
let _isExport = Boolean(isExport && v.modifiers && v.modifiers.findIndex(v1 => v1.kind === ts.SyntaxKind.ExportKeyword) > -1);
// 是否为export default
let _isExportDefault = _isExport && v.modifiers.findIndex(v1 => v1.kind === ts.SyntaxKind.DefaultKeyword) > -1;
output[v.name.text] = {
node: v.kind === ts.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.createTypeReferenceNode(v.name, undefined),
isExport: true
};
}
}
// namespace
else if (ts.isModuleDeclaration(v) && (v.flags & ts.NodeFlags.Namespace)) {
if (v.body && v.body.kind === ts.SyntaxKind.ModuleBlock) {
// 外层允许export,且自身有被export
let _isExport = Boolean(isExport && v.modifiers && v.modifiers.findIndex(v1 => v1.kind === ts.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.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.createTypeReferenceNode(elem.propertyName.text, undefined),
isExport: true
};
}
// export { A }
else {
exportNames[elem.name.text] = true;
}
});
}
}
// export default
else if (ts.isExportAssignment(v)) {
// 暂不支持 export = XXX
if (v.isExportEquals) {
return;
}
output['default'] = {
node: ts.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.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.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.isParenthesizedTypeNode(node)) {
node = node.type;
}
// AnyType
if (node.kind === ts.SyntaxKind.AnyKeyword || node.kind === ts.SyntaxKind.UnknownKeyword) {
return {
type: SchemaType.Any
};
}
// BufferType
if (ts.isTypeReferenceNode(node)) {
let ref = this._getReferenceTypeSchema(node.typeName, imports);
if (BUFFER_TYPES.binarySearch(ref.target) > -1) {
let output = {
type: SchemaType.Buffer
};
let target = ref.target;
if (target !== 'ArrayBuffer') {
output.arrayType = target;
}
return output;
}
}
// BooleanType
if (node.kind === ts.SyntaxKind.BooleanKeyword) {
return {
type: SchemaType.Boolean
};
}
// ObjectType
if (node.kind === ts.SyntaxKind.ObjectKeyword) {
return {
type: SchemaType.Object
};
}
// NumberType
if (node.kind === ts.SyntaxKind.NumberKeyword) {
return {
type: SchemaType.Number
};
}
else if (node.kind === ts.SyntaxKind.BigIntKeyword) {
return {
type: SchemaType.Number,
scalarType: 'bigint'
};
}
// Scalar value types
if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && SCALAR_TYPES.binarySearch(node.typeName.text) > -1) {
return {
type: SchemaType.Number,
scalarType: node.typeName.text
};
}
// StringType
if (node.kind === ts.SyntaxKind.StringKeyword) {
return { type: SchemaType.String };
}
// ArrayType: xxx[]
if (ts.isArrayTypeNode(node)) {
return {
type: SchemaType.Array,
elementType: this.node2schema(node.elementType, imports, logger, node.getFullText())
};
}
// ArrayType: Array<T>
if (this._isLocalReference(node, imports, 'Array') && node.typeArguments) {
return {
type: SchemaType.Array,
elementType: this.node2schema(node.typeArguments[0], imports, logger, node.getFullText())
};
}
// TupleType
if (ts.isTupleTypeNode(node)) {
let optionalStartIndex;
let output = {
type: SchemaType.Tuple,
elementTypes: node.elements.map((v, i) => {
if (v.kind === ts.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.isLiteralTypeNode(node)) {
if (ts.isStringLiteral(node.literal)) {
return {
type: SchemaType.Literal,
literal: node.literal.text
};
}
else if (ts.isNumericLiteral(node.literal)) {
return {
type: SchemaType.Literal,
literal: parseFloat(node.literal.text)
};
}
else if (node.literal.kind === ts.SyntaxKind.TrueKeyword) {
return {
type: SchemaType.Literal,
literal: true
};
}
else if (node.literal.kind === ts.SyntaxKind.FalseKeyword) {
return {
type: SchemaType.Literal,
literal: false
};
}
else if (node.literal.kind === ts.SyntaxKind.NullKeyword) {
return {
type: SchemaType.Literal,
literal: null
};
}
}
// Literal: undefined
else if (node.kind === ts.SyntaxKind.UndefinedKeyword) {
return {
type: SchemaType.Literal,
literal: undefined
};
}
// EnumType
if (ts.isEnumDeclaration(node)) {
let initializer = 0;
let schema = {
type: SchemaType.Enum,
members: node.members.map((v, i) => {
if (v.initializer) {
if (ts.isStringLiteral(v.initializer)) {
initializer = NaN;
return {
id: i,
name: v.name.getText(),
value: v.initializer.text
};
}
else if (ts.isNumericLiteral(v.initializer)) {
initializer = parseFloat(v.initializer.text);
return {
id: i,
name: v.name.getText(),
value: initializer++
};
}
// 负数
else if (ts.isPrefixUnaryExpression(v.initializer) && v.initializer.operator === ts.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.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.isInterfaceDeclaration(node) || ts.isTypeLiteralNode(node)) {
// extends
let extendsInterface;
if (ts.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.isPropertySignature(member)) {
if (ts.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.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.SyntaxKind.NumberKeyword) {
keyType = 'Number';
}
else {
keyType = 'String';
}
indexSignature = {
keyType: keyType,
type: this.node2schema(member.type, imports, logger, member.getFullText())
};
}
});
// output
let output = {
type: 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.isIndexedAccessTypeNode(node)) {
// A['a']
if (ts.isLiteralTypeNode(node.indexType)) {
let index;
if (ts.isStringLiteral(node.indexType.literal) || ts.isNumericLiteral(node.indexType.literal)) {
index = node.indexType.literal.text;
}
else if (node.indexType.literal.kind === ts.SyntaxKind.TrueKeyword
|| node.indexType.literal.kind === ts.SyntaxKind.FalseKeyword
|| node.indexType.literal.kind === ts.SyntaxKind.NullKeyword
|| node.indexType.literal.kind === ts.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: SchemaType.IndexedAccess,
index: index,
objectType: objectType
};
}
// A['a' | 'b']
else if (ts.isUnionTypeNode(node.indexType)) ;
else {
throw new Error(`Error IndexedAccessType indexType: ${node.getText()}`);
}
}
// UnionType
if (ts.isUnionTypeNode(node)) {
return {
type: SchemaType.Union,
members: node.types.map((v, i) => ({
id: i,
type: this.node2schema(v, imports, logger, v.getFullText())
}))
};
}
// IntersectionType
if (ts.isIntersectionTypeNode(node)) {
return {
type: 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.isTypeReferenceNode(node)) {
nodeName = node.typeName.getText();
}
else if (ts.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 !== SchemaType.Union && target.type !== 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: SchemaType.Pick } : { type: 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: SchemaType.Partial,
target: target
};
}
// OverwriteType
if (ts.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: SchemaType.Overwrite,
target: target,
overwrite: overwrite
};
}
// DateType
if (ts.isTypeReferenceNode(node) && this._typeNameToString(node.typeName) === 'Date' && !imports['Date']) {
return {
type: SchemaType.Date
};
}
// NonNullableType
if (ts.isTypeReferenceNode(node) && this._typeNameToString(node.typeName) === 'NonNullable' && !imports['NonNullable']) {
let target = this.node2schema(node.typeArguments[0], imports, logger, node.getFullText());
return {
type: SchemaType.NonNullable,
target: target
};
}
// Keyof
if (ts.isTypeOperatorNode(node) && node.operator === ts.SyntaxKind.KeyOfKeyword) {
return {
type: SchemaType.Keyof,
target: this.node2schema(node.type, imports, logger),
};
}
// ReferenceType放最后(因为很多其它类型,如Pick等,都解析为ReferenceNode)
if (ts.isTypeReferenceNode(node)) {
return this._getReferenceTypeSchema(node.typeName, imports);
}
if (ts.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.isIdentifier(name)) {
return name.text;
}
else {
let left = ts.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: SchemaType.Reference,
target: importItem.path + '/' + importName.join('.')
};
}
else {
let ref = {
type: SchemaType.Reference,
target: name
};
return ref;
}
}
_isLocalReference(node, imports, referenceName) {
let ref;
if (ts.isTypeReferenceNode(node)) {
ref = this._getReferenceTypeSchema(node.typeName, imports);
}
else if (ts.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 => Crypto.md5(JSON.stringify(v.type)));
default:
return [];
}
}
static getKey(value) {
return typeof (value) === 'object' ? 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: 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 === 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 === 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 !== SchemaType.Union // 自身不为Union
// 或自身为Union,但没有undefined成员条件
|| propItem.type.members.findIndex(v => v.type.type === SchemaType.Literal && v.type.literal === undefined) === -1)) {
propType = {
type: SchemaType.Union,
members: [
{ id: 0, type: propType },
{
id: 1,
type: {
type: SchemaType.Literal,
literal: undefined
}
}
]
};
}
return this.isTypeReference(propType) ? this.parseReference(propType) : propType;
}
else if (schema.type === 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: SchemaType.Union,
members: flatInterface.properties.map((v, i) => ({
id: i,
type: {
type: 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 === SchemaType.Interface ||
schema.type === SchemaType.Pick ||
schema.type === SchemaType.Partial ||
schema.type === SchemaType.Omit ||
schema.type === SchemaType.Overwrite;
}
}
isTypeReference(schema) {
return schema.type === SchemaType.Reference || schema.type === 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 === SchemaType.Intersection || schema.type === 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: SchemaType.String,
type: { type: SchemaType.Any }
};
}
else if (prop === '[[Number]]') {
newSchema.indexSignature = newSchema.indexSignature || {
keyType: SchemaType.Number,
type: { type: SchemaType.Any }
};
}
else if (!schema.properties.find(v => v.name === prop)) {
newSchema.properties.push({
id: -1,
name: prop,
optional: true,
type: {
type: SchemaType.Any
}
});
}
}
return newSchema;
}
/**
* 将interface及其引用转换为展平的schema
*/
getFlatInterfaceSchema(schema) {
if (this.isTypeReference(schema)) {
let parsed = this.parseReference(schema);
if (parsed.type !== SchemaType.Interface) {
throw new Error(`Cannot flatten non interface type: ${parsed.type}`);
}
return this.getFlatInterfaceSchema(parsed);
}
else if (schema.type === 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 !== 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: 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 === SchemaType.Pick || target.type === SchemaType.Partial || target.type === SchemaType.Omit || target.type === SchemaType.Overwrite) {
flatTarget = this._flattenMappedType(target);
}
else if (target.type === SchemaType.Interface) {
flatTarget = this._flattenInterface(target);
}
else {
throw new Error(`Invalid target.type: ${target.type}`);
}
// 开始执行Mapped逻辑
if (schema.type === 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: SchemaType.Interface,
properties: properties
};
}
else if (schema.type === SchemaType.Partial) {
for (let v of flatTarget.properties) {
v.optional = true;
}
return flatTarget;
}
else if (schema.type === SchemaType.Omit) {
for (let key of schema.keys) {
flatTarget.properties.removeOne(v => v.name === key);
}
return flatTarget;
}
else if (schema.type === 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 === SchemaType.Pick || child.type === SchemaType.Omit || child.type === SchemaType.Partial || child.type === SchemaType.Overwrite);
// Final
if (child.type === SchemaType.Interface) {
return child;
}
// PickOmit<A|B> === PickOmit<A> | PickOmit<B>
else if (child.type === SchemaType.Union) {
let newSchema = {
type: 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 SchemaType.Array:
output = output.concat(this.getUsedReferences(schema.elementType));
break;
case SchemaType.Tuple:
output = output.concat(this.getUsedReferences(schema.elementTypes));
break;
case 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 SchemaType.IndexedAccess:
output = output.concat(this.getUsedReferences(schema.objectType));
break;
case SchemaType.Reference:
output.push(schema);
break;
case SchemaType.Union:
case SchemaType.Intersection:
output = output.concat(this.getUsedReferences(schema.members.map(v => v.type)));
break;
case SchemaType.Pick:
case SchemaType.Omit:
output = output.concat(this.getUsedReferences(schema.target));
if ((_a = schema.pre) === null || _a === void 0 ? void 0 : _a.key) {
output = output.concat(this.getUsedReferences(schema.pre.key));
}
break;
case SchemaType.Partial:
case SchemaType.NonNullable:
case SchemaType.Keyof:
output = output.concat(this.getUsedReferences(schema.target));
break;
case SchemaType.Overwrite:
output = output.concat(this.getUsedReferences(schema.target));
output = output.concat(this.getUsedReferences(schema.overwrite));
break;
}
}
return output;
}
}
class ProtoGenerator {
constructor(options = {}) {
this.options = {
baseDir: '.',
verbose: false,
readFile: (v => fs.readFileSync(path.resolve(this.options.baseDir, v)).toString()),
/** 默认将 module 解析为 chdir 下的node_modules */
resolveModule: defaultResolveModule
};
Object.assign(this.options, options);
this._astParser = new AstParser(options);
}
/**
* 生成FileSchema
* 对modules(例如node_modules)的引用,也会全部转为相对路径引用
* @param paths 于baseDir的相对路径
* @param options
*/
async generate(paths, options = {}) {
var _a;
let output = {};
const logger = 'logger' in options ? options.logger : console;
if (typeof paths === 'string') {
paths = [paths];
}
// 确保路径安全,再次将paths转为相对路径
paths = paths.map(v => path.relative(this.options.baseDir, path.resolve(this.options.baseDir, v)));
if (this.options.verbose) {
logger === null || logger === void 0 ? void 0 : logger.debug('[TSBuffer Schema Generator]', 'generate', `Ready to generate ${paths.length} file`);
logger === null || logger === void 0 ? void 0 : logger.debug('[TSBuffer Schema Generator]', 'generate', 'BaseDir=' + this.options.baseDir);
}
// AST CACHE
let astCache = (_a = this.options.astCache) !== null && _a !== void 0 ? _a : {};
// Init Post
this._astParser.pre = {
prePickOmitSchemas: [],
preEnumSchemas: []
};
// 默认filter是导出所有export项
let filter = options.filter || (v => v.isExport);
// 是要被导出的直接引用的项目
let exports = {};
// 生成这几个文件的AST CACHE
for (let filepath of paths) {
if (this.options.verbose) {
logger === null || logger === void 0 ? void 0 : logger.debug('[TSBuffer Schema Generator]', 'generate', 'FilePath=' + filepath);
}
// 生成该文件的AST
let { ast, astKey } = await this._ge