idlize

Форк
0
/
PeerGeneratorVisitor.ts 
901 строка · 37.9 Кб
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

16
import * as ts from "typescript"
17
import {
18
    asString,
19
    capitalize,
20
    identName,
21
    nameOrNull,
22
    serializerBaseMethods,
23
    className,
24
    isDefined,
25
    isStatic,
26
    throwException,
27
    getComment,
28
    isReadonly,
29
    getDeclarationsByNode,
30
    Language
31
} from "../util"
32
import { GenericVisitor } from "../options"
33
import {
34
    ArgConvertor, RetConvertor,
35
} from "./Convertors"
36
import { PeerGeneratorConfig } from "./PeerGeneratorConfig";
37
import { DeclarationTable, PrimitiveType } from "./DeclarationTable"
38
import {
39
    singleParentDeclaration,
40
} from "./inheritance"
41
import { PeerClass } from "./PeerClass"
42
import { PeerMethod } from "./PeerMethod"
43
import { PeerFile, EnumEntity } from "./PeerFile"
44
import { PeerLibrary } from "./PeerLibrary"
45
import { MaterializedClass, MaterializedField, MaterializedMethod, SuperElement, checkTSDeclarationMaterialized, isMaterialized } from "./Materialized"
46
import { Field, FieldModifier, Method, MethodModifier, NamedMethodSignature, Type } from "./LanguageWriters";
47
import { mapType } from "./TypeNodeNameConvertor";
48
import { convertDeclaration, convertTypeNode } from "./TypeNodeConvertor";
49
import { DeclarationDependenciesCollector, TypeDependenciesCollector } from "./dependencies_collector";
50
import { convertDeclToFeature } from "./ImportsCollector";
51
import { addSyntheticDeclarationDependency, isSyntheticDeclaration, makeSyntheticTypeAliasDeclaration } from "./synthetic_declaration";
52
import { isBuilderClass, isCustomBuilderClass, toBuilderClass } from "./BuilderClass";
53

54
export enum RuntimeType {
55
    UNEXPECTED = -1,
56
    NUMBER = 1,
57
    STRING = 2,
58
    OBJECT = 3,
59
    BOOLEAN = 4,
60
    UNDEFINED = 5,
61
    BIGINT = 6,
62
    FUNCTION = 7,
63
    SYMBOL = 8,
64
    MATERIALIZED = 9,
65
}
66

67
/**
68
 * Theory of operations.
69
 *
70
 * We use type definition as "grammar", and perform recursive descent to terminal nodes of such grammar
71
 * generating serialization code. We use TS typechecker to analyze compound and union types and generate
72
 * universal finite automata to serialize any value of the given type.
73
 */
74

75

76
export interface TypeAndName {
77
    type: ts.TypeNode
78
    name: string
79
    optional: boolean
80
}
81

82
export type PeerGeneratorVisitorOptions = {
83
    sourceFile: ts.SourceFile
84
    typeChecker: ts.TypeChecker
85
    declarationTable: DeclarationTable,
86
    peerLibrary: PeerLibrary
87
}
88

89
export class ComponentDeclaration {
90
    constructor(
91
        public readonly name: string,
92
        public readonly interfaceDeclaration: ts.InterfaceDeclaration | undefined,
93
        public readonly attributesDeclarations: ts.ClassDeclaration,
94
    ) {}
95
}
96

97
function isSubclass(typeChecker: ts.TypeChecker, node: ts.ClassDeclaration, maybeParent: ts.ClassDeclaration): boolean {
98
    const heritageParentType = node.heritageClauses?.[0].types[0].expression
99
    const heritageDeclarations = heritageParentType ? getDeclarationsByNode(typeChecker, heritageParentType) : []
100
    return heritageDeclarations.some(it => {
101
        if (it === maybeParent)
102
            return true
103
        if (ts.isClassDeclaration(it))
104
            return isSubclass(typeChecker, it, maybeParent)
105
        return false
106
    })
107
}
108

109
function isSubclassComponent(typeChecker: ts.TypeChecker, a: ComponentDeclaration, b: ComponentDeclaration) {
110
    return isSubclass(typeChecker, a.attributesDeclarations, b.attributesDeclarations)
111
}
112

113
export class PeerGeneratorVisitor implements GenericVisitor<void> {
114
    private readonly sourceFile: ts.SourceFile
115
    declarationTable: DeclarationTable
116

117
    static readonly serializerBaseMethods = serializerBaseMethods()
118
    readonly typeChecker: ts.TypeChecker
119

120
    readonly peerLibrary: PeerLibrary
121
    readonly peerFile: PeerFile
122

123
    constructor(options: PeerGeneratorVisitorOptions) {
124
        this.sourceFile = options.sourceFile
125
        this.typeChecker = options.typeChecker
126
        this.declarationTable = options.declarationTable
127
        this.peerLibrary = options.peerLibrary
128
        this.peerFile = new PeerFile(this.sourceFile.fileName, this.declarationTable, this.peerLibrary.componentsToGenerate)
129
        this.peerLibrary.files.push(this.peerFile)
130
    }
131

132
    visitWholeFile(): void {
133
        ts.forEachChild(this.sourceFile, (node) => this.visit(node))
134
    }
135

136
    visit(node: ts.Node) {
137
        if (ts.isVariableStatement(node)) {
138
            this.processVariableStatement(node)
139
        } else if (ts.isModuleDeclaration(node)) {
140
            if (node.body && ts.isModuleBlock(node.body)) {
141
                node.body.statements.forEach(it => this.visit(it))
142
            }
143
        } else if (ts.isClassDeclaration(node) ||
144
            ts.isInterfaceDeclaration(node) ||
145
            ts.isEnumDeclaration(node) ||
146
            ts.isVariableStatement(node) ||
147
            ts.isExportDeclaration(node) ||
148
            ts.isTypeAliasDeclaration(node) ||
149
            ts.isFunctionDeclaration(node) ||
150
            ts.isEmptyStatement(node) ||
151
            ts.isImportDeclaration(node) ||
152
            node.kind == ts.SyntaxKind.EndOfFileToken) {
153
            // Do nothing.
154
        } else {
155
            throw new Error(`Unknown node: ${node.kind} ${node.getText()}`)
156
        }
157
    }
158

159
    private processVariableStatement(node: ts.VariableStatement) {
160
        node.declarationList.declarations.forEach(variable => {
161
            const interfaceDecl = this.maybeTypeReferenceToDeclaration(variable.type)
162
            if (!interfaceDecl || !ts.isInterfaceDeclaration(interfaceDecl))
163
                return
164
            const attributesDecl = this.interfaceToComponentAttributes(interfaceDecl)
165
            if (attributesDecl) {
166
                if (this.peerLibrary.isComponentDeclaration(interfaceDecl) ||
167
                    this.peerLibrary.isComponentDeclaration(attributesDecl))
168
                    throw new Error("Component is already defined")
169
                const componentName = identName(variable.name)!
170
                if (PeerGeneratorConfig.ignoreComponents.includes(componentName))
171
                    return
172
                this.peerLibrary.componentsDeclarations.push(new ComponentDeclaration(
173
                    componentName,
174
                    interfaceDecl,
175
                    attributesDecl,
176
                ))
177
            }
178
        })
179
    }
180

181
    private maybeTypeReferenceToDeclaration(node: ts.TypeNode | undefined): ts.Declaration | undefined {
182
        if (!node || !ts.isTypeReferenceNode(node))
183
            return undefined
184
        return getDeclarationsByNode(this.typeChecker, node.typeName)?.[0]
185
    }
186

187
    private interfaceToComponentAttributes(node: ts.InterfaceDeclaration | undefined): ts.ClassDeclaration | undefined {
188
        if (!node)
189
            return undefined
190
        const members = node.members.filter(it => !ts.isConstructSignatureDeclaration(it))
191
        if (!members.length || !members.every(it => ts.isCallSignatureDeclaration(it)))
192
            return undefined
193
        const callable = members[0] as ts.CallSignatureDeclaration
194
        const retDecl = this.maybeTypeReferenceToDeclaration(callable.type)
195
        const isSameReturnType = (node: ts.TypeElement): boolean => {
196
            if (!ts.isCallSignatureDeclaration(node))
197
                throw "Expected to be a call signature"
198
            const otherRetDecl = this.maybeTypeReferenceToDeclaration(node.type)
199
            return otherRetDecl === retDecl
200
        }
201

202
        if (!retDecl || !ts.isClassDeclaration(retDecl) || !members.every(isSameReturnType))
203
            return undefined
204

205
        return retDecl
206
    }
207

208
    private processCustomComponent(node: ts.ClassDeclaration) {
209
        const methods = node.members
210
            .filter(it => ts.isMethodDeclaration(it) || ts.isMethodSignature(it))
211
            .map(it => it.getText().replace(/;\s*$/g, ''))
212
            .map(it => `${it} { throw new Error("not implemented"); }`)
213
        this.peerLibrary.customComponentMethods.push(...methods)
214
    }
215
}
216

217
function tempExtractParameters(method: ts.ConstructorDeclaration | ts.MethodDeclaration | ts.MethodSignature | ts.CallSignatureDeclaration): ts.ParameterDeclaration[] {
218
    if (!ts.isCallSignatureDeclaration(method) && identName(method.name) === "onWillScroll") {
219
        /**
220
         * ScrollableCommonMethod has a method `onWillScroll(handler: Optional<OnWillScrollCallback>): T;`
221
         * ScrollAttribute extends ScrollableCommonMethod and overrides this method as
222
         * `onWillScroll(handler: ScrollOnWillScrollCallback): ScrollAttribute;`. So that override is not
223
         * valid and cannot be correctly processed and we want to stub this for now.
224
         */
225
        return [{
226
            ...ts.factory.createParameterDeclaration(
227
                undefined,
228
                undefined,
229
                "stub_for_onWillScroll",
230
                undefined,
231
                {
232
                    ...ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
233
                    getText: () => "any"
234
                },
235
            ),
236
            getText: () => "stub_for_onWillScroll: any",
237
        }]
238
    }
239
    return Array.from(method.parameters)
240
}
241

