Skip to content

Instantly share code, notes, and snippets.

@goloveychuk
Created April 24, 2017 17:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save goloveychuk/81ff1125d0b079a1faa87aa546bb9dd2 to your computer and use it in GitHub Desktop.
Save goloveychuk/81ff1125d0b079a1faa87aa546bb9dd2 to your computer and use it in GitHub Desktop.
import * as ts from "typescript";
import * as fs from "fs";
import * as types from './lib';
class PropertyInfo {
constructor(readonly name: string, readonly type: types.AllTypes) {
}
}
class TypeWrapper {
constructor(readonly name: string) {}
}
class ClsInfo {
constructor(readonly name: string) {
}
properties: PropertyInfo[] = []
};
export function unwrap<T>(typ: T | undefined): T | never {
if (typ === undefined) throw new Error(`type is undefined`);
return typ
}
class Visiter {
result: ClsInfo[] = []
constructor(readonly checker: ts.TypeChecker, readonly sourceFile: ts.SourceFile) {
}
visitAllNodes() {
ts.forEachChild(this.sourceFile, this.visit);
}
serializePropType(type: ts.TypeNode): types.AllTypes {
if (type.kind == ts.SyntaxKind.TypeReference) {
const nodType = <ts.TypeReferenceNode>type;
const typeName = nodType.typeName.getText()
const typeArgs = nodType.typeArguments;
let allTypes: types.AllTypes[] = [];
if (typeArgs !== undefined) {
allTypes = typeArgs.map(t => this.serializePropType(t))
}
return { kind: types.TypeKind.Reference, arguments: allTypes, type: new TypeWrapper(typeName) }
} else if (type.kind == ts.SyntaxKind.UnionType) {
const nodType = <ts.UnionTypeNode>type;
const nestedTypes = nodType.types.map(t => this.serializePropType(t))
return { kind: types.TypeKind.Union, types: nestedTypes }
} else if (type.kind == ts.SyntaxKind.NumberKeyword) {
return { kind: types.TypeKind.Number }
} else if (type.kind == ts.SyntaxKind.BooleanKeyword) {
return { kind: types.TypeKind.Boolean }
} else if (type.kind == ts.SyntaxKind.StringKeyword) {
return { kind: types.TypeKind.String }
} else if (type.kind == ts.SyntaxKind.UndefinedKeyword) {
return { kind: types.TypeKind.Undefined }
} else if (type.kind == ts.SyntaxKind.NullKeyword) {
return { kind: types.TypeKind.Null }
}
throw new Error()
}
visitClsChild(clsInfo: ClsInfo, node: ts.Node) {
if (node.kind === ts.SyntaxKind.PropertyDeclaration) {
const nod = <ts.PropertyDeclaration>node
const propName = nod.name.getText();
const nodeType = unwrap(nod.type)
const optional = nod.questionToken !== undefined
let serializedType = this.serializePropType(nodeType)
if (optional) {
serializedType = {kind: types.TypeKind.Union, types: [serializedType, {kind: types.TypeKind.Undefined}]}
}
const property = new PropertyInfo(propName, serializedType)
clsInfo.properties.push(property)
}
}
visit = (node: ts.Node) => {
if (node.kind === ts.SyntaxKind.ClassDeclaration) {
const nod = <ts.ClassDeclaration>node;
if (nod.name == undefined) {
throw new Error('cls name is undefined')
}
const clsInfo = new ClsInfo(nod.name.text)
this.result.push(clsInfo)
this.checker.getSymbolsInScope
ts.forEachChild(node, (n: ts.Node) => this.visitClsChild(clsInfo, n));
}
else if (node.kind === ts.SyntaxKind.ModuleDeclaration) {
ts.forEachChild(node, this.visit);
}
}
}
function serializeType(obj: any): string {
if (obj instanceof TypeWrapper) {
return obj.name
}
if (Array.isArray(obj)) {
const arr = obj.map(o => serializeType(o)).join(', ')
return `[${arr}]`
}
if (obj instanceof Object) {
let res: string[] = []
for (const k in obj) {
let s = JSON.stringify(k)
s+= ': '
s+=serializeType(obj[k])
res.push(s)
}
return `{ ${res.join(', ')} }`
}
return JSON.stringify(obj)
}
function makeDecorators(cls: ClsInfo[]) {
const res: string[] = []
cls.forEach(cls => {
cls.properties.forEach(pr => {
const typ = serializeType(pr.type)
const dec = `Reflect.defineMetadata("tsreflect:type", ${typ}, ${cls.name}, "${pr.name}")`
res.push(dec)
})
})
return res.join("\n")
}
function Proceed() {
// console.log(text)
const filepath = process.argv[2];
let text = fs.readFileSync(filepath).toString();
const sourceFile = ts.createSourceFile(filepath, text, ts.ScriptTarget.ES2016, true)
let program = ts.createProgram([], { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS });
let checker = program.getTypeChecker();
// const sourceFile = program.getSourceFile(filepath)
const visiter = new Visiter(checker, sourceFile)
visiter.visitAllNodes();
const res = makeDecorators(visiter.result)
return text + "\n\n" + res
}
console.log(Proceed())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment