Experiment of creating a TypeScript plugin (WIP)
This commit is contained in:
parent
946b4a495f
commit
b9cfe706fc
@ -5,6 +5,8 @@
|
|||||||
main: 'index.ts',
|
main: 'index.ts',
|
||||||
scripts: {
|
scripts: {
|
||||||
'build-and-run': 'ttsc -b && node build',
|
'build-and-run': 'ttsc -b && node build',
|
||||||
|
'experiment-infer': 'tsc plugins/infer.ts && node plugins/infer.js ./src/generic/arithmetic.ts',
|
||||||
|
'experiment-infer-direct': 'tsc plugins/infer.ts && node plugins/infer.js ./src/generic/arithmeticDirect.ts',
|
||||||
test: 'echo "Error: no test specified" && exit 1',
|
test: 'echo "Error: no test specified" && exit 1',
|
||||||
},
|
},
|
||||||
keywords: [
|
keywords: [
|
||||||
|
1
plugins/.gitignore
vendored
Normal file
1
plugins/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.js
|
98
plugins/infer.ts
Normal file
98
plugins/infer.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
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);
|
||||||
|
});
|
8
src/generic/arithmeticDirect.ts
Normal file
8
src/generic/arithmeticDirect.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import {infer} from './infer'
|
||||||
|
|
||||||
|
export const square = infer(<T>(dep: {
|
||||||
|
multiply: (a: number, b: number) => number,
|
||||||
|
unaryMinus: (x: number) => number, // just for the experiment
|
||||||
|
}): (a: number) => number =>
|
||||||
|
z => dep.multiply(z, z)
|
||||||
|
)
|
4
src/generic/infer.ts
Normal file
4
src/generic/infer.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export function infer<T>(arg: T) : T {
|
||||||
|
console.error('infer should be replace with runtime type information by a magic TypeScript plugin')
|
||||||
|
return arg
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user