From 66bf2e281a3aaa48865e07b4216019b5e1079099 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Thu, 17 Oct 2024 22:20:44 -0700 Subject: [PATCH] feat: Extend type injection to all exported values --- src/Complex/type.ts | 3 +- src/index.ts | 2 +- tools/reflectTypes.mjs | 2 +- tools/ts2json.mjs | 319 ++++++++++++++++++++++++++--------------- 4 files changed, 204 insertions(+), 122 deletions(-) diff --git a/src/Complex/type.ts b/src/Complex/type.ts index 44525d6..ff42a3b 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -17,7 +17,8 @@ export const Complex_type = { (z: Complex) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)), from: { Complex: (dep: {convert: CommonSignature['convert']}) => - (z: Complex) => ({re: dep.convert(z.re), im: dep.convert(z.im)}), + (z: Complex): Complex => + ({re: dep.convert(z.re), im: dep.convert(z.im)}), T: (dep: {zero: CommonSignature['zero']}) => (t: T) => ({re: t, im: dep.zero(t)}) } diff --git a/src/index.ts b/src/index.ts index 648a3b9..c575454 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,4 +2,4 @@ import {inspect} from 'node:util' import * as specifications from './all' -console.log(inspect(specifications, {depth: 10, colors: true})) +console.log(inspect(specifications, {depth: 18, colors: true})) diff --git a/tools/reflectTypes.mjs b/tools/reflectTypes.mjs index 7676fc8..e15aba9 100644 --- a/tools/reflectTypes.mjs +++ b/tools/reflectTypes.mjs @@ -49,7 +49,7 @@ function reflectType5(srcFile, options = { debug: false }) { for (const id in parsedDefs) { if (id.includes('interface')) continue srcReflected += - `\n${id}.reflectedType6 = ${JSON.stringify(parsedDefs[id])}\n` + `\n${id}._reflectedType6 = ${JSON.stringify(parsedDefs[id])}\n` } writeFileSync(srcFile, srcReflected) diff --git a/tools/ts2json.mjs b/tools/ts2json.mjs index 7fc58a6..8ba67f7 100644 --- a/tools/ts2json.mjs +++ b/tools/ts2json.mjs @@ -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]) } }