idlize

Форк
0
/
TestGeneratorVisitor.ts 
256 строк · 10.5 Кб
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
export class TestGeneratorVisitor implements GenericVisitor<string[]> {
21
    private interfacesToTest = new Set<string>()
22
    private methodsToTest = new Set<string>()
23
    private propertiesToTest = new Set<string>()
24

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))
32
    }
33

34
    private output: string[] = []
35

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

41
    visit(node: ts.Node) {
42
        if (ts.isClassDeclaration(node)) {
43
            this.testClass(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));
49
        }
50
    }
51

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)
62
            }
63
        })
64
        this.epilogue(node.name!)
65
    }
66

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)
78
            }
79
        })
80
        this.epilogue(node.name!)
81
    }
82

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

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

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})`)
93
        })
94
    }
95

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")
100
            return []
101
        }
102
        return pick(method.parameters.map(it => it), key => this.generateArg(key))
103
    }
104

105
    generateArg(param: ts.ParameterDeclaration): string[] {
106
        return this.generateValueOfType(param.type!)
107
    }
108

109
    generateValueOfType(type: ts.TypeNode): string[] {
110
        if (type.kind == ts.SyntaxKind.UndefinedKeyword) {
111
            return ["undefined"]
112
        }
113
        if (type.kind == ts.SyntaxKind.NullKeyword) {
114
            return ["null"]
115
        }
116
        if (type.kind == ts.SyntaxKind.NumberKeyword) {
117
            return [`0`, `-1`, `${randInt(2048, -1024)}`]
118
        }
119
        if (type.kind == ts.SyntaxKind.StringKeyword) {
120
            return ['""',`"${randString(randInt(16))}"`]
121
        }
122
        if (type.kind == ts.SyntaxKind.BooleanKeyword) {
123
            return ["false", "true"]
124
        }
125
        if (ts.isTypeReferenceNode(type)) {
126
            let name = type.typeName
127
            if (!ts.isIdentifier(name)) {
128
                console.log(`${asString(name)} is not identifier`)
129
                return []
130
            }
131
            let decls = getDeclarationsByNode(this.typeChecker, name)
132
            if (decls) {
133
                let decl = decls[0]
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}`)
139
                }
140
                if (decl && ts.isTypeAliasDeclaration(decl)) {
141
                    return this.generateValueOfType(decl.type)
142
                }
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}}`)
148
                }
149
                if (decl && ts.isClassDeclaration(decl)) {
150
                    // TODO: logic to find proper way to instantiate class
151
                    return [`undefined`]
152
                }
153
                console.log(`Cannot create value of type ${asString(type)}`)
154
                return []
155
            }
156
        }
157
        if (ts.isOptionalTypeNode(type)) {
158
            return [`undefined`, ...this.generateValueOfType(type.type)]
159
        }
160
        if (ts.isUnionTypeNode(type)) {
161
            return type.types.flatMap(it => this.generateValueOfType(it))
162
        }
163
        if (ts.isArrayTypeNode(type)) {
164
            return pickArray(this.generateValueOfType(type.elementType), 7)
165
        }
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)}`)
170
            return []
171
        }
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}]`)
177
        }
178
        if (ts.isFunctionTypeNode(type)) {
179
            // TODO: be smarter here
180
            return ["() => {}"]
181
        }
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}}`)
187
        }
188
        console.log(`Cannot create value of type ${asString(type)}`)
189
        return []
190
    }
191

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)!}`)
195
    }
196

197
    getClassName(name: ts.Identifier) : string {
198
        const clazzName = nameOrUndefined(name)!
199
        return clazzName.endsWith("Attribute") ? clazzName.replace("Attribute", "") : clazzName
200
    }
201

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"`)
205
        this.output.push(``)
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()`)
209
    }
210

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`)
217
    }
218

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)
226
            fakeOverrides
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[]
233
                ?? []
234
            worklist.push(...bases)
235
        }
236
        return result
237
    }
238

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))
242
        )
243
    }
244

245
    baseDeclarations(heritage: ts.HeritageClause): ts.Declaration[] {
246
        return this.heritageIdentifiers(heritage)
247
            .map(it => getDeclarationsByNode(this.typeChecker, it)[0])
248
            .filter(it => !!it)
249
    }
250

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[]
255
    }
256
}
257

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

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

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

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