idlize

Форк
0
/
TestGeneratorVisitor.ts 
304 строки · 12.8 Кб
1
/*
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
6
 *
7
 * http://www.apache.org/licenses/LICENSE-2.0
8
 *
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.
14
 */
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";
19

20
const LAMBDA = "LAMBDA"
21

22
export class TestGeneratorVisitor implements GenericVisitor<string[]> {
23
    private interfacesToTest = new Set<string>()
24
    private methodsToTest = new Set<string>()
25
    private propertiesToTest = new Set<string>()
26

27
    constructor(private sourceFile: ts.SourceFile, private typeChecker: ts.TypeChecker,
28
        interfacesToTest: string| undefined,
29
        methodsToTest: string| undefined,
30
        propertiesToTest: string| undefined) {
31
            interfacesToTest?.split(",")?.map(it => this.interfacesToTest.add(`${it}Attribute`))
32
            methodsToTest?.split(",")?.map(it => this.methodsToTest.add(it))
33
            propertiesToTest?.split(",")?.map(it => this.propertiesToTest.add(it))
34
    }
35

36
    private output: string[] = []
37

38
    visitWholeFile(): string[] {
39
        ts.forEachChild(this.sourceFile, (node) => this.visit(node))
40
        return this.output
41
    }
42

43
    visit(node: ts.Node) {
44
        if (ts.isClassDeclaration(node)) {
45
            this.testClass(node)
46
        } else if (ts.isInterfaceDeclaration(node)) {
47
            this.testInterface(node)
48
        } else if (ts.isModuleDeclaration(node)) {
49
            // This is a namespace, visit its children
50
            ts.forEachChild(node, (node) => this.visit(node));
51
        }
52
    }
53

54
    testClass(node: ts.ClassDeclaration) {
55
        if (this.interfacesToTest.size > 0 && !this.interfacesToTest.has(nameOrUndefined(node.name)!)) return
56
        this.prologue(node.name!)
57
        node.members.forEach(child => {
58
            if (ts.isConstructorDeclaration(child)) {
59
                this.testConstructor(child)
60
            } else if (ts.isMethodDeclaration(child)) {
61
                this.testMethod(child)
62
            } else if (ts.isPropertyDeclaration(child)) {
63
                this.testProperty(child)
64
            }
65
        })
66
        this.epilogue(node.name!)
67
    }
68

69
    testInterface(node: ts.InterfaceDeclaration) {
70
        if (this.interfacesToTest.size > 0 && !this.interfacesToTest.has(nameOrUndefined(node.name)!)) return
71
        this.prologue(node.name!)
72
        let members = this.membersWithFakeOverrides(node)
73
        members.forEach(child => {
74
            if (ts.isConstructSignatureDeclaration(child)) {
75
                this.testConstructor(child)
76
            } else if (ts.isMethodSignature(child)) {
77
                this.testMethod(child)
78
            } else if (ts.isPropertySignature(child)) {
79
                this.testProperty(child)
80
            }
81
        })
82
        this.epilogue(node.name!)
83
    }
84

85
    testConstructor(ctor: ts.ConstructorDeclaration | ts.ConstructSignatureDeclaration) {
86
        if (this.methodsToTest.size > 0 && !this.methodsToTest.has("constructor")) return
87
    }
88

89
    testMethod(method: ts.MethodDeclaration | ts.MethodSignature) {
90
        if (this.methodsToTest.size > 0 && !this.methodsToTest.has(nameOrUndefined(method.name)!)) return
91

92
        this.generateArgs(method).forEach(args => {
93
            let methodName = nameOrUndefined(method.name)
94

95
            // Handle Lambda
96
            let passedArgs = args.replaceAll(LAMBDA, `() => {}`)
97
            let expectedArgs = args.replaceAll(LAMBDA, `"Function 42"`)
98

99
            let golden = `${methodName}(${expectedArgs})`
100
            this.output.push(`  checkResult("${methodName}", () => peer.${methodName}Attribute(${passedArgs}), \`${golden}\`)`)
101
        })
102
    }
103

104
    generateArgs(method: ts.MethodDeclaration | ts.MethodSignature): string[] {
105
        let args = method.parameters.map(it => this.generateArg(it))
106
        if (args.find(it => it === undefined)) {
107
            console.log("Cannot map some argument")
108
            return []
109
        }
110
        return pick(method.parameters.map(it => it), key => this.generateArg(key))
111
    }
112

113
    generateArg(param: ts.ParameterDeclaration): string[] {
114
        return this.generateValueOfType(param.type!)
115
    }
116

117
    generateValueOfType(type: ts.TypeNode): string[] {
118
        if (type.kind == ts.SyntaxKind.UndefinedKeyword) {
119
            return ["undefined"]
120
        }
121
        if (type.kind == ts.SyntaxKind.NullKeyword) {
122
            return ["null"]
123
        }
124
        if (type.kind == ts.SyntaxKind.NumberKeyword) {
125
            return [`0`, `-1`, `${randInt(2048, -1024)}`, `-0.59`, `93.54`]
126
        }
127
        if (type.kind == ts.SyntaxKind.StringKeyword) {
128
            return ['""',`"${randString(randInt(16))}"`]
129
        }
130
        if (type.kind == ts.SyntaxKind.BooleanKeyword) {
131
            return ["false", "true"]
132
        }
133
        if (ts.isTypeReferenceNode(type)) {
134
            let name = type.typeName
135
            if (!ts.isIdentifier(name)) {
136
                console.log(`${asString(name)} is not identifier`)
137
                return []
138
            }
139
            let decls = getDeclarationsByNode(this.typeChecker, name)
140
            if (decls) {
141
                let decl = decls[0]
142
                if (decl && ts.isEnumDeclaration(decl)) {
143
                    // TBD: Use enum constants
144
                    // let name = decl.name
145
                    // ${nameOrUndefined(name)}.${nameOrUndefined(it.name)!}
146
                    return decl.members.map((it, index) => `${index}`)
147
                }
148
                if (decl && ts.isTypeAliasDeclaration(decl)) {
149
                    return this.generateValueOfType(decl.type)
150
                }
151
                if (decl && ts.isInterfaceDeclaration(decl)) {
152

153
                    let interfaceName = asString(name)
154
                    // Array from built-in
155
                    if (interfaceName === "Array") {
156
                        return type.typeArguments ? pickArray(this.generateValueOfType(type.typeArguments[0])) : []
157
                    }
158
                    // Optional from stdlib.d.ts
159
                    if (interfaceName === "Optional") {
160
                        if (type.typeArguments) {
161
                            let argType = type.typeArguments[0]
162
                            if (ts.isTypeNode(argType)) {
163
                                return [`undefined`, ...this.generateValueOfType(argType)]
164
                            }
165
                        }
166
                        return [`undefined`]
167
                    }
168

169
                    return pick(decl.members.filter(ts.isPropertySignature), (key) =>
170
                        this.generateValueOfType(key.type!)
171
                            .map(it => `${nameOrUndefined(key.name)}: ${it}`))
172
                        .map(it => `{${it}}`)
173
                }
174
                if (decl && ts.isClassDeclaration(decl)) {
175

176
                    let className = nameOrUndefined(decl.name)
177
                    console.log(`class: ${nameOrUndefined(decl.name)}`)
178
                    decl.members.forEach(it => console.log(`class member: ${nameOrUndefined(it.name)}`))
179

180
                    let consturctors = decl.members.filter(ts.isConstructorDeclaration)
181
                    if (consturctors.length > 0) {
182
                        let constructor = consturctors[randInt(consturctors.length)]
183
                        constructor.parameters.forEach(it => {console.log(`constructor param: ${nameOrUndefined(it.name)}`)})
184

185
                        // TBD: add imports for classes with constructors
186
                        /*
187
                        return pick(constructor.parameters.map (it => it), (key) =>
188
                            this.generateValueOfType(key.type!)
189
                                .map(it => `${it}`)) // TBD: Use generated class
190
                                // .map(it => `${nameOrUndefined(key.name)}: ${it}`))
191
                            .map(it => `new ${className}(${it})`) // TBD: Use generated class
192
                            // .map(it => `{${it}}`)
193
                        */
194
                       return []
195
                    }
196

197

198
                    return pick(decl.members.filter(ts.isPropertyDeclaration), (key) =>
199
                        this.generateValueOfType(key.type!)
200
                            .map(it => `${nameOrUndefined(key.name)}: ${it}`))
201
                        .map(it => `{${it}}`)
202
                }
203
                console.log(`Cannot create value of type ${asString(type)}`)
204
                return []
205
            }
206
        }
207
        if (ts.isOptionalTypeNode(type)) {
208
            return [`undefined`, ...this.generateValueOfType(type.type)]
209
        }
210
        if (ts.isUnionTypeNode(type)) {
211
            return type.types.flatMap(it => this.generateValueOfType(it))
212
        }
213
        if (ts.isArrayTypeNode(type)) {
214
            return pickArray(this.generateValueOfType(type.elementType))
215
        }
216
        if (ts.isLiteralTypeNode(type)) {
217
            let literal = type.literal
218
            if (ts.isStringLiteral(literal)) return [`${literal.getText(this.sourceFile)}`]
219
            console.log(`Cannot create value of literal type ${asString(literal)}`)
220
            return []
221
        }
222
        if (ts.isTupleTypeNode(type)) {
223
            // return [`[${type.elements.map(it => this.generateValueOfType(it)).join(",")}]`]
224
            return pick(type.elements.map(it => it), (key) =>
225
                this.generateValueOfType(key))
226
                .map(it => `[${it}]`)
227
        }
228
        if (ts.isFunctionTypeNode(type)) {
229
            // TODO: be smarter here
230
            return [`${LAMBDA}`]
231
        }
232
        if (ts.isTypeLiteralNode(type)) {
233
            // TODO: be smarter here
234
            return pick(type.members.filter(ts.isPropertySignature), (key) =>
235
                this.generateValueOfType(key.type!).map(it => `${nameOrUndefined(key.name)}: ${it}`))
236
                .map(it => `{${it}}`)
237
        }
238
        console.log(`Cannot create value of type ${asString(type)}`)
239
        return []
240
    }
241

242
    testProperty(property: ts.PropertyDeclaration | ts.PropertySignature) {
243
        if (this.methodsToTest.size > 0 && !this.methodsToTest.has(nameOrUndefined(property.name)!)) return
244
        console.log(`test prop ${nameOrUndefined(property.name)!}`)
245
    }
246

247
    getClassName(name: ts.Identifier) : string {
248
        const clazzName = nameOrUndefined(name)!
249
        return clazzName.endsWith("Attribute") ? clazzName.replace("Attribute", "") : clazzName
250
    }
251

252
    prologue(name: ts.Identifier) {
253
        let clazzName = this.getClassName(name)!
254
        this.output.push(`import { Ark${clazzName}Peer } from "@arkoala/arkui/Ark${clazzName}Peer"`)
255
        this.output.push(``)
256
        this.output.push(`function check${clazzName}() {`)
257
        this.output.push(`  let peer = new Ark${clazzName}Peer(ArkUINodeType.${clazzName})`)
258
    }
259

260
    epilogue(name: ts.Identifier) {
261
        let clazzName = this.getClassName(name)!
262
        this.output.push(`}`)
263
        this.output.push(`check${clazzName}()`)
264
        this.output.push(`\n`)
265
    }
266

267
    membersWithFakeOverrides(node: ts.InterfaceDeclaration): ts.TypeElement[] {
268
        const result: ts.TypeElement[] = []
269
        const worklist: ts.InterfaceDeclaration[] = [node]
270
        const overridden = new Set<string>()
271
        while (worklist.length != 0) {
272
            const next = worklist.shift()!
273
            const fakeOverrides = this.filterNotOverridden(overridden, next)
274
            fakeOverrides
275
                .map(it => nameOrUndefined(it.name))
276
                .forEach(it => it ? overridden.add(it) : undefined)
277
            result.push(...fakeOverrides)
278
            const bases = next.heritageClauses
279
                ?.flatMap(it => this.baseDeclarations(it))
280
                ?.filter(it => ts.isInterfaceDeclaration(it)) as ts.InterfaceDeclaration[]
281
                ?? []
282
            worklist.push(...bases)
283
        }
284
        return result
285
    }
286

287
    filterNotOverridden(overridden: Set<string>, node: ts.InterfaceDeclaration): ts.TypeElement[] {
288
        return node.members.filter(it =>
289
            it.name && ts.isIdentifier(it.name) && !overridden.has(ts.idText(it.name))
290
        )
291
    }
292

293
    baseDeclarations(heritage: ts.HeritageClause): ts.Declaration[] {
294
        return this.heritageIdentifiers(heritage)
295
            .map(it => getDeclarationsByNode(this.typeChecker, it)[0])
296
            .filter(it => !!it)
297
    }
298

299
    heritageIdentifiers(heritage: ts.HeritageClause): ts.Identifier[] {
300
        return heritage.types.map(it => {
301
            return ts.isIdentifier(it.expression) ? it.expression : undefined
302
        }).filter(it => !!it) as ts.Identifier[]
303
    }
304
}
305

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.