idlize

Форк
0
/
StructTransformer.ts 
1010 строк · 36.8 Кб
1
/*
2
 * Copyright (c) 2022-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 'ohos-typescript'
17
import { AbstractVisitor } from './AbstractVisitor'
18
import { CallExpressionCollector } from './CallExpressionCollector'
19
import { EntryTracker } from './EntryTracker'
20
import { Importer, isOhosImport } from './Importer'
21
import { adaptorClassName, adaptorComponentName, adaptorEtsName, adaptorEtsParameterName, asString, backingField, backingFieldName, CallTable, collect, consumeVariableName, contextLocalStateOf, createConsumeInitializerField, createProvideInitializerField, customDialogImplName, deduceConsumeName, deduceProvideName, dropBuilder, EntryDecorator, filterConsumes, filterDecorators, filterDefined, filterModifiers, filterProvides, hasLocalDeclaration, initializers, isBuilderLambdaCall, isBuiltinComponentName, isGlobalBuilder, isState, isStructCall, LocalStoragePropertyName, mangleIfBuild, NameTable, prependDoubleLineMemoComment, prependMemoComment, provideVariableName, RewriteNames, voidLambdaType, WatchDecorator } from './utils'
22
import { BuilderParam, classifyProperty, ClassState, PropertyTranslator } from './PropertyTranslators'
23
import { Any, assignment, createThisFieldAccess, Export, findDecoratorArguments, findDecoratorLiterals, getDeclarationsByNode, hasDecorator, id, isKnownIdentifier, isUndefined, optionalParameter, orUndefined, parameter, partial, Private, provideAnyTypeIfNone, Undefined, undefinedValue, Void } from './ApiUtils'
24

25
function parameterNameIdentifier(name: ts.LeftHandSideExpression): ts.Identifier {
26
    if (ts.isIdentifier(name)) {
27
        const newName = adaptorEtsParameterName(name)
28
        return newName
29
    } else {
30
        throw new Error("expected ETS name to be an Identifier, got: " + ts.SyntaxKind[name.kind])
31
    }
32
}
33

34
export class StructTransformer extends AbstractVisitor {
35
    private printer = ts.createPrinter({ removeComments: false })
36
    constructor(
37
        sourceFile: ts.SourceFile,
38
        ctx: ts.TransformationContext,
39
        public typechecker: ts.TypeChecker,
40
        public importer: Importer,
41
        public nameTable: NameTable,
42
        public entryTracker: EntryTracker,
43
        public callTable: CallTable
44
    ) {
45
        super(sourceFile, ctx)
46
        this.importer.addAdaptorImport(adaptorComponentName("PageTransitionEnter"))
47
        this.importer.addAdaptorImport(adaptorComponentName("PageTransitionExit"))
48
    }
49

50
    dropImportEtsExtension(node: ts.ImportDeclaration, oldLiteral: string): ts.ImportDeclaration {
51
        if (!oldLiteral.endsWith(".ets")) return node
52
        const newLiteral = oldLiteral.substring(0, oldLiteral.length - 4)
53
        return ts.factory.updateImportDeclaration(
54
            node,
55
            node.modifiers,
56
            node.importClause,
57
            ts.factory.createStringLiteral(newLiteral),
58
            undefined
59
        )
60
    }
61

62
    translateImportDeclaration(node: ts.ImportDeclaration): ts.ImportDeclaration {
63
        const oldModuleSpecifier = node.moduleSpecifier
64
        if (!ts.isStringLiteral(oldModuleSpecifier)) return node
65
        const oldLiteral = oldModuleSpecifier.text
66

67
        if (isOhosImport(oldLiteral)) {
68
            const oldDefaultName = node.importClause!.name ? ts.idText(node.importClause!.name) : ""
69
            return this.importer.translateOhosImport(node, oldLiteral, oldDefaultName)
70
        }
71

72
        return this.dropImportEtsExtension(node, oldLiteral)
73
    }
74

75
    emitStartApplicationBody(name: string): ts.Block {
76
        return ts.factory.createBlock(
77
            [ts.factory.createReturnStatement(ts.factory.createCallExpression(
78
                id(name),
79
                undefined,
80
                []
81
            ))],
82
            true
83
        )
84
    }
85

86
    emitStartApplicationDeclaration(name: string): ts.Statement {
87
        const koalaEntry = ts.factory.createFunctionDeclaration(
88
            [],
89
            [Export()],
90
            undefined,
91
            id("KoalaEntry"),
92
            undefined,
93
            [],
94
            undefined,
95
            this.emitStartApplicationBody(name)
96
        )
97
        prependMemoComment(koalaEntry)
98
        return koalaEntry
99
    }
100

101
    entryCode: ts.Statement[] = []
102
    entryFile: string | undefined = undefined
103
    prepareEntryCode(name: string) {
104
        this.entryCode = [
105
            this.emitStartApplicationDeclaration(name),
106
        ]
107
        this.entryFile = this.sourceFile.fileName
108
    }
109
    findContextLocalState(name: string, type: ts.TypeNode | undefined): ts.Expression {
110
        const state = ts.factory.createCallExpression(
111
            id(this.importer.withRuntimeImport("contextLocal")),
112
            type ? [type] : undefined,
113
            [ts.factory.createStringLiteral(name)]
114
        )
115
        return ts.factory.createAsExpression(
116
            state,
117
            ts.factory.createTypeReferenceNode(
118
                id("MutableState"),
119
                [type!]
120
            )
121
        )
122
    }
123

124
    createConsumeState(consume: ts.PropertyDeclaration): ts.Statement {
125
        if (!ts.isIdentifier(consume.name) && !ts.isPrivateIdentifier(consume.name)) {
126
            throw new Error("Expected an identifier")
127
        }
128

129
        return ts.factory.createVariableStatement(
130
            undefined,
131
            ts.factory.createVariableDeclarationList([
132
                ts.factory.createVariableDeclaration(
133
                    consumeVariableName(consume),
134
                    undefined,
135
                    undefined,
136
                    this.findContextLocalState(deduceConsumeName(consume), consume.type)
137
                )
138
            ],
139
                ts.NodeFlags.Const
140
            )
141
        )
142
    }
143

144
    createProvideState(provide: ts.PropertyDeclaration): ts.Statement {
145
        this.importer.addAdaptorImport("contextLocalStateOf")
146

147
        if (!ts.isIdentifier(provide.name) && !ts.isPrivateIdentifier(provide.name)) {
148
            throw new Error("Expected an identifier")
149
        }
150
        if (!provide.initializer) {
151
            throw new Error("Expected an initialization for @Provide " + deduceProvideName(provide))
152
        }
153
        return ts.factory.createVariableStatement(
154
            undefined,
155
            ts.factory.createVariableDeclarationList([
156
                ts.factory.createVariableDeclaration(
157
                    provideVariableName(provide),
158
                    undefined,
159
                    undefined,
160
                    contextLocalStateOf(deduceProvideName(provide), provide.initializer!, provide.type)
161
                )
162
            ],
163
                ts.NodeFlags.Const
164
            )
165
        )
166
    }
167

168
    createBuildProlog(
169
        structNode: ts.StructDeclaration,
170
        members?: ts.NodeArray<ts.ClassElement>,
171
        propertyTranslators?: PropertyTranslator[],
172
    ): ts.MethodDeclaration|undefined {
173

174
        const propertyInitializationProcessors = propertyTranslators ?
175
            filterDefined(propertyTranslators.map(it => it.translateToUpdate())) :
176
            undefined
177

178
        const watchHandlers = members ? this.translateWatchDecorators(members) : undefined
179

180
        if (!propertyInitializationProcessors?.length &&
181
            !watchHandlers?.length
182
        ) return undefined
183

184
        const body = ts.factory.createBlock(
185
            collect(
186
                propertyInitializationProcessors,
187
                watchHandlers,
188
            ),
189
            true
190
        )
191

192
        const className = adaptorClassName(structNode.name!)!
193

194
        const method = ts.factory.createMethodDeclaration(
195
            undefined,
196
            undefined,
197
            undefined,
198
            id(RewriteNames.UpdateStruct),
199
            undefined,
200
            undefined,
201
            [
202
                parameter(initializers(), orUndefined(partial(className)))
203
            ],
204
            Void(),
205
            body
206
        )
207

208
        return prependMemoComment(method)
209
    }
210

211
    private createThisMethodCall(name: string | ts.Identifier | ts.PrivateIdentifier, args?: ReadonlyArray<ts.Expression>): ts.Expression {
212
        return ts.factory.createCallChain(
213
            createThisFieldAccess(name),
214
            undefined,
215
            undefined,
216
            args
217
        )
218
    }
219

220
    private createWatchCall(name: string): ts.Statement {
221
        return ts.factory.createExpressionStatement(this.createThisMethodCall(name))
222
    }
223

224
    translateWatchDecorators(members?: ts.NodeArray<ts.ClassElement>): ts.Statement[] {
225
        const statements: ts.Statement[] = []
226
        if (members && members.length) {
227
            for (const property of members) {
228
                if (ts.isPropertyDeclaration(property)) {
229
                    if (ts.isIdentifier(property.name) || ts.isPrivateIdentifier(property.name)) {
230
                        const name = ts.idText(property.name)
231
                        const watches = findDecoratorLiterals(filterDecorators(property), WatchDecorator, 0)
232
                        if (watches && watches.length) statements.push(
233
                            ts.factory.createExpressionStatement(
234
                                ts.factory.createCallExpression(
235
                                    id(this.importer.withRuntimeImport("OnChange")),
236
                                    undefined,
237
                                    [
238
                                        createThisFieldAccess(name),
239
                                        ts.factory.createArrowFunction(
240
                                            undefined,
241
                                            undefined,
242
                                            [],
243
                                            undefined,
244
                                            ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
245
                                            watches.length == 1
246
                                                ? this.createThisMethodCall(watches[0])
247
                                                : ts.factory.createBlock(watches.map(it => this.createWatchCall(it)))
248
                                        ),
249
                                    ]
250
                                )
251
                            )
252
                        )
253
                    }
254
                }
255
            }
256
        }
257
        return statements
258
    }
259

260
    translateBuilder(structure: ts.StructDeclaration, propertyTranslators: PropertyTranslator[], member: ts.ClassElement, isMainBuild: boolean): ts.MethodDeclaration {
261
        if (!ts.isMethodDeclaration(member)) {
262
            throw new Error("Expected member declaration, got: " + ts.SyntaxKind[member.kind])
263
        }
264

265
        const className = adaptorClassName(structure.name!)!
266

267
        const stateParameters = isMainBuild ? [
268
            prependDoubleLineMemoComment(
269
                parameter(
270
                    "builder",
271
                    orUndefined(
272
                        ts.factory.createFunctionTypeNode(
273
                            undefined,
274
                            [
275
                                parameter(
276
                                    id("instance"),
277
                                    ts.factory.createTypeReferenceNode(className),
278
                                )
279
                            ],
280
                            Void()
281
                        )
282
                    )
283
                )
284
            )
285
        ] : []
286

287
        const newMethod = ts.factory.updateMethodDeclaration(
288
            member,
289
            dropBuilder(member.modifiers),
290
            member.asteriskToken,
291
            mangleIfBuild(member.name),
292
            member.questionToken,
293
            member.typeParameters,
294
            [
295
                ...stateParameters,
296
                ...member.parameters
297
            ],
298
            member.type,
299
            member.body
300
        )
301
        return prependMemoComment(newMethod)
302
    }
303

304
    translateGlobalBuilder(func: ts.FunctionDeclaration): ts.FunctionDeclaration {
305
        const newFunction = ts.factory.createFunctionDeclaration(
306
            dropBuilder(func.modifiers),
307
            func.asteriskToken,
308
            func.name,
309
            func.typeParameters,
310
            func.parameters.map(it => provideAnyTypeIfNone(it)),
311
            func.type,
312
            func.body
313
        )
314
        return prependMemoComment(newFunction)
315
    }
316

317
    translateMemberFunction(method: ts.MethodDeclaration): ts.MethodDeclaration {
318
        // TODO: nothing for now?
319
        return method
320
    }
321

322
    translateStructMembers(structNode: ts.StructDeclaration, propertyTranslators: PropertyTranslator[]): ts.ClassElement[] {
323
        const propertyMembers = propertyTranslators.map(translator =>
324
            translator.translateMember()
325

326
        )
327
        const updateStruct = this.createBuildProlog(structNode, structNode.members, propertyTranslators)
328

329
        // The rest of the struct members are translated here directly.
330
        const restMembers = structNode.members.map(member => {
331
            if (isKnownIdentifier(member.name, "build")) {
332
                return this.translateBuilder(structNode, propertyTranslators, member, true)
333
            } else if (hasDecorator(member, "Builder")) {
334
                return this.translateBuilder(structNode, propertyTranslators, member, false)
335
            } else if (isKnownIdentifier(member.name, "pageTransition")) {
336
                return prependMemoComment(member)
337
            } else if (ts.isMethodDeclaration(member)) {
338
                return this.translateMemberFunction(member)
339
            }
340
            return []
341
        }).flat()
342
        return collect(
343
            ...propertyMembers,
344
            updateStruct,
345
            ...restMembers
346
        )
347
    }
348

349
    translateComponentName(name: ts.Identifier | undefined): ts.Identifier | undefined {
350
        if (!name) return undefined
351
        // return id(adaptorName(ts.idText(name)))
352
        return id(ts.idText(name))
353
    }
354

355
    /**
356
     * @param name - a unique state name
357
     * @returns a statement to initialize a context local state with new map
358
     */
