idlize

Форк
0
/
PropertyTranslators.ts 
603 строки · 20.9 Кб
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 {
18
    Any,
19
    assignment,
20
    createThisFieldAccess,
21
    dropReadonly,
22
    Exclamation,
23
    findDecoratorArgument,
24
    id,
25
    orUndefined,
26
    parameter,
27
    undefinedValue,
28
} from "./ApiUtils"
29
import { Importer } from "./Importer"
30
import {
31
    backingField,
32
    BuilderParamDecorator,
33
    collect,
34
    ConsumeDecorator,
35
    createBlock,
36
    createNotNullAccessor,
37
    createNullableAccessor,
38
    createNullishCoalescing,
39
    createValueAccessor,
40
    dropDecorators,
41
    filterDecorators,
42
    idTextOrError,
43
    initializers,
44
    isBuilderParam,
45
    isConsume,
46
    isLink,
47
    isLocalStorageLink,
48
    isLocalStorageProp,
49
    isObjectLink,
50
    isProp,
51
    isProvide,
52
    isState,
53
    isStorageLink,
54
    isStorageProp,
55
    LinkDecorator,
56
    LocalStorageLinkDecorator,
57
    LocalStoragePropDecorator,
58
    LocalStoragePropertyName,
59
    ObjectLinkDecorator,
60
    prependDoubleLineMemoComment,
61
    prependMemoComment,
62
    PropDecorator,
63
    ProvideDecorator,
64
    StateDecorator,
65
    StorageLinkDecorator,
66
    StoragePropDecorator,
67
    SyncedPropertyConstructor,
68
    WatchDecorator,
69
} from "./utils"
70

71
export abstract class PropertyTranslator {
72
    private cachedType: ts.TypeNode | undefined // do not analyze this.property.initializer every time
73
    constructor(protected property: ts.PropertyDeclaration, protected importer: Importer) { }
74

75
    get propertyName(): string {
76
        return idTextOrError(this.property.name)
77
    }
78
    get propertyType(): ts.TypeNode {
79
        let type = this.property.type ?? this.cachedType
80
        if (!type) this.cachedType = type = this.typeInference(this.property.initializer)
81
        return type
82
    }
83
    private typeInference(initializer?: ts.Expression): ts.TypeNode {
84
        // TODO: try to analyze this.property.initializer
85
        return Any()
86
    }
87

88
    abstract translateMember(): ts.ClassElement[]
89
    translateInitializer(): ts.Expression|undefined {
90
        return undefined
91
    }
92
    translateToInitialization(): ts.Statement | undefined {
93
        return undefined
94
    }
95
    translateToUpdate(): ts.Statement | undefined {
96
        return undefined
97
    }
98
    translateToBuildParameter(): ts.ParameterDeclaration | undefined {
99
        return undefined
100
    }
101

102
    createStateOf(type: ts.TypeNode | undefined, ...initializer: ts.Expression[]): ts.Expression {
103
        return createStateOf(this.importer, type, ...initializer)
104
    }
105

106
    translateStateMember(
107
        property: ts.PropertyDeclaration,
108
        decoratorName: string,
109
        initializer: ts.Expression | undefined,
110
        type: ts.TypeNode | undefined,
111
        addExclamation: boolean
112
    ): ts.ClassElement[] {
113

114
        const originalName = idTextOrError(property.name)
115
        const newName = backingField(originalName)
116

117
        const field = ts.factory.updatePropertyDeclaration(
118
            property,
119
            dropDecorators(property.modifiers, decoratorName, WatchDecorator),
120
            newName,
121
            addExclamation ? Exclamation() : undefined,
122
            type,
123
            initializer
124
        )
125

126
        const getter = this.createStateGetter(originalName, newName)
127
        const setter = this.createStateSetter(originalName, newName)
128

129
        return [field, getter, setter]
130
    }
131

132
    translatePlainMember(
133
        property: ts.PropertyDeclaration,
134
        initializer: ts.Expression | undefined,
135
        type: ts.TypeNode | undefined,
136
        decorator?: string,
137
        memo?: boolean
138
    ): ts.ClassElement[] {
139
        const originalName = idTextOrError(property.name)
140
        const newName = backingField(originalName)
141

142
        const field = ts.factory.updatePropertyDeclaration(
143
            property,
144
            dropReadonly(dropDecorators(property.modifiers, decorator)),
145
            newName,
146
            initializer ? undefined : ts.factory.createToken(ts.SyntaxKind.ExclamationToken),
147
            type,
148
            initializer
149
        )
150

151
        const getter = this.createPlainGetter(originalName, newName, type, memo)
152
        const setter = this.createPlainSetter(originalName, newName, type, memo)
153

154
        const backingFieldWithMemo = memo ? prependMemoComment(field) : field
155

156
        // TODO: don't produce any setters for readonly properties
157
        return [backingFieldWithMemo, getter, setter]
158
    }
159

160
    mutableState(type: ts.TypeNode,): ts.TypeNode {
161
        return ts.factory.createTypeReferenceNode(
162
            id(this.importer.withRuntimeImport("MutableState")),
163
            [type]
164
        )
165
    }
166

167
    mutableStateOrUndefined(type: ts.TypeNode, importer: Importer): ts.TypeNode {
168
        return orUndefined(this.mutableState(type))
169
    }
170

171

172
    translateStateWithoutInitializer(
173
        property: ts.PropertyDeclaration,
174
        decoratorName: string,
175
        syncedProperty: boolean = false,
176
    ): ts.ClassElement[] {
177
        return this.translateStateMember(
178
            property,
179
            decoratorName,
180
            undefined,
181
            ts.factory.createTypeReferenceNode(
182
                syncedProperty
183
                    ? this.importer.withAdaptorImport("SyncedProperty")
184
                    : this.importer.withRuntimeImport("MutableState"),
185
                [
186
                    this.propertyType
187
                ]
188
            ),
189
            true
190
        )
191
    }
192

193
    translateStateWithInitializer(
194
        property: ts.PropertyDeclaration,
195
        decorator: string,
196
        initializer: ts.Expression,
197
    ): ts.ClassElement[] {
198
        return this.translateStateMember(property, decorator, initializer, undefined, false)
199
    }
200

201
    protected createAppStorageState(decoratorName: string): ts.Expression {
202
        return ts.factory.createCallExpression(
203
            id(this.importer.withAdaptorImport("AppStorageLinkState")),
204
            [this.propertyType],
205
            [
206
                findDecoratorArgument(filterDecorators(this.property), decoratorName, 0),
207
                this.property.initializer!
208
            ]
209
        )
210
    }
211

212
    protected createLocalStorageState(decoratorName: string): ts.Expression {
213
        return ts.factory.createCallExpression(
214
            id(this.importer.withAdaptorImport("StorageLinkState")),
215
            [this.propertyType],
216
            [
217
                createThisFieldAccess(LocalStoragePropertyName),
218
                findDecoratorArgument(filterDecorators(this.property), decoratorName, 0),
219
                this.property.initializer!
220
            ]
221
        )
222
    }
223

224
    private createStateGetter(originalName: string, newName: string): ts.GetAccessorDeclaration {
225
        return ts.factory.createGetAccessorDeclaration(
226
            undefined,
227
            id(originalName),
228
            [],
229
            this.propertyType,
230
            createBlock(
231
                    ts.factory.createReturnStatement(
232
                        createValueAccessor(
233
                            // TODO: issue a message if there is no @Provide.
234
                            ts.factory.createNonNullExpression(
235
                                createThisFieldAccess(newName)
236
                            )
237
                        )
238
                    )
239
            )
240
        )
241

242
    }
243

244
    private createStateSetter(originalName: string, newName: string, postStatements?: ts.Statement[]) {
245
        const preStatement =
246
            (postStatements?.length ?? 0) > 0 ?
247
                ts.factory.createVariableStatement(
248
                    undefined,
249
                    ts.factory.createVariableDeclarationList(
250
                        [
251
                            ts.factory.createVariableDeclaration(
252
                                id("oldValue"),
253
                                undefined,
254
                                undefined,
255
                                createValueAccessor(
256
                                    ts.factory.createNonNullExpression(
257
                                        createThisFieldAccess(newName)
258
                                    )
259
                                )
260
                            )
261
                        ],
262
                        ts.NodeFlags.Const
263
                    )
264
                )
265
                : undefined
266

267
        return ts.factory.createSetAccessorDeclaration(
268
            undefined,
269
            id(originalName),
270
            [
271
                parameter(
272
                    "value",
273
                    this.propertyType
274
                )
275
            ],
276
            ts.factory.createBlock(
277
                collect(
278
                    preStatement,
279
                    assignment(
280
                        createValueAccessor(
281
                            // TODO: issue a message if there is no @Provide.
282
                            ts.factory.createNonNullExpression(
283
                                createThisFieldAccess(newName)
284
                            )
285
                        ),
286
                        ts.factory.createCallExpression(
287
                            id(this.importer.withAdaptorImport("observableProxy")),
288
                            undefined,
289
                            [
290
                                id("value"),
291
                            ]
292
                        )
293
                    ),
294
                    postStatements
295
                ),
296
                true
297
            )
298
        )
299
    }
300

301
    createPlainGetter(originalName: string, newName: string, type: ts.TypeNode | undefined, memo?: boolean): ts.GetAccessorDeclaration {
302
        const getter = ts.factory.createGetAccessorDeclaration(
303
            undefined,
304
            id(originalName),
305
            [],
306
            this.propertyType,
307
            createBlock(
308
                    ts.factory.createReturnStatement(
309
                        createThisFieldAccess(newName)
310
                    )
311
            )
312
        )
313
        return memo ? prependMemoComment(getter) : getter
314
    }
315

316
    createPlainSetter(originalName: string, newName: string, type: ts.TypeNode | undefined, memo?: boolean) {
317
        const param = parameter(
318
            id("value"),
319
            type
320
        )
321

322
        return ts.factory.createSetAccessorDeclaration(
323
            undefined,
324
            id(originalName),
325
            [
326
                memo ? prependDoubleLineMemoComment(param) : param
327
            ],
328
            createBlock(assignToField(newName, id("value")))
329
        )
330
    }
331

332
    translateInitializerOfSyncedProperty(constructorName: SyncedPropertyConstructor, withValue?: ts.Expression): ts.Expression {
333
        return ts.factory.createCallExpression(
334
            id(this.importer.withAdaptorImport(constructorName)),
335
            this.property.type ? [this.property.type] : undefined,
336
            withValue ? [withValue] : []
337
        )
338
    }
339

340
    translateToUpdateSyncedProperty(withValue: ts.Expression): ts.Statement {
341
        return ts.factory.createExpressionStatement(
342
            ts.factory.createCallExpression(
343
                ts.factory.createPropertyAccessExpression(
344
                    createThisFieldAccess(backingField(this.propertyName)),
345
                    "update"
346
                ),
347
                undefined,
348
                [withValue]
349
            )
350
        )
351
    }
352
}
353