242
export function generateSignature(method: ts.ConstructorDeclaration | ts.MethodDeclaration | ts.MethodSignature | ts.CallSignatureDeclaration): NamedMethodSignature {
243
    const parameters = tempExtractParameters(method)
244
    const returnName = identName(method.type)!
245
    const returnType = returnName === "void" ? Type.Void
246
        : isStatic(method.modifiers) ? new Type(returnName) : Type.This
247
    return new NamedMethodSignature(returnType,
248
        parameters
249
            .map(it => new Type(mapType(it.type), it.questionToken != undefined)),
250
        parameters
251
            .map(it => identName(it.name)!),
252
    )
253
}
254

255
function generateArgConvertor(table: DeclarationTable, param: ts.ParameterDeclaration): ArgConvertor {
256
    if (!param.type) throw new Error("Type is needed")
257
    let paramName = asString(param.name)
258
    let optional = param.questionToken !== undefined
259
    return table.typeConvertor(paramName, param.type, optional)
260
}
261

262
function generateRetConvertor(typeNode?: ts.TypeNode): RetConvertor {
263
    let nativeType = typeNode ? mapCInteropRetType(typeNode) : "void"
264
    let isVoid = nativeType == "void"
265
    return {
266
        isVoid: isVoid,
267
        nativeType: () => nativeType,
268
        macroSuffixPart: () => isVoid ? "V" : ""
269
    }
270
}
271

272
function mapCInteropRetType(type: ts.TypeNode): string {
273
    if (type.kind == ts.SyntaxKind.VoidKeyword) {
274
        return `void`
275
    }
276
    if (type.kind == ts.SyntaxKind.NumberKeyword) {
277
        return PrimitiveType.Int32.getText()
278
    }
279
    if (type.kind == ts.SyntaxKind.BooleanKeyword) {
280
        return PrimitiveType.Boolean.getText()
281
    }
282
    if (ts.isTypeReferenceNode(type)) {
283
        let name = identName(type.typeName)!
284
        /* HACK, fix */
285
        if (name.endsWith("Attribute")) return "void"
286
        switch (name) {
287
            /* ANOTHER HACK, fix */
288
            case "T": return "void"
289
            case "UIContext": return PrimitiveType.NativePointer.getText()
290
            default: return PrimitiveType.NativePointer.getText()
291
        }
292
    }
293
    if (type.kind == ts.SyntaxKind.StringKeyword) {
294
        /* HACK, fix */
295
        // return `KStringPtr`
296
        return "void"
297
    }
298
    if (ts.isUnionTypeNode(type)) {
299
        console.log(`WARNING: unhandled union type: ${type.getText()}`)
300
        // TODO: not really properly supported.
301
        if (type.types[0].kind == ts.SyntaxKind.VoidKeyword) return "void"
302
        if (type.types.length == 2) {
303
            if (type.types[1].kind == ts.SyntaxKind.UndefinedKeyword) return `void`
304
            if (ts.isLiteralTypeNode(type.types[1]) && type.types[1].literal.kind == ts.SyntaxKind.NullKeyword) {
305
                // NavPathStack | null
306
                return mapCInteropRetType(type.types[0])
307
            }
308
        }
309
        // TODO: return just type of the first elem
310
        // for the materialized class getter with union type
311
        return mapCInteropRetType(type.types[0])
312
    }
313
    if (ts.isArrayTypeNode(type)) {
314
        /* HACK, fix */
315
        // return array by some way
316
        return "void"
317
    }
318
    if (ts.isParenthesizedTypeNode(type)) {
319
        return `(${mapCInteropRetType(type.type)})`
320
    }
321
    throw new Error(type.getText())
322
}
323

324

325
class ImportsAggregateCollector extends TypeDependenciesCollector {
326
    constructor(
327
        private readonly peerLibrary: PeerLibrary,
328
        private readonly expandAliases: boolean,
329
    ) {
330
        super(peerLibrary.declarationTable.typeChecker!)
331
    }
332

333
    override convertImport(node: ts.ImportTypeNode): ts.Declaration[] {
334
        const generatedName = mapType(node)
335
        if (!this.peerLibrary.importTypesStubToSource.has(generatedName)) {
336
            this.peerLibrary.importTypesStubToSource.set(generatedName, node.getText())
337
        }
338
        let syntheticDeclaration: ts.Declaration
339

340
        if (node.qualifier?.getText() === 'Resource') {
341
            syntheticDeclaration = makeSyntheticTypeAliasDeclaration(
342
                'SyntheticDeclarations',
343
                generatedName,
344
                ts.factory.createTypeReferenceNode("ArkResource"),
345
            )
346
            addSyntheticDeclarationDependency(syntheticDeclaration, {feature: "ArkResource", module: "./ArkResource"})
347
        } else {
348
            syntheticDeclaration = makeSyntheticTypeAliasDeclaration(
349
                'SyntheticDeclarations',
350
                generatedName,
351
                ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
352
            )
353
        }
354
        return [
355
            ...super.convertImport(node),
356
            syntheticDeclaration
357
        ]
358
    }
359

360
    override convertTypeReference(node: ts.TypeReferenceNode): ts.Declaration[] {
361
        const declarations = super.convertTypeReference(node)
362
        const result = [...declarations]
363
        for (const decl of declarations) {
364
            // expand type aliaces because we have serialization inside peers methods
365
            if (this.expandAliases && ts.isTypeAliasDeclaration(decl)) {
366
                result.push(...this.convert(decl.type))
367
            }
368
        }
369
        return result
370
    }
371
}
372