359
    contextLocalStateMap(name: string): ts.Statement {
360
        this.importer.addAdaptorImport("contextLocalStateOf")
361
        return ts.factory.createExpressionStatement(contextLocalStateOf(name, ts.factory.createNewExpression(
362
            id("Map"),
363
            undefined,
364
            []
365
        )))
366
    }
367

368
    /**
369
     * @param source - a node to find named call expressions
370
     * @returns an array of statements corresponding to the found expressions
371
     */
372
    collectContextLocals(source: ts.Node): ts.Statement[] {
373
        const statements: ts.Statement[] = []
374
        const collector = new CallExpressionCollector(this.sourceFile, this.ctx,
375
            "ArkRadio",
376
            "ArkCheckbox",
377
            "ArkCheckboxGroup",
378
        )
379
        collector.visitor(source)
380
        if (collector.isVisited("ArkRadio")) {
381
            statements.push(this.contextLocalStateMap("contextLocalMapOfRadioGroups"))
382
        }
383
        if (collector.isVisited("ArkCheckbox") || collector.isVisited("ArkCheckboxGroup")) {
384
            statements.push(this.contextLocalStateMap("contextLocalMapOfCheckboxGroups"))
385
        }
386
        return statements
387
    }
388

389
    topLevelMemoFunctions: ts.FunctionDeclaration[] = []