354

355
function createStateOf(importer: Importer, type: ts.TypeNode | undefined, ...initializer: ts.Expression[]): ts.Expression {
356
    return ts.factory.createCallExpression(
357
        id(importer.withAdaptorImport("stateOf")),
358
        type ? [type] : undefined,
359
        initializer
360
    )
361
}
362

363
class State extends PropertyTranslator {
364
    translateMember(): ts.ClassElement[] {
365
        return this.translateStateWithoutInitializer(this.property, StateDecorator)
366
    }
367

368
    translateToInitialization(): ts.Statement {
369
        const name = this.propertyName
370
        return assignToBackingField(
371
            name,
372
            this.createStateOf(
373
                this.propertyType,
374
                createNullishCoalescing(
375
                    createNullableAccessor(initializers(), name),
376
                    this.property.initializer!
377
                ),
378
                ts.factory.createThis())
379
        )
380
    }
381
}
382

383
export class ClassState extends PropertyTranslator {
384
    translateMember(): ts.ClassElement[] {
385
        // special case: @State in any class other than struct
386
        return this.translateStateWithInitializer(this.property, StateDecorator, this.createStateOf(
387
            this.property.type,
388
            this.property.initializer!,
389
            undefinedValue(),
390
            undefinedValue()))
391
    }
392
}
393

