typocomath/plugins/infer.ts

99 lines
3.1 KiB
TypeScript

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(<T>(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 =
// <T>(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);
});