373
class FilteredDeclarationCollector extends DeclarationDependenciesCollector {
374
    constructor(
375
        private readonly library: PeerLibrary,
376
        typeDepsCollector: TypeDependenciesCollector,
377
    ) {
378
        super(library.declarationTable.typeChecker!, typeDepsCollector)
379
    }
380

381
    protected override convertHeritageClause(clause: ts.HeritageClause): ts.Declaration[] {
382
        const parent = clause.parent
383
        if (ts.isClassDeclaration(parent) && this.library.isComponentDeclaration(parent)) {
384
            return []
385
        }
386
        return super.convertHeritageClause(clause)
387
    }
388
}
389

390
class ComponentsCompleter {
391
    constructor(
392
        private readonly library: PeerLibrary,
393
    ) {}
394

395
    private componentNameByClass(node: ts.ClassDeclaration): string {
396
        return node.name!.text
397
    }
398

399
    public process(): void {
400
        for (let i = 0; i < this.library.componentsDeclarations.length; i++) {
401
            const attributes = this.library.componentsDeclarations[i].attributesDeclarations
402
            if ((attributes.heritageClauses?.length ?? 0) > 1)
403
                throw new Error("Expected component attributes to have single heritage clause at most")
404
            const heritage = attributes.heritageClauses?.[0]
405
            if (!heritage)
406
                continue
407
            const parentDecls = getDeclarationsByNode(this.library.declarationTable.typeChecker!, heritage.types[0].expression)
408
                // to resolve a problem with duplicate CommonMethod interface in koala fakes
409
                .filter(it => ts.isClassDeclaration(it))
410
            if (parentDecls.length !== 1)
411
                throw new Error("Expected parent to have single declaration")
412
            const parentDecl = parentDecls[0]
413
            if (!ts.isClassDeclaration(parentDecl))
414
                throw new Error("Expected parent to be a class")
415
            if (!this.library.isComponentDeclaration(parentDecl)) {
416
                this.library.componentsDeclarations.push(new ComponentDeclaration(
417
                    this.componentNameByClass(parentDecl),
418
                    undefined,
419
                    parentDecl,
420
                ))
421
            }
422
        }
423
        // topological sort
424
        const components = this.library.componentsDeclarations
425
        for (let i = 0; i < components.length; i++) {
426
            for (let j = i + 1; j < components.length; j++) {
427
                if (isSubclassComponent(this.library.declarationTable.typeChecker!, components[i], components[j])) {
428
                    components.splice(i, 0, ...components.splice(j, 1))
429
                    i--
430
                    break
431
                }
432
            }
433
        }
434
    }
435
}
436