390
    topLevelInitialization: ts.Statement[] = []
391

392
    createTopLevelMemo(node: ts.StructDeclaration, impl: boolean): ts.FunctionDeclaration {
393
        const className = this.translateComponentName(adaptorClassName(node.name))!
394
        const functionName = impl ? customDialogImplName(node.name) : node.name
395

396
        const factory = ts.factory.createArrowFunction(
397
            undefined,
398
            undefined,
399
            [],
400
            undefined,
401
            ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
402
            ts.factory.createNewExpression(
403
                className,
404
                undefined,
405
                undefined
406
            )
407
        )
408

409
        const provideVariables = filterProvides(node.members).map(it => this.createProvideState(it))
410
        const consumeVariables = filterConsumes(node.members).map(it => this.createConsumeState(it))
411
        const contextLocals = this.collectContextLocals(node)
412

413
        const additionalStatements = [
414
            ...provideVariables,
415
            ...consumeVariables,
416
            ...contextLocals,
417
        ]
418

419
        const provides = filterProvides(node.members).map(it => createProvideInitializerField(it))
420
        const consumes = filterConsumes(node.members).map(it => createConsumeInitializerField(it))
421
        const updatedInitializers = (provides.length != 0 || consumes.length != 0) ?
422
            ts.factory.createObjectLiteralExpression(
423
                [
424
                    ...provides,
425
                    ...consumes,
426
                    ts.factory.createSpreadAssignment(initializers()),
427
                ],
428
                true
429
            ) :
430
            initializers()
431

432

433
        const callInstantiate = ts.factory.createReturnStatement(
434
            ts.factory.createCallExpression(
435
                ts.factory.createPropertyAccessExpression(
436
                    className,
437
                    id("_instantiate")
438
            ),
439
            undefined,
440
            [
441
                impl ? undefinedValue() : id("style"),
442
                factory,
443
                impl ? undefinedValue() : id("content"),
444
                updatedInitializers
445
            ]
446
          ))
447

448
        const memoFunction = ts.factory.createFunctionDeclaration(
449
            [Export()],
450
            undefined,
451
            functionName,
452
            undefined,
453
            impl ? [
454
                optionalParameter(
455
                    initializers(),
456
                    partial(className),
457
                )
458
            ]:
459
            [
460
                optionalParameter(
461
                    "style",
462
                    Any(),
463
                ),
464
                prependDoubleLineMemoComment(
465
                    optionalParameter(
466
                        "content",
467
                        voidLambdaType(),
468
                    )
469
                ),
470
                optionalParameter(
471
                    initializers(),
472
                    partial(className),
473
                )
474
            ],
475
            ts.factory.createTypeReferenceNode(className),
476
            ts.factory.createBlock(
477
                [
478
                    ...additionalStatements,
479
                    callInstantiate
480
                ],
481
                true
482
            )
483
        )
484

485
        return prependMemoComment(memoFunction)
486
    }
