/
TypeScriptClassPropertyToModelHarvester.ts
125 lines (119 loc) · 5.17 KB
/
TypeScriptClassPropertyToModelHarvester.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/* eslint-disable max-depth */
import ts, { ClassDeclaration, Expression, PropertyDeclaration, SyntaxKind } from 'typescript'
import { Artifact } from '../../system/Artifact'
import { ComponentOrigin } from '../../system/ComponentOrigin'
import { Permanence } from '../../system/Permanence'
import { ProgrammingLanguage } from '../../system/ProgrammingLanguage'
import { System } from '../../system/System'
import { SystemComponent } from '../../system/SystemComponent'
import { SystemComponentArtifact } from '../../system/SystemComponentArtifact'
import { ValueType } from '../../system/ValueType'
import { Class, InformationModel, Property } from '../information-architecture'
import { AbstractTypeScriptAstHarvester } from './AbstractTypeScriptAstHarvester'
export class TypeScriptClassPropertyToModelHarvester extends AbstractTypeScriptAstHarvester {
constructor(configurationValues?: { [key: string]: any }) {
super('A harvester that infers the data model from typescript classes', {}, configurationValues)
}
// eslint-disable-next-line max-lines-per-function
async harvestFromAst(system: System, ast: ts.SourceFile): Promise<Artifact[]> {
let representedClass: Class
const _thisThis = this
const results = [] as Artifact[]
const model = InformationModel.fromSystem(system)
try {
// eslint-disable-next-line max-lines-per-function, @typescript-eslint/naming-convention
ts.forEachChild(ast, function visit(node: ts.Node) {
switch (node.kind) {
case SyntaxKind.ClassDeclaration:
const classDeclaration = node as ClassDeclaration
const name = classDeclaration.name?.getText(ast) as string
if (name != null && _thisThis.validClass(name)) {
const fullName = SystemComponent.fullConstantCase(model.fullConstantCaseName, name)
representedClass = system.descendants[fullName] as Class
ts.forEachChild(node, visit)
}
break
case SyntaxKind.PropertyDeclaration:
const member = node as PropertyDeclaration
const memberIdentifier = member.name
const memberName = memberIdentifier.getText(ast)
if (representedClass != null && representedClass.propertiesMap[memberName] == null) {
const memberJsDoc = (node as any).jsDoc as any[]
let memberDocumentationText = ''
if (memberJsDoc != null) {
memberJsDoc.forEach((jsDocItem) => {
memberDocumentationText += jsDocItem.comment as string
})
}
let typeNode = member.type as ts.TypeNode
let typeName
if (typeNode == null && member.initializer) {
const definingInitializer = member.initializer as Expression
if ((definingInitializer as any).type) typeNode = (definingInitializer as any).type
else {
const initializerText = definingInitializer.getText(ast)
if (initializerText) {
if (!isNaN(parseInt(initializerText))) typeName = 'number'
else if (!isNaN(parseFloat(initializerText))) typeName = 'number'
else {
try {
const asJson = JSON.parse(initializerText)
if (typeof asJson == 'boolean') typeName = 'boolean'
else typeName = 'string'
} catch (problem) {
typeName = 'string'
}
}
}
}
}
if (!typeName) {
if (typeNode == null) typeName = 'string'
else typeName = typeNode.getText(ast)
}
if (typeName.indexOf('|') >= 0) {
typeName = typeName.substr(0, typeName.indexOf('|')).trim()
}
let isArray
if (typeName.indexOf('[]') >= 0) {
typeName = typeName.replace('[]', '')
isArray = true
} else isArray = false
const staticModifier = member.modifiers?.find((modifier) => modifier.kind == SyntaxKind.StaticKeyword)
const fullName = SystemComponent.fullConstantCase(model.fullConstantCaseName, typeName)
let propertyType
try {
propertyType = ValueType.fromNameInLanguage(ProgrammingLanguage.typeScript, typeName)
if (!propertyType) propertyType = system.descendants[fullName] as ValueType
if (isArray && propertyType) propertyType = propertyType.asCollection
} catch (problem) {
_thisThis.logger.error(`harvestFromAst(failed) ${problem}`)
propertyType = ValueType.fromNameInLanguage(ProgrammingLanguage.typeScript, 'string')
}
if (propertyType) {
if (member.questionToken) propertyType = propertyType.asOptional
const newProperty = new Property(representedClass.constantCaseFullName, memberName, memberDocumentationText, propertyType, 0)
newProperty.permanence = Permanence.persistent
newProperty.informational = true
newProperty.functional = false
newProperty.optional = member.questionToken ? true : false
newProperty.origin = ComponentOrigin.harvested
newProperty.static = staticModifier ? true : false
representedClass.addChild(newProperty)
results.push(new SystemComponentArtifact(newProperty))
}
}
break
default:
// ignore
break
}
//console.log(node.kind);
})
return results
} catch (problem) {
this.logger.error(`harvestFromAst(failed) ${problem}`)
throw problem
}
}
}