import { readFileSync } from "fs"; import * as ts from "typescript"; import { inspect } from 'util' /** * # The idea * * Create a TypeScript plugin which can replace structures like: * * infer(factoryFunction) * * where `factoryFunction` is a mathjs factory function in TypeScript, with something like: * * infer({ signature: factoryFunction }) * * where `signature` is a string containing the type of the factory function and its dependencies. * * # How to run * * pnpm experiment-infer * pnpm experiment-infer-direct * * # Read more * * - https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin * - https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API * - https://github.com/Microsoft/TypeScript/wiki/Using-the-Language-Service-API */ export function infer(sourceFile: ts.SourceFile) { recurse(sourceFile); function getType(kind: number) { switch(kind) { case ts.SyntaxKind.NumberKeyword: return 'number' case ts.SyntaxKind.StringKeyword: return 'string' case ts.SyntaxKind.BooleanKeyword: return 'boolean' default: return String(ts.SyntaxKind[kind]) // TODO: work out all types } } function recurse(node: ts.Node) { if (node.kind === ts.SyntaxKind.Identifier) { console.log('Identifier', node['escapedText'], ts.SyntaxKind[node.kind]) } // recognize a structure like: // // export const square = infer((dep: { // multiply: (a: number, b: number) => number // }): (a: number) => number => // z => dep.multiply(z, z) // ) if (node?.['name']?.kind === ts.SyntaxKind.Identifier && node?.['name']['escapedText'] === 'dep') { console.log('dep', getType(node['type'].kind), node) node['type']?.members?.forEach(member => { console.log('member', { name: member.name.escapedText, parameters: member.type.parameters.map(parameter => { return parameter.name.escapedText + ': ' + getType(parameter.type.kind) }), returns: getType(member.type.type.kind) }) }) } // recognize a structure like: // // export const square = // (dep: Dependencies<'multiply' | 'unaryMinus', T>): Signature<'square', T> => // z => dep.multiply(z, z) if (node?.['name']?.kind === ts.SyntaxKind.Identifier && node?.['name']['escapedText'] === 'dep') { // TODO } ts.forEachChild(node, recurse); } } const fileNames = process.argv.slice(2); console.log('infer files', fileNames) fileNames.forEach(fileName => { // Parse a file const sourceFile = ts.createSourceFile( fileName, readFileSync(fileName).toString(), ts.ScriptTarget.ES2022, /*setParentNodes */ true ); console.log('AST', fileName, inspect(sourceFile, { depth: null, colors: true })) console.log(sourceFile.text) console.log() infer(sourceFile); });