2
* Copyright (c) 2024 Huawei Device Co., Ltd.
3
* Licensed under the Apache License, Version 2.0 (the "License");
4
* you may not use this file except in compliance with the License.
5
* You may obtain a copy of the License at
7
* http://www.apache.org/licenses/LICENSE-2.0
9
* Unless required by applicable law or agreed to in writing, software
10
* distributed under the License is distributed on an "AS IS" BASIS,
11
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
* See the License for the specific language governing permissions and
13
* limitations under the License.
15
import * as ts from "typescript"
16
import { asString, nameOrNullForIdl as nameOrUndefined, getDeclarationsByNode } from "./util"
17
import { GenericVisitor } from "./options"
18
import {randInt, randString, pick, pickArray} from "./rand_utils";
20
export class TestGeneratorVisitor implements GenericVisitor<string[]> {
21
private interfacesToTest = new Set<string>()
22
private methodsToTest = new Set<string>()
23
private propertiesToTest = new Set<string>()
25
constructor(private sourceFile: ts.SourceFile, private typeChecker: ts.TypeChecker,
26
interfacesToTest: string| undefined,
27
methodsToTest: string| undefined,
28
propertiesToTest: string| undefined) {
29
interfacesToTest?.split(",")?.map(it => this.interfacesToTest.add(`${it}Attribute`))
30
methodsToTest?.split(",")?.map(it => this.methodsToTest.add(it))
31
propertiesToTest?.split(",")?.map(it => this.propertiesToTest.add(it))
34
private output: string[] = []
36
visitWholeFile(): string[] {
37
ts.forEachChild(this.sourceFile, (node) => this.visit(node))
41
visit(node: ts.Node) {
42
if (ts.isClassDeclaration(node)) {
44
} else if (ts.isInterfaceDeclaration(node)) {
45
this.testInterface(node)
46
} else if (ts.isModuleDeclaration(node)) {
47
// This is a namespace, visit its children
48
ts.forEachChild(node, (node) => this.visit(node));
52
testClass(node: ts.ClassDeclaration) {
53
if (this.interfacesToTest.size > 0 && !this.interfacesToTest.has(nameOrUndefined(node.name)!)) return
54
this.prologue(node.name!)
55
node.members.forEach(child => {
56
if (ts.isConstructorDeclaration(child)) {
57
this.testConstructor(child)
58
} else if (ts.isMethodDeclaration(child)) {
59
this.testMethod(child)
60
} else if (ts.isPropertyDeclaration(child)) {
61
this.testProperty(child)
64
this.epilogue(node.name!)
67
testInterface(node: ts.InterfaceDeclaration) {
68
if (this.interfacesToTest.size > 0 && !this.interfacesToTest.has(nameOrUndefined(node.name)!)) return
69
this.prologue(node.name!)
70
let members = this.membersWithFakeOverrides(node)
71
members.forEach(child => {
72
if (ts.isConstructSignatureDeclaration(child)) {
73
this.testConstructor(child)
74
} else if (ts.isMethodSignature(child)) {
75
this.testMethod(child)
76
} else if (ts.isPropertySignature(child)) {
77
this.testProperty(child)
80
this.epilogue(node.name!)
83
testConstructor(ctor: ts.ConstructorDeclaration | ts.ConstructSignatureDeclaration) {
84
if (this.methodsToTest.size > 0 && !this.methodsToTest.has("constructor")) return
87
testMethod(method: ts.MethodDeclaration | ts.MethodSignature) {
88
if (this.methodsToTest.size > 0 && !this.methodsToTest.has(nameOrUndefined(method.name)!)) return
90
this.generateArgs(method).forEach(args => {
91
this.output.push(` console.log(\`${nameOrUndefined(method.name)}(${args})\`)`)
92
this.output.push(` peer.${nameOrUndefined(method.name)}(${args})`)
96
generateArgs(method: ts.MethodDeclaration | ts.MethodSignature): string[] {
97
let args = method.parameters.map(it => this.generateArg(it))
98
if (args.find(it => it === undefined)) {
99
console.log("Cannot map some argument")
102
return pick(method.parameters.map(it => it), key => this.generateArg(key))
105
generateArg(param: ts.ParameterDeclaration): string[] {
106
return this.generateValueOfType(param.type!)
109
generateValueOfType(type: ts.TypeNode): string[] {
110
if (type.kind == ts.SyntaxKind.UndefinedKeyword) {
113
if (type.kind == ts.SyntaxKind.NullKeyword) {
116
if (type.kind == ts.SyntaxKind.NumberKeyword) {
117
return [`0`, `-1`, `${randInt(2048, -1024)}`]
119
if (type.kind == ts.SyntaxKind.StringKeyword) {
120
return ['""',`"${randString(randInt(16))}"`]
122
if (type.kind == ts.SyntaxKind.BooleanKeyword) {
123
return ["false", "true"]
125
if (ts.isTypeReferenceNode(type)) {
126
let name = type.typeName
127
if (!ts.isIdentifier(name)) {
128
console.log(`${asString(name)} is not identifier`)
131
let decls = getDeclarationsByNode(this.typeChecker, name)
134
if (decl && ts.isEnumDeclaration(decl)) {
135
// TBD: Use enum constants
136
// let name = decl.name
137
// ${nameOrUndefined(name)}.${nameOrUndefined(it.name)!}
138
return decl.members.map((it, index) => `${index}`)
140
if (decl && ts.isTypeAliasDeclaration(decl)) {
141
return this.generateValueOfType(decl.type)
143
if (decl && ts.isInterfaceDeclaration(decl)) {
144
return pick(decl.members.filter(ts.isPropertySignature), (key) =>
145
this.generateValueOfType(key.type!)
146
.map(it => `${nameOrUndefined(key.name)}: ${it}`))
147
.map(it => `{${it}}`)
149
if (decl && ts.isClassDeclaration(decl)) {
150
// TODO: logic to find proper way to instantiate class
153
console.log(`Cannot create value of type ${asString(type)}`)
157
if (ts.isOptionalTypeNode(type)) {
158
return [`undefined`, ...this.generateValueOfType(type.type)]
160
if (ts.isUnionTypeNode(type)) {
161
return type.types.flatMap(it => this.generateValueOfType(it))
163
if (ts.isArrayTypeNode(type)) {
164
return pickArray(this.generateValueOfType(type.elementType), 7)
166
if (ts.isLiteralTypeNode(type)) {
167
let literal = type.literal
168
if (ts.isStringLiteral(literal)) return [`${literal.getText(this.sourceFile)}`]
169
console.log(`Cannot create value of literal type ${asString(literal)}`)
172
if (ts.isTupleTypeNode(type)) {
173
// return [`[${type.elements.map(it => this.generateValueOfType(it)).join(",")}]`]
174
return pick(type.elements.map(it => it), (key) =>
175
this.generateValueOfType(key))
176
.map(it => `[${it}]`)
178
if (ts.isFunctionTypeNode(type)) {
179
// TODO: be smarter here
182
if (ts.isTypeLiteralNode(type)) {
183
// TODO: be smarter here
184
return pick(type.members.filter(ts.isPropertySignature), (key) =>
185
this.generateValueOfType(key.type!).map(it => `${nameOrUndefined(key.name)}: ${it}`))
186
.map(it => `{${it}}`)
188
console.log(`Cannot create value of type ${asString(type)}`)
192
testProperty(property: ts.PropertyDeclaration | ts.PropertySignature) {
193
if (this.methodsToTest.size > 0 && !this.methodsToTest.has(nameOrUndefined(property.name)!)) return
194
console.log(`test prop ${nameOrUndefined(property.name)!}`)
197
getClassName(name: ts.Identifier) : string {
198
const clazzName = nameOrUndefined(name)!
199
return clazzName.endsWith("Attribute") ? clazzName.replace("Attribute", "") : clazzName
202
prologue(name: ts.Identifier) {
203
let clazzName = this.getClassName(name)!
204
this.output.push(`import { Ark${clazzName}Peer } from "@arkoala/arkui/Ark${clazzName}Peer"`)
206
this.output.push(`function check${clazzName}() {`)
207
this.output.push(` console.log("call ${clazzName} peer")`)
208
this.output.push(` let peer = new Ark${clazzName}Peer()`)
211
epilogue(name: ts.Identifier) {
212
let clazzName = this.getClassName(name)!
213
this.output.push(` console.log("\\n")`)
214
this.output.push(`}`)
215
this.output.push(`check${clazzName}()`)
216
this.output.push(`\n`)
219
membersWithFakeOverrides(node: ts.InterfaceDeclaration): ts.TypeElement[] {
220
const result: ts.TypeElement[] = []
221
const worklist: ts.InterfaceDeclaration[] = [node]
222
const overridden = new Set<string>()
223
while (worklist.length != 0) {
224
const next = worklist.shift()!
225
const fakeOverrides = this.filterNotOverridden(overridden, next)
227
.map(it => nameOrUndefined(it.name))
228
.forEach(it => it ? overridden.add(it) : undefined)
229
result.push(...fakeOverrides)
230
const bases = next.heritageClauses
231
?.flatMap(it => this.baseDeclarations(it))
232
?.filter(it => ts.isInterfaceDeclaration(it)) as ts.InterfaceDeclaration[]
234
worklist.push(...bases)
239
filterNotOverridden(overridden: Set<string>, node: ts.InterfaceDeclaration): ts.TypeElement[] {
240
return node.members.filter(it =>
241
it.name && ts.isIdentifier(it.name) && !overridden.has(ts.idText(it.name))
245
baseDeclarations(heritage: ts.HeritageClause): ts.Declaration[] {
246
return this.heritageIdentifiers(heritage)
247
.map(it => getDeclarationsByNode(this.typeChecker, it)[0])
251
heritageIdentifiers(heritage: ts.HeritageClause): ts.Identifier[] {
252
return heritage.types.map(it => {
253
return ts.isIdentifier(it.expression) ? it.expression : undefined
254
}).filter(it => !!it) as ts.Identifier[]