From 4f90e865fa376332408a2c3792650901c429fbd1 Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Sun, 11 Aug 2019 13:16:29 +0700 Subject: [PATCH] Use experimental Typescript API to process class defs The goal is to generate reusable interfaces from entity definitions. While class definitions could be made to implement interfaces, this seems like too much work at this point. --- packages/scripts/src/commands/index.ts | 1 + .../scripts/src/commands/intergen.sample.ts | 38 +++++++ packages/scripts/src/commands/intergen.ts | 107 ++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 packages/scripts/src/commands/intergen.sample.ts create mode 100644 packages/scripts/src/commands/intergen.ts diff --git a/packages/scripts/src/commands/index.ts b/packages/scripts/src/commands/index.ts index 2e3cc30..7dee872 100644 --- a/packages/scripts/src/commands/index.ts +++ b/packages/scripts/src/commands/index.ts @@ -1,2 +1,3 @@ export * from './build' export * from './newlib' +export * from './intergen' diff --git a/packages/scripts/src/commands/intergen.sample.ts b/packages/scripts/src/commands/intergen.sample.ts new file mode 100644 index 0000000..c3a145d --- /dev/null +++ b/packages/scripts/src/commands/intergen.sample.ts @@ -0,0 +1,38 @@ +export class Primitives { + str!: string + num!: number + bool!: boolean + + strArray!: string[] + numArray!: number[] + boolArray!: boolean[] +} + +export class Name { + firstName!: string + lastName!: string +} + +export interface IYear { + year: number +} + +type AorB = 'A' | 'B' + +export class Person { + readonly name!: Name + readonly aliases?: Name[] + readonly aOrB!: AorB + readonly inlineAOrB?: 'a' | 'b' + readonly inlineInterface?: { + a: number + b: string + } + age?: number + birthyear: IYear | null = null + stringAndNumberTuple!: [string, number] +} + +export class Employee extends Person { + duties: string[] = [] +} diff --git a/packages/scripts/src/commands/intergen.ts b/packages/scripts/src/commands/intergen.ts new file mode 100644 index 0000000..909f55f --- /dev/null +++ b/packages/scripts/src/commands/intergen.ts @@ -0,0 +1,107 @@ +import * as path from 'path' +import * as ts from 'typescript' +import {readFileSync} from 'fs' + +function delint(sourceFile: ts.SourceFile) { + delintNode(sourceFile) + + function delintNode(node: ts.Node) { + // TODO check which classes are exported + // TODO check which interfaces are in use + // TODO check which type references are in use in addition to the + // primitives like string, string[], number, number[], boolean, + // boolean[] + switch (node.kind) { + case ts.SyntaxKind.ClassDeclaration: + const cls = node as ts.ClassDeclaration + if (!cls.name) { + // TODO warn + console.log('no class name', cls.pos) + break + } + console.log(cls.name.escapedText) + for (const m of cls.members) { + if (m.kind !== ts.SyntaxKind.PropertyDeclaration) { + console.log('not implemented support for node.kind:', m.kind) + break + } + const member = m as ts.PropertyDeclaration + const name = m.name as ts.Identifier + console.log(' ', name.escapedText) + if (m.modifiers) { + for (const modifier of m.modifiers) { + if (modifier.kind === ts.SyntaxKind.ReadonlyKeyword) { + console.log(' ', 'readonly') + } else { + console.log(' modifier.kind:', modifier.kind) + } + } + } + if (!member.type) { + console.log('no member type!') + break + } + // console.log(' member:', JSON.stringify(member, null, ' ')) + console.log(' questionToken:', !!member.questionToken) + switch (member.type.kind) { + case ts.SyntaxKind.StringKeyword: + console.log(' string') + break + case ts.SyntaxKind.BooleanKeyword: + console.log(' boolean') + break + case ts.SyntaxKind.NumberKeyword: + console.log(' number') + break + case ts.SyntaxKind.TypeReference: + const typeRef = member.type as ts.TypeReferenceNode + console.log(' ', (typeRef.typeName as any).escapedText) + break + case ts.SyntaxKind.TypeLiteral: + console.log(' ', 'type literal...') + break + case ts.SyntaxKind.UnionType: + console.log(' ', 'union type...') + break + case ts.SyntaxKind.TupleType: + console.log(' ', 'tuple type...') + break + case ts.SyntaxKind.ArrayType: + const arrayType = member.type as ts.ArrayTypeNode + switch (arrayType.elementType.kind) { + case ts.SyntaxKind.StringKeyword: + console.log(' Array') + break + case ts.SyntaxKind.BooleanKeyword: + console.log(' Array') + break + case ts.SyntaxKind.NumberKeyword: + console.log(' Array') + break + case ts.SyntaxKind.TypeReference: + const typeRef = arrayType.elementType as ts.TypeReferenceNode + console.log(` Array<${(typeRef.typeName as any).escapedText}>`) + default: + console.log('WARN unhandled array type:', arrayType.elementType.kind) + } + break + default: + console.log('unhandled type:', member.type.kind) + } + } + } + ts.forEachChild(node, delintNode) + } +} + +export async function intergen(argv: string[]) { + const filename = path.join(__dirname, 'intergen.sample.ts') + const sourceFile = ts.createSourceFile( + filename, + readFileSync(filename).toString(), + ts.ScriptTarget.ES2015, + false, + ) + + delint(sourceFile) +}