diff --git a/src/index.ts b/src/index.ts index 66d4a5b..648a3b9 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: 8, colors: true})) +console.log(inspect(specifications, {depth: 10, colors: true})) diff --git a/tools/reflectTypes.mjs b/tools/reflectTypes.mjs index 89c6af6..7676fc8 100644 --- a/tools/reflectTypes.mjs +++ b/tools/reflectTypes.mjs @@ -2,6 +2,8 @@ import { readFileSync, writeFileSync, readdirSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, join, relative } from 'node:path' +import ts2json from './ts2json.mjs' + const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) @@ -15,11 +17,13 @@ for (const file of files) { } function reflectType5(srcFile, options = { debug: false }) { - log(`Reflecting file "${relative(__dirname, srcFile)}"`) + log(`=========Reflecting file "${relative(__dirname, srcFile)}"`) const defFile = srcFile.replace(/.js$/, '.d.ts') const src = String(readFileSync(srcFile)) const defs = String(readFileSync(defFile)) + const parsedDefs = ts2json(defFile) + console.log('Defs from', defFile, 'are', parsedDefs) const typeDefMatches = defs.matchAll(/: ({(?:(?!\n}).)+\n}) & (?:(?!ReflectedTypeInfo).)+ReflectedTypeInfo/gs) if (!typeDefMatches) { @@ -31,7 +35,7 @@ function reflectType5(srcFile, options = { debug: false }) { log(` ${typeDefs.length} ReflectedTypeInfo found`) let index = 0 - const srcReflected = src.replaceAll(/(\s*)\.ship\(\)/g, () => { + let srcReflected = src.replaceAll(/(\s*)\.ship\(\)/g, () => { const def = typeDefs[index] index++ return `.ship({ reflectedType5: \`${def}\` })` @@ -42,6 +46,11 @@ function reflectType5(srcFile, options = { debug: false }) { log(' WARNING: not all ReflectedTypeInfo occurrences could be injected') } + for (const id in parsedDefs) { + if (id.includes('interface')) continue + srcReflected += + `\n${id}.reflectedType6 = ${JSON.stringify(parsedDefs[id])}\n` + } writeFileSync(srcFile, srcReflected) function log(...args) { diff --git a/tools/ts2json.mjs b/tools/ts2json.mjs new file mode 100644 index 0000000..7fc58a6 --- /dev/null +++ b/tools/ts2json.mjs @@ -0,0 +1,161 @@ +import * as ts from 'typescript' + +const PROPERTY_TYPES = { + any: ts.SyntaxKind.AnyKeyword, + boolean: ts.SyntaxKind.BooleanKeyword, + number: ts.SyntaxKind.NumberKeyword, + string: ts.SyntaxKind.StringKeyword, +} + +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 + } +} + +function typeStructure(typeNode, checker) { + switch (typeNode.kind) { + case ts.SyntaxKind.IntersectionType: + return { + _intersection: typeNode.types.map(t => typeStructure(t, checker)) + } + case ts.SyntaxKind.ImportType: + return { + _importedFrom: typeNode.argument.literal.text, + _name: typeNode.qualifier.text + } + case ts.SyntaxKind.TypeLiteral: + return Object.fromEntries(typeNode.members.map( + mem => [mem.name.text, typeStructure(mem.type, checker)])) + case ts.SyntaxKind.FunctionType: + return { + _parameters: typeNode.parameters.map( + p => typeStructure(p.type, 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 +} + +const visit = (parent, checker) => node => { + switch (node.kind) { + case ts.SyntaxKind.ImportDeclaration: + 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]) + } + 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 + case ts.SyntaxKind.ModuleBlock: + 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 + 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( + 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 + default: + console.warn( + 'Unhandled node kind', + node.kind, ts.SyntaxKind[node.kind]) + } +} + +export default function(filename, options = {}) { + const ROOT_NAME = 'root' + const node = new TSNode(ROOT_NAME) + + let program = ts.createProgram([filename], options) + let checker = program.getTypeChecker() + const sourceFiles = program.getSourceFiles() + let sourceFile = sourceFiles.find(file => file.fileName === filename) + + ts.forEachChild(sourceFile, visit(node, checker)) + + return node.getObject()[ROOT_NAME] +}