487

488
    /*
489
    Creates something like:
490
        export function DialogExample(initializer: any = {}) {
491
            return { build: bindCustomDialog(DialogExampleImpl, initializer), buildOptions: initializer };
492
        }
493
    */
494
    createCustomDialogConstructor(node: ts.StructDeclaration) {
495
        return ts.factory.createFunctionDeclaration(
496
            undefined,
497
            [Export()],
498
            undefined,
499
            node.name,
500
            undefined,
501
            [
502
                parameter(
503
                    id("initializer"),
504
                    Any(),
505
                    ts.factory.createObjectLiteralExpression()
506
                )
507
            ],
508
            undefined,
509
            ts.factory.createBlock(
510
                [
511
                    ts.factory.createReturnStatement(
512
                        ts.factory.createObjectLiteralExpression([
513
                            ts.factory.createPropertyAssignment("build",
514
                                ts.factory.createCallExpression(
515
                                    id(this.importer.withAdaptorImport("bindCustomDialog")),
516
                                    undefined,
517
                                    [
518
                                        customDialogImplName(node.name)!,
519
                                        id("initializer")
520
                                    ]
521
                                )
522
                            ),
523
                            ts.factory.createPropertyAssignment("buildOptions",
524
                                id("initializer"))
525
                        ])
526
                    )
527
                ],
528
                true
529
            )
530
        )
531

532
    }
