diff --git a/README.md b/README.md index 968cf51..6402234 100644 --- a/README.md +++ b/README.md @@ -11,28 +11,45 @@ pnpm build-and-run ## experiment -Have a look at the section under `/src/experiment` and `/src/plugins`: - -- `src/plugins/typeInferPlugin.ts` is the actual plugin -- in `tsconfig.json` we configure TypeScript to run the plugin -- `src/experiment/arithmeticInfer.ts` with an example where we define `__infer__` -- after running TypeScript: look at `build/experiment/arithmeticInfer.ts` where the `__infer__` string literal is replaced with the actual types +See: the section under `/src/experiment` and `/src/plugins`. ### The idea -Create a TypeScript plugin which can replace a string literal like `__infer__` in a typed-function definition: +Create a TypeScript plugin which can replace structures like: - typed('square', '__infer__', (dep: { ... } => { ... }) + infer(factoryFunction) -with the actual types, something like: +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. + +Relevant methods of the TypeScript compiler are: - typed('square', '{ deps: { multiply: (a: T, b: T) => T; }; return: (a: T) => T }', (dep: { ... } => { ... }) +```ts +const program = ts.createProgram(fileNames, options) +const typeChecker = program.getTypeChecker() + +// relevant methods: +// +// typeChecker.getSymbolAtLocation +// typeChecker.getTypeOfSymbolAtLocation +// typeChecker.getResolvedSignature +// typeChecker.getSignaturesOfType +``` + +### Status + +None of the experiments (`infer1` and `infer2`) are outputting something useful yet. -(We can discuss what syntax we like most, this is just a POC) ### How to run - pnpm build-and-run + pnpm experiment:infer1 + pnpm experiment:infer1-direct + pnpm experiment:infer2 + pnpm experiment:infer3 ### Read more @@ -44,8 +61,3 @@ with the actual types, something like: - https://stackoverflow.com/questions/48886508/typechecker-api-how-do-i-find-inferred-type-arguments-to-a-function - https://blog.logrocket.com/using-typescript-transforms-to-enrich-runtime-code-3fd2863221ed/ - https://github.com/itsdouges/typescript-transformer-handbook#transforms - -### Interesting libraries - -- https://github.com/GoogleFeud/ts-macros/ -- https://ts-morph.com diff --git a/package.json5 b/package.json5 index 02bb592..3c214e5 100644 --- a/package.json5 +++ b/package.json5 @@ -4,7 +4,12 @@ description: 'A hopeful final typescipt-pragmatic mathjs proof-of-concept', main: 'index.ts', scripts: { - 'build-and-run': 'ttsc -b && node build/index.js', + 'build-and-run': 'ttsc -b && node build', + 'experiment:infer1': 'ttsc -b && node build/plugins/infer1.js ./src/generic/arithmetic.ts', + 'experiment:infer1-direct': 'ttsc -b && node build/plugins/infer1.js ./src/experiment/arithmeticInfer1.ts', + 'experiment:infer2': 'ttsc -b && node build/plugins/infer2.js ./src/experiment/arithmeticInfer2.ts', + 'experiment:infer3': 'ttsc -b && node build/plugins/infer3.js ./src/experiment/arithmeticInfer3.js', + test: 'echo "Error: no test specified" && exit 1', }, keywords: [ 'math', @@ -18,9 +23,12 @@ url: 'https://code.studioinfinity.org/glen/typocomath.git', }, dependencies: { - '@types/node': '20.5.7', + 'reflect-metadata': '0.1.13', + 'source-map': '^0.7.4', + 'typescript-rtti': '0.8.3', }, devDependencies: { + '@types/node': '18.11.18', 'ts-node': '10.9.1', ttypescript: '1.5.15', typescript: '4.7.4', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fdeb100..47c4737 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,16 +1,22 @@ lockfileVersion: 5.4 specifiers: - '@types/node': 20.5.7 + '@types/node': 18.11.18 + reflect-metadata: 0.1.13 + source-map: ^0.7.4 ts-node: 10.9.1 ttypescript: 1.5.15 typescript: 4.7.4 + typescript-rtti: 0.8.3 dependencies: - '@types/node': 20.5.7 + reflect-metadata: 0.1.13 + source-map: 0.7.4 + typescript-rtti: 0.8.3_qh5shpxvbkbt6m3jqtkkp2svgu devDependencies: - ts-node: 10.9.1_l7whiu4appksmcywzzf5ucsgha + '@types/node': 18.11.18 + ts-node: 10.9.1_nv75g3i7xuh23du6z7qul3uiqi ttypescript: 1.5.15_6oasmw356qmm23djlsjgkwvrtm typescript: 4.7.4 @@ -55,8 +61,9 @@ packages: resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} dev: true - /@types/node/20.5.7: - resolution: {integrity: sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==} + /@types/node/18.11.18: + resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} + dev: true /acorn-walk/8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} @@ -107,6 +114,10 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true + /reflect-metadata/0.1.13: + resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} + dev: false + /resolve/1.22.4: resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} hasBin: true @@ -116,12 +127,17 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true + /source-map/0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + dev: false + /supports-preserve-symlinks-flag/1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} dev: true - /ts-node/10.9.1_l7whiu4appksmcywzzf5ucsgha: + /ts-node/10.9.1_nv75g3i7xuh23du6z7qul3uiqi: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -140,7 +156,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.5.7 + '@types/node': 18.11.18 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -160,15 +176,25 @@ packages: typescript: '>=3.2.2' dependencies: resolve: 1.22.4 - ts-node: 10.9.1_l7whiu4appksmcywzzf5ucsgha + ts-node: 10.9.1_nv75g3i7xuh23du6z7qul3uiqi typescript: 4.7.4 dev: true + /typescript-rtti/0.8.3_qh5shpxvbkbt6m3jqtkkp2svgu: + resolution: {integrity: sha512-uX1A0JKs1o/ptLJqkubRCGgN7NOCYSTKRXyRIjG80exsLrPDq4jJWMfQxlHMAcv/zjoX0V6iIGU7bwjGWTzpLg==} + engines: {node: '>=10'} + peerDependencies: + reflect-metadata: ^0.1.13 + typescript: ^4.5 || ^4.6 || ^4.7 + dependencies: + reflect-metadata: 0.1.13 + typescript: 4.7.4 + dev: false + /typescript/4.7.4: resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} engines: {node: '>=4.2.0'} hasBin: true - dev: true /v8-compile-cache-lib/3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index 4ba58fa..1da89ff 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -1,3 +1,6 @@ +import "reflect-metadata" +import { reflect, type CallSite } from 'typescript-rtti' + /* A Dispatcher is a collection of operations that do run-time * dispatch on the types of their arguments. Thus, every individual * method is like a typed-function (from the library by that name), @@ -48,15 +51,15 @@ export class Dispatcher { console.log('Pretending to install', name, signature, '=>', returns) // @ts-ignore - // console.log(name, 'signature', reflect(signature)) - // console.log(name, 'dependencies', reflect(dependencies)) + console.log(name, 'signature', reflect(signature)) + console.log(name, 'dependencies', reflect(dependencies)) //TODO: implement me } installType(name: TypeName, typespec: TypeSpecification) { console.log('Pretending to install type', name, typespec) //TODO: implement me } - constructor(collection: SpecificationsGroup) { + constructor(collection: SpecificationsGroup) { for (const key in collection) { console.log('Working on', key) for (const identifier in collection[key]) { diff --git a/src/core/typed.ts b/src/core/typed.ts deleted file mode 100644 index f66a4ad..0000000 --- a/src/core/typed.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function typed(name: string, types: string, arg: T) : T { - // TODO: implement typed-function for real - if (types === '__infer__') { - console.error('__infer__ should be replaced with runtime type information by the TypeScript plugin') - } - - console.log(`TYPED-FUNCTION: Creating function "${name}" with types ${types}`) - return arg -} diff --git a/src/experiment/arithmeticInfer.ts b/src/experiment/arithmeticInfer.ts deleted file mode 100644 index be544a3..0000000 --- a/src/experiment/arithmeticInfer.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { typed } from '../core/typed.js' - -export const square = typed('square', '__infer__', (dep: { - multiply: (a: T, b: T) => T, - unaryMinus: (x: T) => T, // just for the experiment -}): (a: T) => T => - z => dep.multiply(z, z) -) diff --git a/src/experiment/arithmeticInfer1.ts b/src/experiment/arithmeticInfer1.ts new file mode 100644 index 0000000..92aeffd --- /dev/null +++ b/src/experiment/arithmeticInfer1.ts @@ -0,0 +1,8 @@ +import {infer} from '../generic/infer' + +export const square = infer((dep: { + multiply: (a: number, b: number) => number, + unaryMinus: (x: number) => number, // just for the experiment +}): (a: number) => number => + z => dep.multiply(z, z) +) diff --git a/src/experiment/arithmeticInfer2.ts b/src/experiment/arithmeticInfer2.ts new file mode 100644 index 0000000..ab4eebf --- /dev/null +++ b/src/experiment/arithmeticInfer2.ts @@ -0,0 +1,15 @@ +import { infer } from '../generic/infer' +import { Dependencies, Signature } from '../interfaces/type' + +export type multiplyDep = Dependencies<'multiply', T> + +export const square1 = + (dep: Dependencies<'multiply', T>): Signature<'square', T> => + z => dep.multiply(z, z) + +export const square2 = infer((dep: { + multiply: (a: number, b: number) => number, + unaryMinus: (x: number) => number, // just for the experiment +}): (a: number) => number => + z => dep.multiply(z, z) +) diff --git a/src/experiment/arithmeticInfer3.js b/src/experiment/arithmeticInfer3.js new file mode 100644 index 0000000..2d0b6da --- /dev/null +++ b/src/experiment/arithmeticInfer3.js @@ -0,0 +1,16 @@ +// @ts-check + +/** + * Function square + * + * Description of function square bla bla bla + * + * @param {{ + * multiply: (a: number, b: number) => number, + * unaryMinus: (x: number) => number + * }} dep + * @return {(a: number) => number} + */ +export function square3 (dep) { + return z => dep.multiply(z, z) +} diff --git a/src/generic/infer.ts b/src/generic/infer.ts new file mode 100644 index 0000000..ac038dd --- /dev/null +++ b/src/generic/infer.ts @@ -0,0 +1,4 @@ +export function infer(arg: T) : T { + console.error('infer should be replaced with runtime type information by a magic TypeScript plugin') + return arg +} diff --git a/src/index.ts b/src/index.ts index 9fd1e68..ce04c21 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,28 +1,99 @@ -import { Complex } from './Complex/type.js' -import { absquare as absquare_complex } from './Complex/arithmetic.js' -import { square } from './experiment/arithmeticInfer.js' +import 'reflect-metadata' -const add = (a: number, b: number) => a + b -const multiply = (a: number, b: number) => a * b -const unaryMinus = (a: number) => -a -const absquare = (z: Complex) => z.re * z.re + z.im * z.im +import {Dispatcher} from './core/Dispatcher.js' +import * as specifications from './all.js' +import {Complex} from './Complex/type.js' +import {absquare as absquare_complex} from './Complex/arithmetic.js' + +import { CallSite, ReflectedObjectRef, reflect } from 'typescript-rtti' +import { square } from './generic/arithmetic.js' + +// verify that typescript-rtti works (just as experiment) +const add = (a: number, b: number): number => a + b +console.log('reflect add') +console.log('parameterNames', reflect(add).parameterNames) +console.log('parameterTypes', reflect(add).parameterTypes.map(type => type.toString())) +console.log('returnType', reflect(add).returnType.toString()) +console.log() +// output: +// reflect function add +// parameterNames [ 'a', 'b' ] +// parameterTypes [ 'class Number', 'class Number' ] +// returnType class Number + +// try out a very simple case (just as experiment) +function createSquare(deps: { + multiply: (a: number, b: number) => number, + subtract: (a: number, b: number) => number +}) { + return (a: number) => deps.multiply(a, a) +} +console.log('reflect createSquare') +console.log('parameter names', reflect(createSquare).parameters.map(parameter => parameter.name)) +// console.log('parameter[0]', (reflect(createSquare).parameters[0] as ReflectedFunctionParameter)) +// @ts-ignore +console.log('parameterTypes[0]', (reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m) +console.log('parameterTypes[0].ref.m[0]', + // @ts-ignore + (reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m[0].n, + // @ts-ignore + (reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m[0] +) +console.log('parameterTypes[0].ref.m[0].t.m', + // @ts-ignore + (reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m[0].t.m +) +// @ts-ignore +// console.log('parameters[0]', reflect(createSquare).parameters[0]) +// FIXME: where to find the information of the types of the dependencies multiply and subtract? + +// Test whether we loose the type information when casting to a generic interface +// Conclusion: we keep the information, that is good. +console.log() +console.log('reflect createFunction') +type MathjsDependencies = Record +type MathjsCreateFunction = (deps: MathjsDependencies) => Function +const createFunction: MathjsCreateFunction = createSquare as MathjsCreateFunction +console.log('parameter names', reflect(createFunction).parameters.map(parameter => parameter.name)) +// @ts-ignore +console.log('parameterTypes[0]', (reflect(createFunction).parameterTypes[0] as ReflectedObjectRef)._ref.m) + +// TODO: more specific definition of Specifications +type Specifications = Record> + +console.log() +console.log('CallSite') +function reflectSpecifications(specifications: Specifications, callSite? : CallSite) { + console.log('specifications', reflect(callSite).parameters[0]) + // @ts-ignore + console.log('specifications', reflect(callSite).parameters[0]._ref) // shows 'numbers', 'Complex, 'complex', 'generic' + // @ts-ignore + console.log('specifications', reflect(callSite).parameters[0]._ref.m + .find(item => item.n === 'generic').t.m) // shows 'square', 'unequal' + // @ts-ignore + console.log('specifications', reflect(callSite).parameters[0]._ref.m + .find(item => item.n === 'generic').t.m + .find(item => item.n === 'square').t.p) // [ { n: 'dep', t: [Function: t], b: undefined, v: null } ] + // @ts-ignore + // FIXME: now, we should be able to get the signature of the multiply dependency of the function square, but how? +} +reflectSpecifications(specifications); + + +// TODO: import all specifications (turned off for debugging purposes) +// export default new Dispatcher(Specifications) + + +const mockRealAdd = (a: number, b: number) => a+b +const mockComplexAbsquare = (z: Complex) => z.re*z.re + z.im*z.im const quatAbsquare = absquare_complex({ - add, - absquare + add: mockRealAdd, + absquare: mockComplexAbsquare }) -const result = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}}) -const typeTest: typeof result = 7 // check myabs is just a number +const myabs = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}}) +const typeTest: typeof myabs = 7 // check myabs is just a number console.log() -console.log('Result is', result) - - -const mySquare = square({ - multiply, - unaryMinus -}) - -console.log() -console.log('mySquare(4)=', mySquare(4)) +console.log('Result is', myabs) diff --git a/src/plugins/infer1.ts b/src/plugins/infer1.ts new file mode 100644 index 0000000..9aea0da --- /dev/null +++ b/src/plugins/infer1.ts @@ -0,0 +1,73 @@ +import { readFileSync } from "fs"; +import * as ts from "typescript"; +import { inspect } from 'util' + +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); +}); diff --git a/src/plugins/infer2.ts b/src/plugins/infer2.ts new file mode 100644 index 0000000..43fc64b --- /dev/null +++ b/src/plugins/infer2.ts @@ -0,0 +1,74 @@ +import * as ts from "typescript" + +// based on: https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#using-the-type-checker + +infer2(process.argv.slice(2), { + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.CommonJS +}) + +function infer2( + fileNames: string[], + options: ts.CompilerOptions +): void { + const program = ts.createProgram(fileNames, options) + const typeChecker = program.getTypeChecker() + + for (const sourceFile of program.getSourceFiles()) { + if (!sourceFile.isDeclarationFile) { + ts.forEachChild(sourceFile, visit) + } + } + + return + + function visit(node: ts.Node) { + // // Only consider exported nodes + // if (!isNodeExported(node)) { + // return; + // } + + // console.log('Node', node.kind, node?.['name']?.escapedText) + + if (ts.isModuleDeclaration(node)) { + // This is a namespace, visit its children + console.log('check') + ts.forEachChild(node, visit); + } else if (ts.isTypeAliasDeclaration(node)) { + console.log('isTypeAliasDeclaration', node.name.escapedText) + + let symbol = typeChecker.getSymbolAtLocation(node.name); + if (symbol) { + const symbolType = typeChecker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration) + const symbolSignature = typeChecker.getSignaturesOfType(symbolType, ts.SignatureKind.Call) + + // checker.getResolvedSignature(symbol) + console.log('symbol', symbol.getName(), symbolSignature) + + // getTypeOfSymbolAtLocation + // getResolvedSignature + } + } else if (ts.isCallExpression(node)) { + console.log('isCallExpression', node.expression) + } else if (ts.isFunctionDeclaration(node)) { + console.log('isFunctionDeclaration', node.name.escapedText, { typeParameter0: node.typeParameters[0] }) + + if (node.name.escapedText === 'infer') { + const param0 = node.typeParameters[0] + if (ts.isPropertyDeclaration(param0)) { + const symbol = typeChecker.getSymbolAtLocation(param0) + + // TODO: get resolving + + // console.log('getResolvedSignature', typeChecker.getResolvedSignature(node) ) + + // const symbolType = typeChecker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration) + // const symbolSignature = typeChecker.getSignaturesOfType(symbolType, ts.SignatureKind.Call) + // console.log('symbol', symbol.getName(), symbolSignature) + + // console.log('getSignaturesOfType', typeChecker.getSignaturesOfType(param0) + } + } + } + } +} diff --git a/src/plugins/infer3.ts b/src/plugins/infer3.ts new file mode 100644 index 0000000..5c599db --- /dev/null +++ b/src/plugins/infer3.ts @@ -0,0 +1,65 @@ +import { readFileSync } from "fs"; +import * as ts from "typescript"; +import { inspect } from 'util' + +export function infer3(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' + case ts.SyntaxKind.JSDoc: return 'jsdoc' + 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]) + } + + if (node['jsDoc']) { + console.log('Found a JSDoc comment:') + // console.log(inspect(node['jsDoc'], { depth: null, colors: true })) + + const fullComment = sourceFile.text.slice(node.pos, node.end) + console.log(fullComment) + // TODO: next steps: + // - either get the types from the TypeScript AST, + // or extract them ourselves with regex or anything from the comment text + // - After that, we have to transform the source file and insert the comments + // as string or object that is runtime accessible in JavaScript + } + + 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('FILE') + console.log(fileName) + console.log() + + console.log('SOURCE') + console.log(sourceFile.text) + console.log() + + console.log('AST') + console.log(inspect(sourceFile, { depth: null, colors: true })) + console.log() + + console.log('INFER') + infer3(sourceFile); +}); diff --git a/src/plugins/typeInferPlugin.ts b/src/plugins/typeInferPlugin.ts deleted file mode 100644 index 4ca6c21..0000000 --- a/src/plugins/typeInferPlugin.ts +++ /dev/null @@ -1,56 +0,0 @@ -import ts from 'typescript' - -const transformer: ts.TransformerFactory = context => { - // TODO: get a reference to the program instance that the plugin is running in instead of creating a new program? - const program = ts.createProgram([], {}) - const checker = program.getTypeChecker() - - return sourceFile => { - const visitor = (node: ts.Node): ts.Node => { - // @ts-ignore - if (ts.isStringLiteral(node) && node.text === '__infer__') { - console.log('PLUGIN: FOUND AN OCCURRENCE OF __infer__') - - // we're looking for a function call like typed('name', '__infer__', deps => ...) - const parentNode = node.parent - if (ts.isCallExpression(parentNode) && ts.isIdentifier(parentNode.expression) && parentNode.expression.escapedText === 'typed') { - // console.log('PARENT') - // console.log(parentNode) - - // TODO: validate that argNode is an ArrowFunction - // @ts-ignore - const argNode = parentNode.arguments[2] - // @ts-ignore - const returnType = argNode.type.getText(sourceFile) - console.log('PLUGIN: RETURN TYPE') - console.log(returnType) - // (a: number) => number - - // @ts-ignore - const paramNode = argNode.parameters[0] - const paramTypeSrc = paramNode.type.getText(sourceFile) - console.log('PLUGIN: PARAM TYPE SRC', paramTypeSrc) - // { - // multiply: (a: number, b: number) => number, - // unaryMinus: (x: number) => number, // just for the experiment - // } - - const type = checker.getTypeAtLocation(paramNode) - const paramType = checker.typeToString(type, paramNode, ts.TypeFormatFlags.InTypeAlias) - console.log('PLUGIN: PARAM TYPE STRING', paramType) - // { multiply: (a: number, b: number) => number; unaryMinus: (x: number) => number; } - - const depsAndReturnType = `{ deps: ${paramType}; return: ${returnType} }` - - return ts.factory.createStringLiteral(depsAndReturnType) - } - } - - return ts.visitEachChild(node, visitor, context) - } - - return ts.visitNode(sourceFile, visitor) - } -} - -export default transformer diff --git a/tsconfig.json b/tsconfig.json index c6b3a50..c761525 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,9 +8,10 @@ "noImplicitAny": false, "moduleResolution": "Node", "module": "commonjs", - "plugins": [{ - "transform": "./src/plugins/typeInferPlugin.ts", - "type": "raw" - }] + "plugins": [ + { + "transform": "typescript-rtti/dist/transformer" + } + ] } }