437
class PeersGenerator {
438
    constructor(
439
        private readonly library: PeerLibrary,
440
    ) {}
441

442
    private get declarationTable(): DeclarationTable {
443
        return this.library.declarationTable
444
    }
445

446
    private extractMethods(node: ts.ClassDeclaration | ts.InterfaceDeclaration): (ts.MethodDeclaration | ts.CallSignatureDeclaration)[] {
447
        return (node.members as ts.NodeArray<ts.Node>).filter(
448
            it => (ts.isMethodDeclaration(it) || ts.isCallSignatureDeclaration(it))
449
        ) as (ts.MethodDeclaration | ts.CallSignatureDeclaration)[]
450
    }
451

452
    private processMethodOrCallable(
453
        method: ts.MethodDeclaration | ts.CallSignatureDeclaration,
454
        peer: PeerClass,
455
        parentName?: string
456
    ): PeerMethod | undefined {
457
        const isCallSignature = ts.isCallSignatureDeclaration(method)
458
        // Some method have other parents as part of their names
459
        // Such as the ones coming from thr friend interfaces
460
        // E.g. ButtonInterface instead of ButtonAttribute
461
        const originalParentName = parentName ?? peer.originalClassName!
462
        const methodName = isCallSignature ? `_set${peer.componentName}Options` : identName(method.name)!
463

464
        if (PeerGeneratorConfig.ignorePeerMethod.includes(methodName)) return
465

466
        this.declarationTable.setCurrentContext(`${originalParentName}.${methodName}()`)
467

468
        // TODO: fix this ugly code to prevent method args aliases name collisions.
469
        let methodIndex = 0, index = 0
470
        let clazz = method.parent
471
        if (ts.isClassDeclaration(clazz) || ts.isInterfaceDeclaration(clazz)) {
472
            clazz.members.forEach(it => {
473
                if (((ts.isMethodDeclaration(it) && identName(it.name) == methodName) || ts.isCallSignatureDeclaration(it))) {
474
                    if (method == it) methodIndex = index
475
                    index++
476
                }
477
            })
478
        }
479

480
        const parameters = tempExtractParameters(method)
481
        parameters.forEach((param, index) => {
482
            if (param.type) {
483
                this.declarationTable.requestType(
484
                    `Type_${originalParentName}_${methodName}${methodIndex == 0 ? "" : methodIndex.toString()}_Arg${index}`,
485
                    param.type,
486
                    this.library.shouldGenerateComponent(peer.componentName),
487
                )
488
            }
489
        })
490
        const argConvertors = parameters
491
            .map((param) => generateArgConvertor(this.declarationTable, param))
492
        const declarationTargets = parameters
493
            .map((param) => this.declarationTable.toTarget(param.type ??
494
                throwException(`Expected a type for ${asString(param)} in ${asString(method)}`)))
495
        const retConvertor = generateRetConvertor(method.type)
496

497
        // TODO: restore collapsing logic!
498
        const signature = /* collapsed?.signature ?? */ generateSignature(method)
499

500
        const peerMethod = new PeerMethod(
501
            originalParentName,
502
            declarationTargets,
503
            argConvertors,
504
            retConvertor,
505
            isCallSignature,
506
            false,
507
            new Method(methodName, signature, isStatic(method.modifiers) ? [MethodModifier.STATIC] : []),
508
        )
509
        this.declarationTable.setCurrentContext(undefined)
510
        return peerMethod
511
    }
512

513
    private createComponentAttributesDeclaration(node: ts.ClassDeclaration, peer: PeerClass): void {
514
        if (PeerGeneratorConfig.invalidAttributes.includes(peer.componentName)) {
515
            return
516
        }
517
        const seenAttributes = new Set<string>()
518
        node.members.forEach(child => {
519
            if (ts.isMethodDeclaration(child)) {
520
                this.processOptionAttribute(seenAttributes, child, peer)
521
            }
522
        })
523
    }
524

525
    private processOptionAttribute(seenAttributes: Set<string>, method: ts.MethodDeclaration | ts.MethodSignature, peer: PeerClass): void {
526
        const methodName = method.name.getText()
527
        if (seenAttributes.has(methodName)) {
528
            console.log(`WARNING: ignore seen method: ${methodName}`)
529
            return
530
        }
531
        const parameters = tempExtractParameters(method)
532
        if (parameters.length != 1) {
533
            // We only convert one argument methods to attributes.
534
            return
535
        }
536
        seenAttributes.add(methodName)
537
        const type = this.argumentType(methodName, parameters, peer)
538
        peer.attributesFields.push(`${methodName}?: ${type}`)
539
    }
540

541
    private argumentType(methodName: string, parameters: ts.ParameterDeclaration[], peer: PeerClass): string {
542
        const argumentTypeName = capitalize(methodName) + "ValuesType"
543
        if (parameters.length === 1 && ts.isTypeLiteralNode(parameters[0].type!)) {
544
            const typeLiteralStatements = parameters[0].type!.members
545
                .map(it => {
546
                    // TODO: properly support IndexSignature
547
                    if (ts.isIndexSignatureDeclaration(it)) {
548
                        return {
549
                            name: "indexed",
550
                            type: it.type,
551
                            questionToken: !!it.questionToken
552
                        }
553
                    }
554
                    if (!ts.isPropertySignature(it)) {
555
                        throw new Error(`Expected type literal property to be ts.PropertySignature, not ${asString(it)} got "${it.getText()}"`)
556
                    }
557
                    return {
558
                        name: asString(it.name),
559
                        type: it.type!,
560
                        questionToken: !!it.questionToken
561
                    }
562
                })
563

564
            peer.attributesTypes.push(
565
                {typeName: argumentTypeName, content: this.createParameterType(argumentTypeName, typeLiteralStatements)}
566
            )
567
            // Arkts needs a named type as its argument method, not an anonymous type
568
            // at which producing 'SyntaxError: Invalid Type' error
569
            const peerMethod = peer.methods.find((method) => method.overloadedName == methodName)
570
            if (peerMethod !== undefined) {
571
                peerMethod.method.signature.args = [new Type(argumentTypeName)]
572
            }
573
            return argumentTypeName
574
        }
575
        if (parameters.length > 2) {
576
            const attributeInterfaceStatements = parameters.map(it => ({
577
                name: asString(it.name),
578
                type: it.type!,
579
                questionToken: !!it.questionToken
580
            }))
581
            peer.attributesTypes.push(
582
                {typeName: argumentTypeName, content: this.createParameterType(argumentTypeName, attributeInterfaceStatements)}
583
            )
584
            return argumentTypeName
585
        }
586

587
        return parameters.map(it => mapType(it.type)).join(', ')
588
    }
589

590
    private createParameterType(
591
        name: string,
592
        attributes: { name: string, type: ts.TypeNode, questionToken: boolean }[]
593
    ): string {
594
        const attributeDeclarations = attributes
595
            .map(it => `\n  ${it.name}${it.questionToken ? "?" : ""}: ${mapType(it.type)}`)
596
            .join('')
597
        return `export interface ${name} {${attributeDeclarations}\n}`
598
    }
599

600
    private fillInterface(peer: PeerClass, node: ts.InterfaceDeclaration) {
601
        peer.originalInterfaceName = identName(node.name)!
602
        const tsMethods = this.extractMethods(node)
603
        const peerMethods = tsMethods
604
            .filter(it => ts.isCallSignatureDeclaration(it))
605
            .map(it => this.processMethodOrCallable(it, peer, identName(node)!))
606
            .filter(isDefined)
607
        PeerMethod.markOverloads(peerMethods)
608
        peer.methods.push(...peerMethods)
609
    }
610

611
    private fillClass(peer: PeerClass, node: ts.ClassDeclaration) {
612
        peer.originalClassName = className(node)
613
        peer.hasGenericType = (node.typeParameters?.length ?? 0) > 0
614
        const parent = singleParentDeclaration(this.declarationTable.typeChecker!, node) as ts.ClassDeclaration
615
        if (parent) {
616
            const parentComponent = this.library.findComponentByDeclaration(parent)!
617
            peer.originalParentName = className(parent)
618
            peer.originalParentFilename = parent.getSourceFile().fileName
619
            peer.parentComponentName = parentComponent.name
620
        }
621

622
        const peerMethods = this.extractMethods(node)
623
            .map(it => this.processMethodOrCallable(it, peer))
624
            .filter(isDefined)
625
        PeerMethod.markOverloads(peerMethods)
626
        peer.methods.push(...peerMethods)
627

628
        this.createComponentAttributesDeclaration(node, peer)
629
    }
630

631
    public generatePeer(component: ComponentDeclaration): void {
632
        const sourceFile = component.attributesDeclarations.parent
633
        if (!ts.isSourceFile(sourceFile))
634
            throw new Error("Expected parent of attributes to be a SourceFile")
635
        const file = this.library.findFileByOriginalFilename(sourceFile.fileName)
636
        if (!file)
637
            throw new Error("Not found a file corresponding to attributes class")
638
        const peer = new PeerClass(file, component.name, sourceFile.fileName, this.declarationTable)
639
        if (component.interfaceDeclaration)
640
            this.fillInterface(peer, component.interfaceDeclaration)
641
        this.fillClass(peer, component.attributesDeclarations)
642
        file.peers.set(component.name, peer)
643
    }
644
}
645