533

534
    createInitializerMethod(
535
        className: ts.Identifier,
536
        propertyTranslators: PropertyTranslator[]
537
    ): ts.MethodDeclaration {
538
        const parameters = [
539
            prependDoubleLineMemoComment(
540
                optionalParameter(
541
                    "content",
542
                    voidLambdaType(),
543
                )
544
            ),
545
            optionalParameter(
546
                initializers(),
547
                partial(className),
548
            )
549
        ]
550
        const initializations = filterDefined(
551
            propertyTranslators.map(it => it.translateToInitialization())
552
        )
553
        const buildParams = propertyTranslators.filter(it => it instanceof BuilderParam)
554
        if (buildParams.length > 0) {
555
            const field = createThisFieldAccess(backingField(buildParams[0].propertyName))
556
            initializations.push(ts.factory.createIfStatement(
557
                ts.factory.createBinaryExpression(
558
                    ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, field),
559
                    ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
560
                    id("content")
561
                ),
562
                assignment(field, id("content"))
563
            ))
564
        }
565

566
        return ts.factory.createMethodDeclaration(
567
            undefined,
568
            undefined,
569
            RewriteNames.InitializeStruct,
570
            undefined,
571
            undefined,
572
            parameters,
573
            Void(),
574
            ts.factory.createBlock(initializations, true)
575
        )
576
    }
577

578
    createTopLevelInitialization(node: ts.StructDeclaration): ts.ExpressionStatement {
579
        const routerPage = this.entryTracker.sourceFileToRoute(this.sourceFile)
580
        return ts.factory.createExpressionStatement(
581
            ts.factory.createCallExpression(
582
                id(this.importer.withOhosImport("ohos.router", "registerArkuiEntry")),
583
                undefined,
584
                [
585
                    id(ts.idText(node.name!)),
586
                    ts.factory.createStringLiteral(routerPage),
587
                ]
588
            )
589
        )
590
    }
591

592
    createEntryPointAlias(node: ts.StructDeclaration): ts.Statement {
593
        return ts.factory.createVariableStatement(
594
            [Export()],
595
            ts.factory.createVariableDeclarationList(
596
                [
597
                    ts.factory.createVariableDeclaration(
598
                        id("__Entry"),
599
                        undefined,
600
                        undefined,
601
                        id(ts.idText(node.name!)
602
                        )
603
                    )
604
                ],
605
                ts.NodeFlags.Const
606
            )
607
        )
608
    }
609

