|
|
|
@ -1,148 +1,229 @@
|
|
|
|
|
import * as ts from 'typescript'
|
|
|
|
|
|
|
|
|
|
const PROPERTY_TYPES = {
|
|
|
|
|
any: ts.SyntaxKind.AnyKeyword,
|
|
|
|
|
boolean: ts.SyntaxKind.BooleanKeyword,
|
|
|
|
|
number: ts.SyntaxKind.NumberKeyword,
|
|
|
|
|
string: ts.SyntaxKind.StringKeyword,
|
|
|
|
|
}
|
|
|
|
|
const intrinsicTypeKeywords = new Set([
|
|
|
|
|
ts.SyntaxKind.AnyKeyword,
|
|
|
|
|
ts.SyntaxKind.BooleanKeyword,
|
|
|
|
|
ts.SyntaxKind.NeverKeyword,
|
|
|
|
|
ts.SyntaxKind.NumberKeyword,
|
|
|
|
|
ts.SyntaxKind.StringKeyword,
|
|
|
|
|
ts.SyntaxKind.UndefinedKeyword,
|
|
|
|
|
ts.SyntaxKind.UnknownKeyword,
|
|
|
|
|
ts.SyntaxKind.VoidKeyword
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const typeOperatorKeywords = new Map([
|
|
|
|
|
[ts.SyntaxKind.KeyofKeyword, '_keyof'],
|
|
|
|
|
[ts.SyntaxKind.ReadonlyKeyword, '_readonly'],
|
|
|
|
|
[ts.SyntaxKind.UniqueKeyword, '_unique'],
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
class TSNode {
|
|
|
|
|
constructor(name, type) {
|
|
|
|
|
this.children = []
|
|
|
|
|
this.addChild = (name, type) => {
|
|
|
|
|
let node = new TSNode(name, type)
|
|
|
|
|
this.children.push(node)
|
|
|
|
|
return node
|
|
|
|
|
}
|
|
|
|
|
this.getType = () => this.type
|
|
|
|
|
this.getObject = () => {
|
|
|
|
|
let map = {}
|
|
|
|
|
map[this.name] = this.children.length
|
|
|
|
|
? this.children
|
|
|
|
|
.map(child => child.getObject())
|
|
|
|
|
.reduce((pv, child) => {
|
|
|
|
|
for (let key in child) {
|
|
|
|
|
if (pv.hasOwnProperty(key) || key in pv) {
|
|
|
|
|
if (child[key]) {
|
|
|
|
|
Object.assign(pv[key], child[key])
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
pv[key] = child[key]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return pv
|
|
|
|
|
}, {})
|
|
|
|
|
: this.type
|
|
|
|
|
return map
|
|
|
|
|
};
|
|
|
|
|
this.name = name
|
|
|
|
|
this.type = type
|
|
|
|
|
}
|
|
|
|
|
constructor(name, type) {
|
|
|
|
|
this.children = []
|
|
|
|
|
this.addChild = (name, type) => {
|
|
|
|
|
let node = new TSNode(name, type)
|
|
|
|
|
this.children.push(node)
|
|
|
|
|
return node
|
|
|
|
|
}
|
|
|
|
|
this.getType = () => this.type
|
|
|
|
|
this.getObject = () => {
|
|
|
|
|
let map = {}
|
|
|
|
|
map[this.name] = this.children.length
|
|
|
|
|
? this.children
|
|
|
|
|
.map(child => child.getObject())
|
|
|
|
|
.reduce((pv, child) => {
|
|
|
|
|
for (let key in child) {
|
|
|
|
|
if (pv.hasOwnProperty(key) || key in pv) {
|
|
|
|
|
if (child[key]) {
|
|
|
|
|
Object.assign(pv[key], child[key])
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
pv[key] = child[key]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return pv
|
|
|
|
|
}, {})
|
|
|
|
|
: this.type
|
|
|
|
|
return map
|
|
|
|
|
};
|
|
|
|
|
this.name = name
|
|
|
|
|
this.type = type
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parameterTypeStructure(paramNode, checker) {
|
|
|
|
|
let typeStruc = typeStructure(paramNode.type, checker)
|
|
|
|
|
if (paramNode.questionToken) typeStruc = {_optional: typeStruc}
|
|
|
|
|
return typeStruc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function typeStructure(typeNode, checker) {
|
|
|
|
|
switch (typeNode.kind) {
|
|
|
|
|
case ts.SyntaxKind.IntersectionType:
|
|
|
|
|
return {
|
|
|
|
|
switch (typeNode.kind) {
|
|
|
|
|
case ts.SyntaxKind.UnionType:
|
|
|
|
|
return {
|
|
|
|
|
_union: typeNode.types.map(t => typeStructure(t, checker))
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.IntersectionType:
|
|
|
|
|
return {
|
|
|
|
|
_intersection: typeNode.types.map(t => typeStructure(t, checker))
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.ImportType:
|
|
|
|
|
return {
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.ImportType: {
|
|
|
|
|
const typeStruc = {
|
|
|
|
|
_importedFrom: typeNode.argument.literal.text,
|
|
|
|
|
_name: typeNode.qualifier.text
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.TypeLiteral:
|
|
|
|
|
return Object.fromEntries(typeNode.members.map(
|
|
|
|
|
}
|
|
|
|
|
if (typeNode.typeArguments) {
|
|
|
|
|
typeStruc._typeArguments = typeNode.typeArguments.map(
|
|
|
|
|
p => typeStructure(p, checker))
|
|
|
|
|
}
|
|
|
|
|
return typeStruc
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.ArrayType:
|
|
|
|
|
return {_array: typeStructure(typeNode.elementType, checker)}
|
|
|
|
|
case ts.SyntaxKind.TypeLiteral: // Seems to be plain object types
|
|
|
|
|
return Object.fromEntries(typeNode.members.map(
|
|
|
|
|
mem => [mem.name.text, typeStructure(mem.type, checker)]))
|
|
|
|
|
case ts.SyntaxKind.FunctionType:
|
|
|
|
|
return {
|
|
|
|
|
case ts.SyntaxKind.FunctionType: {
|
|
|
|
|
const typeStruc = {
|
|
|
|
|
_parameters: typeNode.parameters.map(
|
|
|
|
|
p => typeStructure(p.type, checker)),
|
|
|
|
|
p => parameterTypeStructure(p, checker)),
|
|
|
|
|
_returns: typeStructure(typeNode.type, checker)
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.LiteralType:
|
|
|
|
|
return checker.typeToString(checker.getTypeFromTypeNode(typeNode))
|
|
|
|
|
default:
|
|
|
|
|
console.log('I See', ts.SyntaxKind[typeNode.kind])
|
|
|
|
|
}
|
|
|
|
|
return checker.getTypeFromTypeNode(typeNode).intrinsicName
|
|
|
|
|
}
|
|
|
|
|
if (typeNode.typeParameters) {
|
|
|
|
|
typeStruc._typeParameters = typeNode.typeParameters.map(
|
|
|
|
|
p => p.name.text)
|
|
|
|
|
}
|
|
|
|
|
return typeStruc
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.IndexedAccessType:
|
|
|
|
|
return {
|
|
|
|
|
_ofType: typeStructure(typeNode.objectType, checker),
|
|
|
|
|
_index: typeStructure(typeNode.indexType, checker)
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.TypeOperator:
|
|
|
|
|
console.log('structuring operator', typeNode)
|
|
|
|
|
const key = typeOperatorKeywords.get(
|
|
|
|
|
typeNode.operator,
|
|
|
|
|
'_unidentified_operator'
|
|
|
|
|
)
|
|
|
|
|
return {
|
|
|
|
|
[key]:
|
|
|
|
|
typeStructure(typeNode.type, checker)
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.ConditionalType:
|
|
|
|
|
return {
|
|
|
|
|
_subtype: typeStructure(typeNode.checkType, checker),
|
|
|
|
|
_basetype: typeStructure(typeNode.extendsType, checker),
|
|
|
|
|
_truetype: typeStructure(typeNode.trueType, checker),
|
|
|
|
|
_falsetype: typeStructure(typeNode.falseType, checker),
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.TypeReference: {
|
|
|
|
|
const typeStruc = {_typeParameter: typeNode.typeName.text}
|
|
|
|
|
if (typeNode.typeArguments) {
|
|
|
|
|
typeStruc._typeArguments = typeNode.typeArguments.map(
|
|
|
|
|
arg => typeStructure(arg, checker))
|
|
|
|
|
}
|
|
|
|
|
return typeStruc
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.TypePredicate:
|
|
|
|
|
return {_is: typeStructure(typeNode.type, checker)}
|
|
|
|
|
case ts.SyntaxKind.LiteralType:
|
|
|
|
|
return checker.typeToString(checker.getTypeFromTypeNode(typeNode))
|
|
|
|
|
default:
|
|
|
|
|
if (intrinsicTypeKeywords.has(typeNode.kind)) {
|
|
|
|
|
return checker.getTypeFromTypeNode(typeNode).intrinsicName
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
throw new Error(`Unhandled type node ${ts.SyntaxKind[typeNode.kind]}`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const visit = (parent, checker) => node => {
|
|
|
|
|
switch (node.kind) {
|
|
|
|
|
case ts.SyntaxKind.ImportDeclaration:
|
|
|
|
|
console.log('Skipping import from', node.moduleSpecifier.text)
|
|
|
|
|
break
|
|
|
|
|
console.log('Skipping import from', node.moduleSpecifier.text)
|
|
|
|
|
break
|
|
|
|
|
case ts.SyntaxKind.VariableStatement:
|
|
|
|
|
console.log('Processing variable',
|
|
|
|
|
ts.SyntaxKind[node.declarationList.kind])
|
|
|
|
|
node.declarationList.declarations.forEach(visit(parent, checker))
|
|
|
|
|
console.log('Done with variable')
|
|
|
|
|
break
|
|
|
|
|
case ts.SyntaxKind.VariableDeclaration:
|
|
|
|
|
console.log('Processing var dec', node.name.text, 'in', parent)
|
|
|
|
|
const typeStruc = typeStructure(node.type, checker)
|
|
|
|
|
console.log('Type is', typeStructure(node.type, checker))
|
|
|
|
|
if ('_intersection' in typeStruc) {
|
|
|
|
|
console.log('First type is', typeStruc._intersection[0])
|
|
|
|
|
console.log('Processing variable',
|
|
|
|
|
ts.SyntaxKind[node.declarationList.kind])
|
|
|
|
|
node.declarationList.declarations.forEach(visit(parent, checker))
|
|
|
|
|
console.log('Done with variable')
|
|
|
|
|
break
|
|
|
|
|
case ts.SyntaxKind.VariableDeclaration: {
|
|
|
|
|
console.log('Processing var dec', node.name.text)
|
|
|
|
|
const typeStruc = typeStructure(node.type, checker)
|
|
|
|
|
parent.addChild(node.name.text, typeStruc)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
case ts.SyntaxKind.FunctionDeclaration: {
|
|
|
|
|
console.log('Processing func dec', node.name.text)
|
|
|
|
|
const typeStruc = {
|
|
|
|
|
_parameters: node.parameters.map(
|
|
|
|
|
p => parameterTypeStructure(p, checker)),
|
|
|
|
|
_returns: typeStructure(node.type, checker)
|
|
|
|
|
}
|
|
|
|
|
if (node.typeParameters) {
|
|
|
|
|
typeStruc._typeParameters = node.typeParameters.map(
|
|
|
|
|
p => p.name.text)
|
|
|
|
|
}
|
|
|
|
|
parent.addChild(node.name.text, typeStruc)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
parent.addChild(node.name.text, typeStruc)
|
|
|
|
|
break
|
|
|
|
|
case ts.SyntaxKind.ModuleDeclaration:
|
|
|
|
|
let moduleName = node.name.text
|
|
|
|
|
console.log('Declaring', moduleName)
|
|
|
|
|
ts.forEachChild(node, visit(parent.addChild(moduleName), checker))
|
|
|
|
|
break
|
|
|
|
|
let moduleName = node.name.text
|
|
|
|
|
console.log('Declaring', moduleName)
|
|
|
|
|
ts.forEachChild(node, visit(parent.addChild(moduleName), checker))
|
|
|
|
|
break
|
|
|
|
|
case ts.SyntaxKind.ModuleBlock:
|
|
|
|
|
console.log('Block')
|
|
|
|
|
ts.forEachChild(node, visit(parent, checker));
|
|
|
|
|
break
|
|
|
|
|
console.log('Block')
|
|
|
|
|
ts.forEachChild(node, visit(parent, checker));
|
|
|
|
|
break
|
|
|
|
|
case ts.SyntaxKind.InterfaceDeclaration:
|
|
|
|
|
let interfaceName = node.name.text
|
|
|
|
|
console.log('Skipping Interface', interfaceName);
|
|
|
|
|
break
|
|
|
|
|
parent[interfaceName] = {}
|
|
|
|
|
ts.forEachChild(node, visit(parent.addChild(interfaceName), checker))
|
|
|
|
|
break
|
|
|
|
|
let interfaceName = node.name.text
|
|
|
|
|
console.log('Skipping Interface', interfaceName);
|
|
|
|
|
break
|
|
|
|
|
parent[interfaceName] = {}
|
|
|
|
|
ts.forEachChild(node, visit(parent.addChild(interfaceName), checker))
|
|
|
|
|
break
|
|
|
|
|
case ts.SyntaxKind.PropertySignature:
|
|
|
|
|
let propertyName = node.name
|
|
|
|
|
let propertyType = node.type
|
|
|
|
|
let arrayDeep = 0
|
|
|
|
|
let realPropertyName =
|
|
|
|
|
'string' !== typeof propertyName && 'text' in propertyName
|
|
|
|
|
? propertyName.text
|
|
|
|
|
: propertyName
|
|
|
|
|
console.log('Property', realPropertyName)
|
|
|
|
|
while (propertyType.kind === ts.SyntaxKind.ArrayType) {
|
|
|
|
|
arrayDeep++
|
|
|
|
|
propertyType = propertyType.elementType
|
|
|
|
|
}
|
|
|
|
|
if (propertyType.kind === ts.SyntaxKind.TypeReference) {
|
|
|
|
|
let realPropertyType = propertyType.typeName
|
|
|
|
|
parent.addChild(
|
|
|
|
|
let propertyName = node.name
|
|
|
|
|
let propertyType = node.type
|
|
|
|
|
let arrayDeep = 0
|
|
|
|
|
let realPropertyName =
|
|
|
|
|
'string' !== typeof propertyName && 'text' in propertyName
|
|
|
|
|
? propertyName.text
|
|
|
|
|
: propertyName
|
|
|
|
|
console.log('Property', realPropertyName)
|
|
|
|
|
while (propertyType.kind === ts.SyntaxKind.ArrayType) {
|
|
|
|
|
arrayDeep++
|
|
|
|
|
propertyType = propertyType.elementType
|
|
|
|
|
}
|
|
|
|
|
if (propertyType.kind === ts.SyntaxKind.TypeReference) {
|
|
|
|
|
let realPropertyType = propertyType.typeName
|
|
|
|
|
parent.addChild(
|
|
|
|
|
realPropertyName,
|
|
|
|
|
'Array<'.repeat(arrayDeep) +
|
|
|
|
|
(realPropertyType.kind === ts.SyntaxKind.QualifiedName
|
|
|
|
|
? realPropertyType.getText()
|
|
|
|
|
: 'text' in realPropertyType
|
|
|
|
|
? realPropertyType.text
|
|
|
|
|
: realPropertyType) +
|
|
|
|
|
'>'.repeat(arrayDeep)
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
if (intrinsicTypeKeywords.has(propertyType.kind)) {
|
|
|
|
|
parent.addChild(
|
|
|
|
|
realPropertyName,
|
|
|
|
|
'Array<'.repeat(arrayDeep) +
|
|
|
|
|
(realPropertyType.kind === ts.SyntaxKind.QualifiedName
|
|
|
|
|
? realPropertyType.getText()
|
|
|
|
|
: 'text' in realPropertyType
|
|
|
|
|
? realPropertyType.text
|
|
|
|
|
: realPropertyType) +
|
|
|
|
|
'>'.repeat(arrayDeep)
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
for (let type in PROPERTY_TYPES) {
|
|
|
|
|
if (propertyType.kind === PROPERTY_TYPES[type]) {
|
|
|
|
|
parent.addChild(realPropertyName, type)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
checker.getTypeFromTypeNode(propertyType).intrinsicName)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
case ts.SyntaxKind.EndOfFileToken:
|
|
|
|
|
console.log('File ended.')
|
|
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
console.warn(
|
|
|
|
|
'Unhandled node kind',
|
|
|
|
|
node.kind, ts.SyntaxKind[node.kind])
|
|
|
|
|
console.warn(
|
|
|
|
|
'Unhandled node kind',
|
|
|
|
|
node.kind, ts.SyntaxKind[node.kind])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|