646
export class PeerProcessor {
647
    private readonly typeDependenciesCollector: TypeDependenciesCollector
648
    private readonly declDependenciesCollector: DeclarationDependenciesCollector
649
    private readonly serializeDepsCollector: DeclarationDependenciesCollector
650

651
    constructor(
652
        private readonly library: PeerLibrary,
653
    ) {
654
        this.typeDependenciesCollector = new ImportsAggregateCollector(this.library, false)
655
        this.declDependenciesCollector = new FilteredDeclarationCollector(this.library, this.typeDependenciesCollector)
656
        this.serializeDepsCollector = new FilteredDeclarationCollector(
657
            this.library, new ImportsAggregateCollector(this.library, true))
658
    }
659
    private get declarationTable(): DeclarationTable {
660
        return this.library.declarationTable
661
    }
662

663
    private processBuilder(target: ts.InterfaceDeclaration | ts.ClassDeclaration) {
664
        let name = nameOrNull(target.name)!
665
        if (this.library.builderClasses.has(name)) {
666
            return
667
        }
668

669
        if (isCustomBuilderClass(name)) {
670
            return
671
        }
672

673
        const builderClass = toBuilderClass(name, target, this.declarationTable.typeChecker!)
674
        this.library.builderClasses.set(name, builderClass)
675
    }
676

677
    private processMaterialized(target: ts.InterfaceDeclaration | ts.ClassDeclaration) {
678
        let name = nameOrNull(target.name)!
679
        if (this.library.materializedClasses.has(name)) {
680
            return
681
        }
682

683
        const isClass = ts.isClassDeclaration(target)
684
        const isInterface = ts.isInterfaceDeclaration(target)
685

686
        const superClassType = target.heritageClauses
687
            ?.filter(it => it.token == ts.SyntaxKind.ExtendsKeyword)[0]?.types[0]
688

689

690
        const superClass = superClassType ?
691
            new SuperElement(
692
                identName(superClassType.expression)!,
693
                superClassType.typeArguments?.filter(ts.isTypeReferenceNode).map(it => identName(it.typeName)!))
694
            : undefined
695

696
        const importFeatures = this.serializeDepsCollector.convert(target)
697
            .filter(it => this.isSourceDecl(it))
698
            .filter(it => PeerGeneratorConfig.needInterfaces || checkTSDeclarationMaterialized(it) || isSyntheticDeclaration(it))
699
            .map(it => convertDeclToFeature(this.library, it))
700
        const generics = target.typeParameters?.map(it => it.getText())
701

702
        let constructor = isClass ? target.members.find(ts.isConstructorDeclaration) : undefined
703
        let mConstructor = this.makeMaterializedMethod(name, constructor)
704
        const finalizerReturnType = {isVoid: false, nativeType: () => PrimitiveType.NativePointer.getText(), macroSuffixPart: () => ""}
705
        let mFinalizer = new MaterializedMethod(name, [], [], finalizerReturnType, false,
706
            new Method("getFinalizer", new NamedMethodSignature(Type.Pointer, [], [], []), [MethodModifier.STATIC]))
707
        let mFields = isClass
708
            ? target.members
709
                .filter(ts.isPropertyDeclaration)
710
                .map(it => this.makeMaterializedField(name, it))
711
            : isInterface
712
                ? target.members
713
                    .filter(ts.isPropertySignature)
714
                    .map(it => this.makeMaterializedField(name, it))
715
                : []
716

717
        let mMethods = isClass
718
            ? target.members
719
                .filter(ts.isMethodDeclaration)
720
                .map(method => this.makeMaterializedMethod(name, method))
721
            : isInterface
722
                ? target.members
723
                .filter(ts.isMethodSignature)
724
                .map(method => this.makeMaterializedMethod(name, method))
725
                : []
726
        this.library.materializedClasses.set(name,
727
            new MaterializedClass(name, isInterface, superClass, generics, mFields, mConstructor, mFinalizer, importFeatures, mMethods))
728
    }
729

730
    private makeMaterializedField(className: string, property: ts.PropertyDeclaration | ts.PropertySignature): MaterializedField {
731
        const name = identName(property.name)!
732
        this.declarationTable.setCurrentContext(`Materialized_${className}_${name}`)
733
        const declarationTarget = this.declarationTable.toTarget(property.type!)
734
        const argConvertor = this.declarationTable.typeConvertor(name, property.type!)
735
        const retConvertor = generateRetConvertor(property.type!)
736
        const modifiers = isReadonly(property.modifiers) ? [FieldModifier.READONLY] : []
737
        this.declarationTable.setCurrentContext(undefined)
738
        return new MaterializedField(declarationTarget, argConvertor, retConvertor,
739
            new Field(name, new Type(mapType(property.type)), modifiers))
740
    }
741

742
    private makeMaterializedMethod(parentName: string, method: ts.ConstructorDeclaration | ts.MethodDeclaration | ts.MethodSignature | undefined) {
743
        const methodName = method === undefined || ts.isConstructorDeclaration(method) ? "ctor" : identName(method.name)!
744
        this.declarationTable.setCurrentContext(`Materialized_${parentName}_${methodName}`)
745

746
        const retConvertor = method === undefined || ts.isConstructorDeclaration(method)
747
            ? { isVoid: false, isStruct: false, nativeType: () => PrimitiveType.NativePointer.getText(), macroSuffixPart: () => "" }
748
            : generateRetConvertor(method.type)
749

750
        if (method === undefined) {
751
            // interface or class without constructors
752
            const ctor = new Method("ctor", new NamedMethodSignature(Type.Void, [], []), [MethodModifier.STATIC])
753
            this.declarationTable.setCurrentContext(undefined)
754
            return new MaterializedMethod(parentName, [], [], retConvertor, false, ctor)
755
        }
756

757
        const generics = method.typeParameters?.map(it => it.getText())
758
        const declarationTargets = method.parameters.map(param =>
759
            this.declarationTable.toTarget(param.type ??
760
                throwException(`Expected a type for ${asString(param)} in ${asString(method)}`)))
761
        method.parameters.forEach(it => this.declarationTable.requestType(undefined, it.type!, true))
762
        const argConvertors = method.parameters.map(param => generateArgConvertor(this.declarationTable, param))
763
        const signature = generateSignature(method)
764
        const modifiers = ts.isConstructorDeclaration(method) || isStatic(method.modifiers) ? [MethodModifier.STATIC] : []
765
        this.declarationTable.setCurrentContext(undefined)
766
        return new MaterializedMethod(parentName, declarationTargets, argConvertors, retConvertor, false,
767
            new Method(methodName, signature, modifiers, generics))
768
    }
769

770
    private collectDepsRecursive(node: ts.Declaration | ts.TypeNode, deps: Set<ts.Declaration>): void {
771
        const currentDeps = ts.isTypeNode(node)
772
            ? convertTypeNode(this.typeDependenciesCollector, node)
773
            : convertDeclaration(this.declDependenciesCollector, node)
774
        for (const dep of currentDeps) {
775
            if (deps.has(dep)) continue
776
            if (!this.isSourceDecl(dep)) continue
777
            deps.add(dep)
778
            this.collectDepsRecursive(dep, deps)
779
        }
780
    }
781

782
    private processEnum(node: ts.EnumDeclaration) {
783
        const file = this.getDeclSourceFile(node)
784
        let name = node.name.getText()
785
        let comment = getComment(file, node)
786
        let enumEntity = new EnumEntity(name, comment)
787
        node.forEachChild(child => {
788
            if (ts.isEnumMember(child)) {
789
                let name = child.name.getText()
790
                let comment = getComment(file, child)
791
                enumEntity.pushMember(name, comment, child.initializer?.getText())
792
            }
793
        })
794
        this.library.findFileByOriginalFilename(file.fileName)!.pushEnum(enumEntity)
795
    }
796

797
    private isSourceDecl(node: ts.Declaration): boolean {
798
        if (isSyntheticDeclaration(node))
799
            return true
800
        if (ts.isModuleBlock(node.parent))
801
            return this.isSourceDecl(node.parent.parent)
802
        if (ts.isTypeParameterDeclaration(node))
803
            return false
804
        if (!ts.isSourceFile(node.parent))
805
            throw 'Expected declaration to be at file root'
806
        return !node.parent.fileName.endsWith('stdlib.d.ts')
807
    }
808

809
    private getDeclSourceFile(node: ts.Declaration): ts.SourceFile {
810
        if (ts.isModuleBlock(node.parent))
811
            return this.getDeclSourceFile(node.parent.parent)
812
        if (!ts.isSourceFile(node.parent))
813
            throw 'Expected declaration to be at file root'
814
        return node.parent
815
    }
816

817
    private generateActualComponents(): ComponentDeclaration[] {
818
        const components = this.library.componentsDeclarations
819
        if (!this.library.componentsToGenerate.size)
820
            return components
821
        const entryComponents = components.filter(it => this.library.shouldGenerateComponent(it.name))
822
        return components.filter(component => {
823
            return entryComponents.includes(component)
824
                // entryComponents.some(entryComponent => isSubclassComponent(this.declarationTable.typeChecker!, entryComponent, component))
825
        })
826
    }
827

828
    private generateDeclarations(): Set<ts.Declaration> {
829
        const deps = new Set(this.generateActualComponents().flatMap(it => {
830
            const decls: ts.Declaration[] = [it.attributesDeclarations]
831
            if (it.interfaceDeclaration)
832
                decls.push(it.interfaceDeclaration)
833
            return decls
834
        }))
835
        const depsCopy = Array.from(deps)
836
        for (const dep of depsCopy) {
837
            this.collectDepsRecursive(dep, deps)
838
        }
839
        for (const dep of Array.from(deps)) {
840
            if (ts.isEnumMember(dep)) {
841
                deps.add(dep.parent)
842
                deps.delete(dep)
843
            }
844
        }
845
        for (const dep of Array.from(deps)) {
846
            if (PeerGeneratorConfig.isConflictedDeclaration(dep)) {
847
                deps.delete(dep)
848
                this.library.conflictedDeclarations.add(dep)
849
            }
850
        }
851
        return deps
852
    }
853

854
    process(): void {
855
        new ComponentsCompleter(this.library).process()
856
        const peerGenerator = new PeersGenerator(this.library)
857
        for (const component of this.library.componentsDeclarations)
858
            peerGenerator.generatePeer(component)
859
        for (const dep of this.generateDeclarations()) {
860
            if (isSyntheticDeclaration(dep))
861
                continue
862
            const file = this.library.findFileByOriginalFilename(this.getDeclSourceFile(dep).fileName)!
863
            const isPeerDecl = this.library.isComponentDeclaration(dep)
864

865
            if (!isPeerDecl && (ts.isClassDeclaration(dep) || ts.isInterfaceDeclaration(dep))) {
866
                if (isBuilderClass(dep)) {
867
                    this.processBuilder(dep)
868
                } else if (isMaterialized(dep)) {
869
                    this.processMaterialized(dep)
870
                    continue
871
                }
872
            }
873

874
            if (ts.isEnumDeclaration(dep)) {
875
                this.processEnum(dep)
876
                continue
877
            }
878

879
            this.declDependenciesCollector.convert(dep).forEach(it => {
880
                if (this.isSourceDecl(it) && (PeerGeneratorConfig.needInterfaces || isSyntheticDeclaration(it))
881
                    && needImportFeature(this.library.declarationTable.language, it))
882
                    file.importFeatures.push(convertDeclToFeature(this.library, it))
883
            })
884
            this.serializeDepsCollector.convert(dep).forEach(it => {
885
                if (this.isSourceDecl(it) && PeerGeneratorConfig.needInterfaces
886
                    && needImportFeature(this.library.declarationTable.language, it)) {
887
                    file.serializeImportFeatures.push(convertDeclToFeature(this.library, it))
888
                }
889
            })
890
            if (PeerGeneratorConfig.needInterfaces
891
                && needImportFeature(this.library.declarationTable.language, dep)) {
892
                file.declarations.add(dep)
893
                file.importFeatures.push(convertDeclToFeature(this.library, dep))
894
            }
895
        }
896
    }
897
}
898

899
function needImportFeature(language: Language, decl: ts.Declaration): boolean {
900
    return !(language === Language.ARKTS && !ts.isEnumDeclaration(decl));
901
}
902

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

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

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

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