610
    translateStructToClass(node: ts.StructDeclaration): ts.ClassDeclaration {
611
        const className = this.translateComponentName(adaptorClassName(node.name)) // TODO make me string for proper reuse
612
        const baseClassName = this.importer.withAdaptorImport("ArkStructBase")
613

614
        let entryLocalStorage: ts.Expression | undefined = undefined
615

616
        if (hasDecorator(node, "CustomDialog")) {
617
            this.topLevelMemoFunctions.push(
618
                this.createTopLevelMemo(node, true),
619
                this.createCustomDialogConstructor(node)
620
            )
621
        } else {
622
            this.topLevelMemoFunctions.push(
623
                this.createTopLevelMemo(node, false)
624
            )
625
        }
626

627
        if (hasDecorator(node, EntryDecorator)) {
628
            this.topLevelInitialization.push(
629
                this.createTopLevelInitialization(node),
630
                this.createEntryPointAlias(node)
631
            )
632
            const args = findDecoratorArguments(filterDecorators(node), EntryDecorator, 0)
633
            switch (args?.length) {
634
                case 0:
635
                    break
636
                case 1:
637
                    entryLocalStorage = args[0]
638
                    break
639
                default:
640
                    throw new Error("Entry must have only one name, but got " + args?.length)
641
            }
642

643
            if (!node.name) throw new Error("Expected @Entry struct to have a name")
644
            this.entryTracker.addEntry(ts.idText(node.name), this.sourceFile)
645
        }
646

647
        const inheritance = ts.factory.createHeritageClause(
648
            ts.SyntaxKind.ExtendsKeyword,
649
            [ts.factory.createExpressionWithTypeArguments(
650
                id(baseClassName),
651
                [
652
                    ts.factory.createTypeReferenceNode(
653
                        adaptorComponentName(ts.idText(node.name!)),
654
                    )
655
                ]
656
            )]
657
        )
658

659
        const entryLocalStorageProperty = ts.factory.createPropertyDeclaration(
660
            undefined,
661
            [Private()],
662
            LocalStoragePropertyName,
663
            undefined,
664
            undefined,
665
            entryLocalStorage ?? ts.factory.createNewExpression(
666
                id(this.importer.withAdaptorImport("LocalStorage")),
667
                undefined,
668
                []
669
            )
670
        )
671

672
        const propertyTranslators = filterDefined(
673
            node.members.map(it => classifyProperty(it, this.importer, this.typechecker))
674
        )
675

676
        const translatedMembers = this.translateStructMembers(node, propertyTranslators)
677

678
        const createdClass = ts.factory.createClassDeclaration(
679
            filterModifiers(node),
680
            className,
681
            node.typeParameters,
682
            [inheritance],
683
            [
684
                entryLocalStorageProperty,
685
                this.createInitializerMethod(className!, propertyTranslators),
686
                ...translatedMembers,
687
            ]
688
        )
689

690
        return createdClass
691
    }
692

693
    findEtsAdaptorName(name: ts.LeftHandSideExpression): ts.LeftHandSideExpression {
694
        if (ts.isIdentifier(name)) {
695
            const newName = adaptorEtsName(name)
696
            this.importer.addAdaptorImport(ts.idText(newName))
697
            return newName
698
        } else {
699
            return name
700
        }
701
    }
702

703
    findEtsAdaptorClassName(name: ts.LeftHandSideExpression): ts.Identifier {
704
        if (ts.isIdentifier(name)) {
705
            const newName = adaptorClassName(name)!
706
            this.importer.addAdaptorImport(ts.idText(newName))
707
            return newName
708
        } else {
709
            throw new Error("expected ETS name to be an Identifier, got: " + ts.SyntaxKind[name.kind])
710
        }
711
    }
712

713
    createContentLambda(node: ts.EtsComponentExpression): ts.Expression {
714
        if (!node.body?.statements || node.body.statements.length == 0) {
715
            return undefinedValue()
716
        }
717

718
        const contentLambdaBody = ts.factory.createBlock(
719
            node.body?.statements,
720
            true
721
        )
722

723
        return ts.factory.createArrowFunction(
724
            undefined,
725
            undefined,
726
            [],
727
            undefined,
728
            ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
729
            contentLambdaBody
730
        )
731
    }
732

733
    createEtsInstanceLambda(node: ts.EtsComponentExpression): ts.Expression {
734
        // Either a lambda or undefined literal.
735
        const instanceArgument = node.arguments[0]
736

737
        if (isUndefined(instanceArgument)) {
738
            return instanceArgument
739
        }
740

741
        return this.createInstanceLambda(node, this.findEtsAdaptorClassName(node.expression))
742
    }
743

744
    createBuilderLambdaInstanceLambda(node: ts.EtsComponentExpression|ts.CallExpression, parameterTypeName?: ts.Identifier): ts.Expression {
745
        // Either a lambda or undefined literal.
746
        const instanceArgument = node.arguments[0]
747

748
        if (isUndefined(instanceArgument)) {
749
            return instanceArgument
750
        }
751

752
        return this.createInstanceLambda(node, undefined)
753
    }
754

755
    createInstanceLambda(node: ts.EtsComponentExpression|ts.CallExpression, parameterTypeName?: ts.Identifier): ts.Expression {
756
        // Either a lambda or undefined literal.
757
        const instanceArgument = node.arguments[0]
758

759
        if (isUndefined(instanceArgument)) {
760
            return instanceArgument
761
        }
762

763
        const parameterName = parameterNameIdentifier(node.expression)
764

765
        const lambdaParameter = parameter(
766
            parameterName,
767
            parameterTypeName ? ts.factory.createTypeReferenceNode(parameterTypeName) : undefined
768
        )
769

770
        const instanceLambdaBody = ts.factory.createBlock(
771
            [
772
                ts.factory.createExpressionStatement(instanceArgument)
773
            ],
774
            true
775
        )
776

777
        return ts.factory.createArrowFunction(
778
            undefined,
779
            undefined,
780
            [lambdaParameter],
781
            undefined,
782
            ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
783
            instanceLambdaBody
784
        )
785
    }
