diff --git a/packages/scripts/src/commands/intergen.sample.ts b/packages/scripts/src/commands/intergen.sample.ts index c3a145d..1846cd1 100644 --- a/packages/scripts/src/commands/intergen.sample.ts +++ b/packages/scripts/src/commands/intergen.sample.ts @@ -6,9 +6,13 @@ export class Primitives { strArray!: string[] numArray!: number[] boolArray!: boolean[] + + nestedArray!: string[][] } export class Name { + private hidden1?: string + protected hidden2?: string firstName!: string lastName!: string } diff --git a/packages/scripts/src/commands/intergen.ts b/packages/scripts/src/commands/intergen.ts index 909f55f..c5a35f1 100644 --- a/packages/scripts/src/commands/intergen.ts +++ b/packages/scripts/src/commands/intergen.ts @@ -2,6 +2,183 @@ import * as path from 'path' import * as ts from 'typescript' import {readFileSync} from 'fs' +// function processLiteral(type: ts.TypeLiteralNode): string { +// switch (type +// } + +function processLiteral( + literal: ts.BooleanLiteral | ts.LiteralExpression | ts.PrefixUnaryExpression, +) { + switch (literal.kind) { + case ts.SyntaxKind.TrueKeyword: + return '\'true\'' + case ts.SyntaxKind.FalseKeyword: + return '\'false\'' + default: + if (ts.isLiteralExpression(literal)) { + return `'${literal.text}'` + } + throw new Error('Unsupported literal type: ' + literal.kind) + } +} + +function processTypes(type: ts.TypeNode): string { + switch (type.kind) { + case ts.SyntaxKind.StringKeyword: + return 'string' + case ts.SyntaxKind.BooleanKeyword: + return 'boolean' + case ts.SyntaxKind.NumberKeyword: + return 'number' + case ts.SyntaxKind.NullKeyword: + return 'null' + case ts.SyntaxKind.TrueKeyword: + return '\'true\'' + case ts.SyntaxKind.FalseKeyword: + return '\'false\'' + case ts.SyntaxKind.LiteralType: + const literalType = type as ts.LiteralTypeNode + return processLiteral(literalType.literal) + // return literalType.literal.text + case ts.SyntaxKind.TypeReference: + const typeRef = type as ts.TypeReferenceNode + // FIXME do not use any + return (typeRef.typeName as any).escapedText + case ts.SyntaxKind.TypeLiteral: + const typeLiteral = type as ts.TypeLiteralNode + return '{' + processInterfaceMembers(typeLiteral.members).join('\n') + '}' + // typeLiteral.members.map(processTypes) + // console.log('aaa', JSON.stringify(typeLiteral, null, ' ')) + // return 'type literal...' + // console.log(' ', 'type literal...') + // TODO recursively iterate through typeLiteral.members + break + case ts.SyntaxKind.UnionType: + const unionType = type as ts.UnionTypeNode + const unionTypes = unionType.types.map(processTypes).join(' | ') + return unionTypes + case ts.SyntaxKind.TupleType: + const tupleTypeNode = type as ts.TupleTypeNode + const tupleTypes = tupleTypeNode.elementTypes.map(processTypes).join(', ') + return `[${tupleTypes}]` + // TODO recursively iterate through tupleTypeNode.elementTypes + break + case ts.SyntaxKind.ArrayType: + const arrayType = type as ts.ArrayTypeNode + return `Array<${processTypes(arrayType.elementType)}>` + default: + throw new Error('unhandled type: ' + type.kind) + } +} + +function processInterfaceMembers( + members: ts.NodeArray, +): string[] { + const results: string[] = [] + + for (const m of members) { + if (m.kind !== ts.SyntaxKind.PropertySignature) { + throw new Error('not implemented support for node.kind: ' + m.kind) + } + const member = m as ts.PropertySignature + const name = (m.name as ts.Identifier).escapedText + + let result = '' + let readonly = false + if (m.modifiers) { + for (const modifier of m.modifiers) { + if (modifier.kind === ts.SyntaxKind.ReadonlyKeyword) { + readonly = true + continue + } + + console.log(' modifier.kind:', modifier.kind) + } + } + + if (readonly) { + result += 'readonly ' + name + } else { + result += name + } + + if (!member.type) { + throw new Error('No member type!') + } + + if (member.questionToken) { + result += '?: ' + } else { + result += ': ' + } + + result += processTypes(member.type) + results.push(result) + } + + return results +} + +function processClassMembers( + members: ts.NodeArray, +): string[] { + const results: string[] = [] + + for (const m of members) { + if (m.kind !== ts.SyntaxKind.PropertyDeclaration) { + throw new Error('not implemented support for node.kind: ' + m.kind) + } + const member = m as ts.PropertyDeclaration + const name = (m.name as ts.Identifier).escapedText + + let result = '' + let readonly = false + let skip = false + if (m.modifiers) { + for (const modifier of m.modifiers) { + if (modifier.kind === ts.SyntaxKind.PrivateKeyword) { + skip = true + continue + } + if (modifier.kind === ts.SyntaxKind.ProtectedKeyword) { + skip = true + continue + } + if (modifier.kind === ts.SyntaxKind.ReadonlyKeyword) { + readonly = true + continue + } + + console.log(' modifier.kind:', modifier.kind) + } + } + + if (skip) { + continue + } + if (readonly) { + result += 'readonly ' + name + } else { + result += name + } + + if (!member.type) { + throw new Error('No member type!') + } + + if (member.questionToken) { + result += '?: ' + } else { + result += ': ' + } + + result += processTypes(member.type) + results.push(result) + } + + return results +} + function delint(sourceFile: ts.SourceFile) { delintNode(sourceFile) @@ -11,84 +188,26 @@ function delint(sourceFile: ts.SourceFile) { // TODO check which type references are in use in addition to the // primitives like string, string[], number, number[], boolean, // boolean[] + // TODO use typeParameters, for example type A = Array switch (node.kind) { + case ts.SyntaxKind.InterfaceDeclaration: + const interfaceDeclaration = node as ts.InterfaceDeclaration + console.log( + interfaceDeclaration.name.escapedText, + processInterfaceMembers(interfaceDeclaration.members)) + break + case ts.SyntaxKind.TypeAliasDeclaration: + const typeAlias = node as ts.TypeAliasDeclaration + console.log('typeAlias') + break case ts.SyntaxKind.ClassDeclaration: const cls = node as ts.ClassDeclaration if (!cls.name) { // TODO warn - console.log('no class name', cls.pos) + throw new Error('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) - } - } + console.log(cls.name.escapedText, processClassMembers(cls.members)) } ts.forEachChild(node, delintNode) }