feat: Extend type injection to all exported values

This commit is contained in:
Glen Whitney 2024-10-17 22:20:44 -07:00
parent d5b4ccfc2a
commit 66bf2e281a
4 changed files with 204 additions and 122 deletions

View File

@ -17,7 +17,8 @@ export const Complex_type = {
(z: Complex<unknown>) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)), (z: Complex<unknown>) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)),
from: { from: {
Complex: <U,T>(dep: {convert: CommonSignature<U,T>['convert']}) => Complex: <U,T>(dep: {convert: CommonSignature<U,T>['convert']}) =>
(z: Complex<U>) => ({re: dep.convert(z.re), im: dep.convert(z.im)}), (z: Complex<U>): Complex<T> =>
({re: dep.convert(z.re), im: dep.convert(z.im)}),
T: <T>(dep: {zero: CommonSignature<T>['zero']}) => T: <T>(dep: {zero: CommonSignature<T>['zero']}) =>
(t: T) => ({re: t, im: dep.zero(t)}) (t: T) => ({re: t, im: dep.zero(t)})
} }

View File

@ -2,4 +2,4 @@ import {inspect} from 'node:util'
import * as specifications from './all' import * as specifications from './all'
console.log(inspect(specifications, {depth: 10, colors: true})) console.log(inspect(specifications, {depth: 18, colors: true}))

View File

@ -49,7 +49,7 @@ function reflectType5(srcFile, options = { debug: false }) {
for (const id in parsedDefs) { for (const id in parsedDefs) {
if (id.includes('interface')) continue if (id.includes('interface')) continue
srcReflected += srcReflected +=
`\n${id}.reflectedType6 = ${JSON.stringify(parsedDefs[id])}\n` `\n${id}._reflectedType6 = ${JSON.stringify(parsedDefs[id])}\n`
} }
writeFileSync(srcFile, srcReflected) writeFileSync(srcFile, srcReflected)

View File

@ -1,148 +1,229 @@
import * as ts from 'typescript' import * as ts from 'typescript'
const PROPERTY_TYPES = { const intrinsicTypeKeywords = new Set([
any: ts.SyntaxKind.AnyKeyword, ts.SyntaxKind.AnyKeyword,
boolean: ts.SyntaxKind.BooleanKeyword, ts.SyntaxKind.BooleanKeyword,
number: ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.NeverKeyword,
string: ts.SyntaxKind.StringKeyword, 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 { class TSNode {
constructor(name, type) { constructor(name, type) {
this.children = [] this.children = []
this.addChild = (name, type) => { this.addChild = (name, type) => {
let node = new TSNode(name, type) let node = new TSNode(name, type)
this.children.push(node) this.children.push(node)
return node return node
} }
this.getType = () => this.type this.getType = () => this.type
this.getObject = () => { this.getObject = () => {
let map = {} let map = {}
map[this.name] = this.children.length map[this.name] = this.children.length
? this.children ? this.children
.map(child => child.getObject()) .map(child => child.getObject())
.reduce((pv, child) => { .reduce((pv, child) => {
for (let key in child) { for (let key in child) {
if (pv.hasOwnProperty(key) || key in pv) { if (pv.hasOwnProperty(key) || key in pv) {
if (child[key]) { if (child[key]) {
Object.assign(pv[key], child[key]) Object.assign(pv[key], child[key])
} }
} else { } else {
pv[key] = child[key] pv[key] = child[key]
} }
} }
return pv return pv
}, {}) }, {})
: this.type : this.type
return map return map
}; };
this.name = name this.name = name
this.type = type 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) { function typeStructure(typeNode, checker) {
switch (typeNode.kind) { switch (typeNode.kind) {
case ts.SyntaxKind.IntersectionType: case ts.SyntaxKind.UnionType:
return { return {
_union: typeNode.types.map(t => typeStructure(t, checker))
}
case ts.SyntaxKind.IntersectionType:
return {
_intersection: typeNode.types.map(t => typeStructure(t, checker)) _intersection: typeNode.types.map(t => typeStructure(t, checker))
} }
case ts.SyntaxKind.ImportType: case ts.SyntaxKind.ImportType: {
return { const typeStruc = {
_importedFrom: typeNode.argument.literal.text, _importedFrom: typeNode.argument.literal.text,
_name: typeNode.qualifier.text _name: typeNode.qualifier.text
} }
case ts.SyntaxKind.TypeLiteral: if (typeNode.typeArguments) {
return Object.fromEntries(typeNode.members.map( 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)])) mem => [mem.name.text, typeStructure(mem.type, checker)]))
case ts.SyntaxKind.FunctionType: case ts.SyntaxKind.FunctionType: {
return { const typeStruc = {
_parameters: typeNode.parameters.map( _parameters: typeNode.parameters.map(
p => typeStructure(p.type, checker)), p => parameterTypeStructure(p, checker)),
_returns: typeStructure(typeNode.type, checker) _returns: typeStructure(typeNode.type, checker)
} }
case ts.SyntaxKind.LiteralType: if (typeNode.typeParameters) {
return checker.typeToString(checker.getTypeFromTypeNode(typeNode)) typeStruc._typeParameters = typeNode.typeParameters.map(
default: p => p.name.text)
console.log('I See', ts.SyntaxKind[typeNode.kind]) }
} return typeStruc
return checker.getTypeFromTypeNode(typeNode).intrinsicName }
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 => { const visit = (parent, checker) => node => {
switch (node.kind) { switch (node.kind) {
case ts.SyntaxKind.ImportDeclaration: case ts.SyntaxKind.ImportDeclaration:
console.log('Skipping import from', node.moduleSpecifier.text) console.log('Skipping import from', node.moduleSpecifier.text)
break break
case ts.SyntaxKind.VariableStatement: case ts.SyntaxKind.VariableStatement:
console.log('Processing variable', console.log('Processing variable',
ts.SyntaxKind[node.declarationList.kind]) ts.SyntaxKind[node.declarationList.kind])
node.declarationList.declarations.forEach(visit(parent, checker)) node.declarationList.declarations.forEach(visit(parent, checker))
console.log('Done with variable') console.log('Done with variable')
break break
case ts.SyntaxKind.VariableDeclaration: case ts.SyntaxKind.VariableDeclaration: {
console.log('Processing var dec', node.name.text, 'in', parent) console.log('Processing var dec', node.name.text)
const typeStruc = typeStructure(node.type, checker) const typeStruc = typeStructure(node.type, checker)
console.log('Type is', typeStructure(node.type, checker)) parent.addChild(node.name.text, typeStruc)
if ('_intersection' in typeStruc) { break
console.log('First type is', typeStruc._intersection[0]) }
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: case ts.SyntaxKind.ModuleDeclaration:
let moduleName = node.name.text let moduleName = node.name.text
console.log('Declaring', moduleName) console.log('Declaring', moduleName)
ts.forEachChild(node, visit(parent.addChild(moduleName), checker)) ts.forEachChild(node, visit(parent.addChild(moduleName), checker))
break break
case ts.SyntaxKind.ModuleBlock: case ts.SyntaxKind.ModuleBlock:
console.log('Block') console.log('Block')
ts.forEachChild(node, visit(parent, checker)); ts.forEachChild(node, visit(parent, checker));
break break
case ts.SyntaxKind.InterfaceDeclaration: case ts.SyntaxKind.InterfaceDeclaration:
let interfaceName = node.name.text let interfaceName = node.name.text
console.log('Skipping Interface', interfaceName); console.log('Skipping Interface', interfaceName);
break break
parent[interfaceName] = {} parent[interfaceName] = {}
ts.forEachChild(node, visit(parent.addChild(interfaceName), checker)) ts.forEachChild(node, visit(parent.addChild(interfaceName), checker))
break break
case ts.SyntaxKind.PropertySignature: case ts.SyntaxKind.PropertySignature:
let propertyName = node.name let propertyName = node.name
let propertyType = node.type let propertyType = node.type
let arrayDeep = 0 let arrayDeep = 0
let realPropertyName = let realPropertyName =
'string' !== typeof propertyName && 'text' in propertyName 'string' !== typeof propertyName && 'text' in propertyName
? propertyName.text ? propertyName.text
: propertyName : propertyName
console.log('Property', realPropertyName) console.log('Property', realPropertyName)
while (propertyType.kind === ts.SyntaxKind.ArrayType) { while (propertyType.kind === ts.SyntaxKind.ArrayType) {
arrayDeep++ arrayDeep++
propertyType = propertyType.elementType propertyType = propertyType.elementType
} }
if (propertyType.kind === ts.SyntaxKind.TypeReference) { if (propertyType.kind === ts.SyntaxKind.TypeReference) {
let realPropertyType = propertyType.typeName let realPropertyType = propertyType.typeName
parent.addChild( 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, realPropertyName,
'Array<'.repeat(arrayDeep) + checker.getTypeFromTypeNode(propertyType).intrinsicName)
(realPropertyType.kind === ts.SyntaxKind.QualifiedName }
? realPropertyType.getText() }
: 'text' in realPropertyType break
? realPropertyType.text case ts.SyntaxKind.EndOfFileToken:
: realPropertyType) + console.log('File ended.')
'>'.repeat(arrayDeep) break
)
} else {
for (let type in PROPERTY_TYPES) {
if (propertyType.kind === PROPERTY_TYPES[type]) {
parent.addChild(realPropertyName, type)
break
}
}
}
break
default: default:
console.warn( console.warn(
'Unhandled node kind', 'Unhandled node kind',
node.kind, ts.SyntaxKind[node.kind]) node.kind, ts.SyntaxKind[node.kind])
} }
} }