786

787
    translateEtsComponent(node: ts.EtsComponentExpression, newName: ts.LeftHandSideExpression): ts.CallExpression {
788
        const newArguments = [
789
            this.createEtsInstanceLambda(node),
790
            this.createContentLambda(node),
791
            ...node.arguments.slice(1)
792
        ]
793

794
        return ts.factory.createCallExpression(
795
            newName,
796
            node.typeArguments,
797
            newArguments
798
        )
799
    }
800

801

802

803
    translateBuiltinEtsComponent(node: ts.EtsComponentExpression): ts.CallExpression {
804
        const newName = this.findEtsAdaptorName(node.expression)
805
        return this.translateEtsComponent(node, newName)
806
    }
807

808
    translateUserEtsComponent(node: ts.EtsComponentExpression): ts.CallExpression {
809
        return this.translateEtsComponent(node, node.expression)
810
    }
811

812
    transformBuilderLambdaCall(node: ts.CallExpression): ts.CallExpression {
813
        const originalCall = ts.getOriginalNode(node) as ts.CallExpression
814
        const newName = this.callTable.builderLambdas.get(originalCall)
815
        if (!newName) return node
816

817
        const newArguments = [
818
            this.createBuilderLambdaInstanceLambda(node),
819
            ...node.arguments.slice(1)
820
        ]
821

822
        return ts.factory.updateCallExpression(
823
            node,
824
            id(newName),
825
            node.typeArguments,
826
            newArguments
827
        )
828
    }
829

830
    transformStructCall(node: ts.CallExpression): ts.CallExpression {
831
        const newArguments = [
832
            undefinedValue(),
833
            undefinedValue(),
834
            ...node.arguments
835
        ]
836

837
        return ts.factory.updateCallExpression(
838
            node,
839
            //id(newName),
840
            node.expression,
841
            node.typeArguments,
842
            newArguments
843
        )
844
    }
845

846
    // This is a heuristics to understand if the given property call
847
    // is a style setting call.
848
    isStyleSettingMethodCall(node: ts.CallExpression): boolean {
849
        const property = node.expression
850
        if (!property || !ts.isPropertyAccessExpression(property)) return false
851
        const name = property.name
852
        if (!name || !ts.isIdentifier(name)) return false
853

854
        const declarations = getDeclarationsByNode(this.typechecker, name)
855

856
        // TODO: handle multiple declarations
857
        const declaration = declarations[0]
858

859
        if (!declaration || !ts.isMethodDeclaration(declaration)) return false
860
        const returnType = declaration.type
861
        if (!returnType || !ts.isTypeReferenceNode(returnType)) return false
862
        const returnTypeName = returnType.typeName
863
        if (!returnTypeName || !ts.isIdentifier(returnTypeName)) return false
864
        const parent = declaration.parent
865
        if (!parent || !ts.isClassDeclaration(parent)) return false
866
        const parentName = parent.name
867
        if (!parentName || !ts.isIdentifier(parentName)) return false
868
        const parentNameString = ts.idText(parentName)
869

870
        const ohosDeclaredClass =
871
            parentNameString.endsWith("Attribute") ||
872
            parentNameString == "CommonMethod"
873

874
        return ohosDeclaredClass
875
    }
876

877
    // TODO: Somehow eTS compiler produces style setting methods with a type parameter.
878
    fixEmptyTypeArgs(node: ts.CallExpression): ts.CallExpression {
879
        if (this.isStyleSettingMethodCall(node)) {
880
            return ts.factory.updateCallExpression(node, node.expression, undefined, node.arguments)
881
        }
882
        return node
883
    }
884

885
    importIfEnum(node: ts.PropertyAccessExpression): ts.PropertyAccessExpression {
886
        const name = node.expression
887
        if (!ts.isIdentifier(name)) return node
888
        const receiverDeclarations = getDeclarationsByNode(this.typechecker, node.expression)
889
        const anyDeclaration = receiverDeclarations[0]
890
        if (anyDeclaration && ts.isEnumDeclaration(anyDeclaration)) {
891
            this.importer.addAdaptorImport(ts.idText(name))
892
        }
893

894
        // Just return the node itself.
895
        return node
896
    }
897