394
class Prop extends PropertyTranslator {
395
    translateMember(): ts.ClassElement[] {
396
        return this.translateStateWithoutInitializer(this.property, PropDecorator, true)
397
    }
398
    override translateToInitialization(): ts.Statement {
399
        return assignToBackingField(
400
            this.propertyName,
401
            this.translateInitializerOfSyncedProperty(SyncedPropertyConstructor.propState, this.property.initializer))
402
    }
403
    translateToUpdate(): ts.Statement {
404
        return this.translateToUpdateSyncedProperty(createNullableAccessor(initializers(), this.propertyName))
405
    }
406
    translateToBuildParameter(): ts.ParameterDeclaration {
407
        return translatePropOrObjectLinkToBuildParameter(this.property, this.importer)
408
    }
409
}
410

411
class Link extends PropertyTranslator {
412
    translateMember(): ts.ClassElement[] {
413
        return this.translateStateWithoutInitializer(this.property, LinkDecorator)
414
    }
415
    translateToInitialization(): ts.Statement {
416
        return translateToInitializationFromInitializers(backingField(this.propertyName))
417
    }
418
}
419

420
class ObjectLink extends PropertyTranslator {
421
    translateMember(): ts.ClassElement[] {
422
        return this.translateStateWithoutInitializer(this.property, ObjectLinkDecorator, true)
423
    }
424
    override translateToInitialization(): ts.Statement {
425
        return assignToBackingField(
426
            this.propertyName,
427
            this.translateInitializerOfSyncedProperty(SyncedPropertyConstructor.objectLink, this.property.initializer))
428
    }
429
    translateToUpdate(): ts.Statement {
430
        return this.translateToUpdateSyncedProperty(createNullableAccessor(initializers(), this.propertyName))
431
    }
432
    translateToBuildParameter(): ts.ParameterDeclaration {
433
        return translatePropOrObjectLinkToBuildParameter(this.property, this.importer)
434
    }
435
}
436

437
class Provide extends PropertyTranslator {
438
    translateMember(): ts.ClassElement[] {
439
        return this.translateStateWithoutInitializer(this.property, ProvideDecorator)
440
    }
441
    translateToInitialization() {
442
        return translateToInitializationFromInitializers(backingField(this.propertyName))
443
    }
444
}
445

446
class Consume extends PropertyTranslator {
447
    translateMember(): ts.ClassElement[] {
448
        return this.translateStateWithoutInitializer(this.property, ConsumeDecorator)
449
    }
450
    translateToInitialization() {
451
        return translateToInitializationFromInitializers(backingField(this.propertyName))
452
    }
453
}
454

455
class StorageLink extends PropertyTranslator {
456
    translateMember(): ts.ClassElement[] {
457
        return this.translateStateWithoutInitializer(this.property, StorageLinkDecorator)
458
    }
459

460
    translateToInitialization(): ts.Statement {
461
        return assignToBackingField(this.propertyName, this.createAppStorageState(StorageLinkDecorator))
462
    }
463

464
}
465
class LocalStorageLink extends PropertyTranslator {
466
    translateMember(): ts.ClassElement[] {
467
        return this.translateStateWithoutInitializer(this.property, LocalStorageLinkDecorator)
468
    }
469

470
    translateToInitialization(): ts.Statement {
471
        return assignToBackingField(this.propertyName, this.createLocalStorageState(LocalStorageLinkDecorator))
472
    }
473

474
}
475
class StorageProp extends PropertyTranslator {
476
    translateMember(): ts.ClassElement[] {
477
        return this.translateStateWithoutInitializer(this.property, StoragePropDecorator, true)
478
    }
479
    override translateToInitialization(): ts.Statement {
480
        return assignToBackingField(
481
            this.propertyName,
482
            this.translateInitializerOfSyncedProperty(SyncedPropertyConstructor.propState, this.createAppStorageValue()))
483
    }
484
    translateToUpdate(): ts.Statement {
485
        return this.translateToUpdateSyncedProperty(this.createAppStorageValue())
486
    }
487
    createAppStorageValue(): ts.Expression {
488
        return createValueAccessor(this.createAppStorageState(StoragePropDecorator))
489
    }
490

491
}
492
class LocalStorageProp extends PropertyTranslator {
493
    translateMember(): ts.ClassElement[] {
494
        return this.translateStateWithoutInitializer(this.property, LocalStoragePropDecorator, true)
495
    }
496
    override translateToInitialization(): ts.Statement {
497
        return assignToBackingField(
498
            this.propertyName,
499
            this.translateInitializerOfSyncedProperty(SyncedPropertyConstructor.propState, this.createLocalStorageValue()))
500
    }
501
    translateToUpdate(): ts.Statement {
502
        return this.translateToUpdateSyncedProperty(this.createLocalStorageValue())
503
    }
504
    createLocalStorageValue(): ts.Expression {
505
        return createValueAccessor(this.createLocalStorageState(LocalStoragePropDecorator))
506
    }
507
}
508

