Add ability to process literals, type defs and interfaces

This commit is contained in:
Jerko Steiner 2019-08-11 14:36:27 +07:00
parent 4f90e865fa
commit fb4bb7b61b
2 changed files with 194 additions and 71 deletions

View File

@ -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
}

View File

@ -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<ts.TypeElement>,
): 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<ts.ClassElement>,
): 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<B> = Array<B>
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<string>')
break
case ts.SyntaxKind.BooleanKeyword:
console.log(' Array<boolean>')
break
case ts.SyntaxKind.NumberKeyword:
console.log(' Array<number>')
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)
}