898
    appendTopLevelMemoFunctions(file: ts.SourceFile): ts.SourceFile {
899
        return ts.factory.updateSourceFile(file,
900
            [...file.statements, ...this.topLevelMemoFunctions, ...this.topLevelInitialization],
901
            file.isDeclarationFile,
902
            file.referencedFiles,
903
            file.typeReferenceDirectives,
904
            file.hasNoDefaultLib,
905
            file.libReferenceDirectives
906
        )
907
    }
908

909
    isDollarFieldAccess(node: ts.Expression): boolean {
910
        if (!ts.isPropertyAccessExpression(node)) return false
911
        const name = node.name
912
        if (!name) return false
913
        if (!ts.isIdentifier(name)) return false
914

915
        const receiver = node.expression
916
        if (!receiver) return false
917
        if (receiver.kind != ts.SyntaxKind.ThisKeyword) return false
918

919
        const nameString = ts.idText(name)
920
        return nameString.startsWith("$")
921
    }
922

923
    translateDollarFieldAccess(node: ts.PropertyAccessExpression): ts.PropertyAccessExpression {
924
        return ts.factory.createPropertyAccessExpression(
925
            node.expression,
926
            backingField(ts.idText(node.name).substring(1))
927
        )
928
    }
929

930
    isDollarFieldAssignment(node: ts.PropertyAssignment): boolean {
931
        if (!ts.isPropertyAccessExpression(node.initializer)) return false
932
        return this.isDollarFieldAccess(node.initializer)
933
    }
934

935
    translateDollarFieldAssignment(node: ts.PropertyAssignment): ts.PropertyAssignment {
936
        if (!ts.isIdentifier(node.name)) return node
937

938
        const initializer = node.initializer
939
        if (this.isDollarFieldAccess(initializer)) {
940
            const newInitializer = this.translateDollarFieldAccess(initializer as ts.PropertyAccessExpression)
941
            return ts.factory.createPropertyAssignment(backingFieldName(node.name), newInitializer)
942
        }
943

944
        return node
945
    }
946

947
    translateClass(node: ts.ClassDeclaration): ts.ClassDeclaration {
948
        const newMembers = node.members.flatMap(it => {
949
            if (!ts.isPropertyDeclaration(it) || !isState(it)) {
950
                return it
951
            }
952
            return new ClassState(it, this.importer).translateMember()
953
        })
954

955
        const createdClass = ts.factory.updateClassDeclaration(
956
            node,
957
            node.modifiers,
958
            node.name,
959
            node.typeParameters,
960
            node.heritageClauses,
961
            newMembers
962
        )
963
        return createdClass
964
    }
965

966
    isUserEts(node: ts.EtsComponentExpression): boolean {
967
        const nameId = node.expression as ts.Identifier
968
        const name = ts.idText(nameId)
969

970
        // Special handling for synthetic names
971
        if (this.callTable.lazyCalls.has(nameId)) return false
972

973
        if (isBuiltinComponentName(this.ctx, name) &&
974
            !hasLocalDeclaration(this.typechecker, nameId)
975
        ) return false
976

977
        return true
978
    }
979

980
    visitor(beforeChildren: ts.Node): ts.Node {
981
        const node = this.visitEachChild(beforeChildren)
982
        if (ts.isStructDeclaration(node)) {
983
            return this.translateStructToClass(node)
984
        } else if (ts.isClassDeclaration(node)) {
985
            return this.translateClass(node)
986
        } else if (isGlobalBuilder(node)) {
987
            return this.translateGlobalBuilder(node as ts.FunctionDeclaration)
988
        } else if (ts.isEtsComponentExpression(node)) {
989
            if (this.isUserEts(node)) {
990
                return this.translateUserEtsComponent(node)
991
            } else {
992
                return this.translateBuiltinEtsComponent(node)
993
            }
994
        } else if (ts.isImportDeclaration(node)) {
995
            const newNode = this.translateImportDeclaration(node)
996
            return this.visitEachChild(newNode)
997
        } else if (ts.isCallExpression(node) && isBuilderLambdaCall(this.callTable, node)) {
998
            return this.transformBuilderLambdaCall(node as ts.CallExpression)
999
        } else if (ts.isCallExpression(node) && isStructCall(this.callTable, node)) {
1000
            return this.transformStructCall(node as ts.CallExpression)
1001
        } else if (ts.isCallExpression(node)) {
1002
            return this.fixEmptyTypeArgs(node)
1003
        } else if (ts.isPropertyAssignment(node) && this.isDollarFieldAssignment(node)) {
1004
            return this.translateDollarFieldAssignment(node)
1005
        } else if (ts.isSourceFile(node)) {
1006
            return this.appendTopLevelMemoFunctions(node)
1007
        }
1008
        return node
1009
    }
1010
}
1011

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

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

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

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