509
export class BuilderParam extends PropertyTranslator {
510
    constructor(protected property: ts.PropertyDeclaration, protected importer: Importer, private typechecker: ts.TypeChecker) {
511
        super(property, importer)
512
    }
513
    translateMember(): ts.ClassElement[] {
514
        return this.translatePlainMember(
515
            this.property,
516
            undefined,
517
            this.propertyType,
518
            BuilderParamDecorator,
519
            /* memo = */ true
520
        )
521
    }
522
    translateToInitialization(): ts.Statement | undefined {
523
        return initializePlainProperty(this.propertyName, this.property.initializer)
524
    }
525
}
526

527
class PlainProperty extends PropertyTranslator {
528
    constructor(protected property: ts.PropertyDeclaration, protected importer: Importer, private typechecker: ts.TypeChecker) {
529
        super(property, importer)
530
    }
531
    translateMember(): ts.ClassElement[] {
532
        return this.translatePlainMember(
533
            this.property,
534
            undefined,
535
            this.propertyType
536
        )
537
    }
538
    translateToInitialization(): ts.Statement | undefined {
539
        return initializePlainProperty(this.propertyName, this.property.initializer)
540
    }
541
}
542

543
function initializePlainProperty(name: string, initializer?: ts.Expression): ts.Statement {
544
    return initializer
545
        ? assignToBackingField(name, createNullishCoalescing(createNullableAccessor(initializers(), name), initializer))
546
        : ts.factory.createIfStatement(
547
            createNullableAccessor(initializers(), name),
548
            createBlock(assignToBackingField(name, createNullableAccessor(initializers(), name)))
549
        )
550
}
551

552
function assignToBackingField(name: string, expression: ts.Expression): ts.Statement {
553
    return assignToField(backingField(name), expression)
554
}
555

556
function assignToField(name: string, expression: ts.Expression): ts.Statement {
557
    return assignment(createThisFieldAccess(name), expression)
558
}
559

560
function translateToInitializationFromInitializers(name: string): ts.Statement {
561
    return assignToField(name, createNotNullAccessor(initializers(), name))
562
}
563

564
function translatePropOrObjectLinkToBuildParameter(property: ts.PropertyDeclaration, importer: Importer): ts.ParameterDeclaration {
565
    return parameter(
566
        backingField(idTextOrError(property.name)),
567
        property.type
568
    )
569
}
570

571
export function haveProvidesOrConsumes(translators: PropertyTranslator[]): boolean {
572
    const providesOrConsumes = translators.filter(it => it instanceof Provide || it instanceof Consume)
573
    return providesOrConsumes.length > 0
574
}
575

576
export function classifyProperty(member: ts.ClassElement, importer: Importer, typechecker: ts.TypeChecker): PropertyTranslator | undefined {
577
    if (!ts.isPropertyDeclaration(member)) return undefined
578
    if (isState(member)) {
579
        return new State(member, importer)
580
    } else if (isStorageProp(member)) {
581
        return new StorageProp(member, importer)
582
    } else if (isStorageLink(member)) {
583
        return new StorageLink(member, importer)
584
    } else if (isLocalStorageLink(member)) {
585
        return new LocalStorageLink(member, importer)
586
    } else if (isLocalStorageProp(member)) {
587
        return new LocalStorageProp(member, importer)
588
    } else if (isLink(member)) {
589
        return new Link(member, importer)
590
    } else if (isProp(member)) {
591
        return new Prop(member, importer)
592
    } else if (isObjectLink(member)) {
593
        return new ObjectLink(member, importer)
594
    } else if (isProvide(member)) {
595
        return new Provide(member, importer)
596
    } else if (isConsume(member)) {
597
        return new Consume(member, importer)
598
    } else if (isBuilderParam(member)) {
599
        return new BuilderParam(member, importer, typechecker)
600
    } else {
601
        return new PlainProperty(member, importer, typechecker)
602
    }
603
}
604

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

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

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

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