idlize
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
16import * as ts from 'ohos-typescript'
17import { AbstractVisitor } from './AbstractVisitor'
18import { CallExpressionCollector } from './CallExpressionCollector'
19import { EntryTracker } from './EntryTracker'
20import { Importer, isOhosImport } from './Importer'
21import { 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'
22import { BuilderParam, classifyProperty, ClassState, PropertyTranslator } from './PropertyTranslators'
23import { Any, assignment, createThisFieldAccess, Export, findDecoratorArguments, findDecoratorLiterals, getDeclarationsByNode, hasDecorator, id, isKnownIdentifier, isUndefined, optionalParameter, orUndefined, parameter, partial, Private, provideAnyTypeIfNone, Undefined, undefinedValue, Void } from './ApiUtils'
24
25function parameterNameIdentifier(name: ts.LeftHandSideExpression): ts.Identifier {
26if (ts.isIdentifier(name)) {
27const newName = adaptorEtsParameterName(name)
28return newName
29} else {
30throw new Error("expected ETS name to be an Identifier, got: " + ts.SyntaxKind[name.kind])
31}
32}
33
34export class StructTransformer extends AbstractVisitor {
35private printer = ts.createPrinter({ removeComments: false })
36constructor(
37sourceFile: ts.SourceFile,
38ctx: ts.TransformationContext,
39public typechecker: ts.TypeChecker,
40public importer: Importer,
41public nameTable: NameTable,
42public entryTracker: EntryTracker,
43public callTable: CallTable
44) {
45super(sourceFile, ctx)
46this.importer.addAdaptorImport(adaptorComponentName("PageTransitionEnter"))
47this.importer.addAdaptorImport(adaptorComponentName("PageTransitionExit"))
48}
49
50dropImportEtsExtension(node: ts.ImportDeclaration, oldLiteral: string): ts.ImportDeclaration {
51if (!oldLiteral.endsWith(".ets")) return node
52const newLiteral = oldLiteral.substring(0, oldLiteral.length - 4)
53return ts.factory.updateImportDeclaration(
54node,
55node.modifiers,
56node.importClause,
57ts.factory.createStringLiteral(newLiteral),
58undefined
59)
60}
61
62translateImportDeclaration(node: ts.ImportDeclaration): ts.ImportDeclaration {
63const oldModuleSpecifier = node.moduleSpecifier
64if (!ts.isStringLiteral(oldModuleSpecifier)) return node
65const oldLiteral = oldModuleSpecifier.text
66
67if (isOhosImport(oldLiteral)) {
68const oldDefaultName = node.importClause!.name ? ts.idText(node.importClause!.name) : ""
69return this.importer.translateOhosImport(node, oldLiteral, oldDefaultName)
70}
71
72return this.dropImportEtsExtension(node, oldLiteral)
73}
74
75emitStartApplicationBody(name: string): ts.Block {
76return ts.factory.createBlock(
77[ts.factory.createReturnStatement(ts.factory.createCallExpression(
78id(name),
79undefined,
80[]
81))],
82true
83)
84}
85
86emitStartApplicationDeclaration(name: string): ts.Statement {
87const koalaEntry = ts.factory.createFunctionDeclaration(
88[],
89[Export()],
90undefined,
91id("KoalaEntry"),
92undefined,
93[],
94undefined,
95this.emitStartApplicationBody(name)
96)
97prependMemoComment(koalaEntry)
98return koalaEntry
99}
100
101entryCode: ts.Statement[] = []
102entryFile: string | undefined = undefined
103prepareEntryCode(name: string) {
104this.entryCode = [
105this.emitStartApplicationDeclaration(name),
106]
107this.entryFile = this.sourceFile.fileName
108}
109findContextLocalState(name: string, type: ts.TypeNode | undefined): ts.Expression {
110const state = ts.factory.createCallExpression(
111id(this.importer.withRuntimeImport("contextLocal")),
112type ? [type] : undefined,
113[ts.factory.createStringLiteral(name)]
114)
115return ts.factory.createAsExpression(
116state,
117ts.factory.createTypeReferenceNode(
118id("MutableState"),
119[type!]
120)
121)
122}
123
124createConsumeState(consume: ts.PropertyDeclaration): ts.Statement {
125if (!ts.isIdentifier(consume.name) && !ts.isPrivateIdentifier(consume.name)) {
126throw new Error("Expected an identifier")
127}
128
129return ts.factory.createVariableStatement(
130undefined,
131ts.factory.createVariableDeclarationList([
132ts.factory.createVariableDeclaration(
133consumeVariableName(consume),
134undefined,
135undefined,
136this.findContextLocalState(deduceConsumeName(consume), consume.type)
137)
138],
139ts.NodeFlags.Const
140)
141)
142}
143
144createProvideState(provide: ts.PropertyDeclaration): ts.Statement {
145this.importer.addAdaptorImport("contextLocalStateOf")
146
147if (!ts.isIdentifier(provide.name) && !ts.isPrivateIdentifier(provide.name)) {
148throw new Error("Expected an identifier")
149}
150if (!provide.initializer) {
151throw new Error("Expected an initialization for @Provide " + deduceProvideName(provide))
152}
153return ts.factory.createVariableStatement(
154undefined,
155ts.factory.createVariableDeclarationList([
156ts.factory.createVariableDeclaration(
157provideVariableName(provide),
158undefined,
159undefined,
160contextLocalStateOf(deduceProvideName(provide), provide.initializer!, provide.type)
161)
162],
163ts.NodeFlags.Const
164)
165)
166}
167
168createBuildProlog(
169structNode: ts.StructDeclaration,
170members?: ts.NodeArray<ts.ClassElement>,
171propertyTranslators?: PropertyTranslator[],
172): ts.MethodDeclaration|undefined {
173
174const propertyInitializationProcessors = propertyTranslators ?
175filterDefined(propertyTranslators.map(it => it.translateToUpdate())) :
176undefined
177
178const watchHandlers = members ? this.translateWatchDecorators(members) : undefined
179
180if (!propertyInitializationProcessors?.length &&
181!watchHandlers?.length
182) return undefined
183
184const body = ts.factory.createBlock(
185collect(
186propertyInitializationProcessors,
187watchHandlers,
188),
189true
190)
191
192const className = adaptorClassName(structNode.name!)!
193
194const method = ts.factory.createMethodDeclaration(
195undefined,
196undefined,
197undefined,
198id(RewriteNames.UpdateStruct),
199undefined,
200undefined,
201[
202parameter(initializers(), orUndefined(partial(className)))
203],
204Void(),
205body
206)
207
208return prependMemoComment(method)
209}
210
211private createThisMethodCall(name: string | ts.Identifier | ts.PrivateIdentifier, args?: ReadonlyArray<ts.Expression>): ts.Expression {
212return ts.factory.createCallChain(
213createThisFieldAccess(name),
214undefined,
215undefined,
216args
217)
218}
219
220private createWatchCall(name: string): ts.Statement {
221return ts.factory.createExpressionStatement(this.createThisMethodCall(name))
222}
223
224translateWatchDecorators(members?: ts.NodeArray<ts.ClassElement>): ts.Statement[] {
225const statements: ts.Statement[] = []
226if (members && members.length) {
227for (const property of members) {
228if (ts.isPropertyDeclaration(property)) {
229if (ts.isIdentifier(property.name) || ts.isPrivateIdentifier(property.name)) {
230const name = ts.idText(property.name)
231const watches = findDecoratorLiterals(filterDecorators(property), WatchDecorator, 0)
232if (watches && watches.length) statements.push(
233ts.factory.createExpressionStatement(
234ts.factory.createCallExpression(
235id(this.importer.withRuntimeImport("OnChange")),
236undefined,
237[
238createThisFieldAccess(name),
239ts.factory.createArrowFunction(
240undefined,
241undefined,
242[],
243undefined,
244ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
245watches.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}
257return statements
258}
259
260translateBuilder(structure: ts.StructDeclaration, propertyTranslators: PropertyTranslator[], member: ts.ClassElement, isMainBuild: boolean): ts.MethodDeclaration {
261if (!ts.isMethodDeclaration(member)) {
262throw new Error("Expected member declaration, got: " + ts.SyntaxKind[member.kind])
263}
264
265const className = adaptorClassName(structure.name!)!
266
267const stateParameters = isMainBuild ? [
268prependDoubleLineMemoComment(
269parameter(
270"builder",
271orUndefined(
272ts.factory.createFunctionTypeNode(
273undefined,
274[
275parameter(
276id("instance"),
277ts.factory.createTypeReferenceNode(className),
278)
279],
280Void()
281)
282)
283)
284)
285] : []
286
287const newMethod = ts.factory.updateMethodDeclaration(
288member,
289dropBuilder(member.modifiers),
290member.asteriskToken,
291mangleIfBuild(member.name),
292member.questionToken,
293member.typeParameters,
294[
295...stateParameters,
296...member.parameters
297],
298member.type,
299member.body
300)
301return prependMemoComment(newMethod)
302}
303
304translateGlobalBuilder(func: ts.FunctionDeclaration): ts.FunctionDeclaration {
305const newFunction = ts.factory.createFunctionDeclaration(
306dropBuilder(func.modifiers),
307func.asteriskToken,
308func.name,
309func.typeParameters,
310func.parameters.map(it => provideAnyTypeIfNone(it)),
311func.type,
312func.body
313)
314return prependMemoComment(newFunction)
315}
316
317translateMemberFunction(method: ts.MethodDeclaration): ts.MethodDeclaration {
318// TODO: nothing for now?
319return method
320}
321
322translateStructMembers(structNode: ts.StructDeclaration, propertyTranslators: PropertyTranslator[]): ts.ClassElement[] {
323const propertyMembers = propertyTranslators.map(translator =>
324translator.translateMember()
325
326)
327const updateStruct = this.createBuildProlog(structNode, structNode.members, propertyTranslators)
328
329// The rest of the struct members are translated here directly.
330const restMembers = structNode.members.map(member => {
331if (isKnownIdentifier(member.name, "build")) {
332return this.translateBuilder(structNode, propertyTranslators, member, true)
333} else if (hasDecorator(member, "Builder")) {
334return this.translateBuilder(structNode, propertyTranslators, member, false)
335} else if (isKnownIdentifier(member.name, "pageTransition")) {
336return prependMemoComment(member)
337} else if (ts.isMethodDeclaration(member)) {
338return this.translateMemberFunction(member)
339}
340return []
341}).flat()
342return collect(
343...propertyMembers,
344updateStruct,
345...restMembers
346)
347}
348
349translateComponentName(name: ts.Identifier | undefined): ts.Identifier | undefined {
350if (!name) return undefined
351// return id(adaptorName(ts.idText(name)))
352return 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*/
359contextLocalStateMap(name: string): ts.Statement {
360this.importer.addAdaptorImport("contextLocalStateOf")
361return ts.factory.createExpressionStatement(contextLocalStateOf(name, ts.factory.createNewExpression(
362id("Map"),
363undefined,
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*/
372collectContextLocals(source: ts.Node): ts.Statement[] {
373const statements: ts.Statement[] = []
374const collector = new CallExpressionCollector(this.sourceFile, this.ctx,
375"ArkRadio",
376"ArkCheckbox",
377"ArkCheckboxGroup",
378)
379collector.visitor(source)
380if (collector.isVisited("ArkRadio")) {
381statements.push(this.contextLocalStateMap("contextLocalMapOfRadioGroups"))
382}
383if (collector.isVisited("ArkCheckbox") || collector.isVisited("ArkCheckboxGroup")) {
384statements.push(this.contextLocalStateMap("contextLocalMapOfCheckboxGroups"))
385}
386return statements
387}
388
389topLevelMemoFunctions: ts.FunctionDeclaration[] = []
390topLevelInitialization: ts.Statement[] = []
391
392createTopLevelMemo(node: ts.StructDeclaration, impl: boolean): ts.FunctionDeclaration {
393const className = this.translateComponentName(adaptorClassName(node.name))!
394const functionName = impl ? customDialogImplName(node.name) : node.name
395
396const factory = ts.factory.createArrowFunction(
397undefined,
398undefined,
399[],
400undefined,
401ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
402ts.factory.createNewExpression(
403className,
404undefined,
405undefined
406)
407)
408
409const provideVariables = filterProvides(node.members).map(it => this.createProvideState(it))
410const consumeVariables = filterConsumes(node.members).map(it => this.createConsumeState(it))
411const contextLocals = this.collectContextLocals(node)
412
413const additionalStatements = [
414...provideVariables,
415...consumeVariables,
416...contextLocals,
417]
418
419const provides = filterProvides(node.members).map(it => createProvideInitializerField(it))
420const consumes = filterConsumes(node.members).map(it => createConsumeInitializerField(it))
421const updatedInitializers = (provides.length != 0 || consumes.length != 0) ?
422ts.factory.createObjectLiteralExpression(
423[
424...provides,
425...consumes,
426ts.factory.createSpreadAssignment(initializers()),
427],
428true
429) :
430initializers()
431
432
433const callInstantiate = ts.factory.createReturnStatement(
434ts.factory.createCallExpression(
435ts.factory.createPropertyAccessExpression(
436className,
437id("_instantiate")
438),
439undefined,
440[
441impl ? undefinedValue() : id("style"),
442factory,
443impl ? undefinedValue() : id("content"),
444updatedInitializers
445]
446))
447
448const memoFunction = ts.factory.createFunctionDeclaration(
449[Export()],
450undefined,
451functionName,
452undefined,
453impl ? [
454optionalParameter(
455initializers(),
456partial(className),
457)
458]:
459[
460optionalParameter(
461"style",
462Any(),
463),
464prependDoubleLineMemoComment(
465optionalParameter(
466"content",
467voidLambdaType(),
468)
469),
470optionalParameter(
471initializers(),
472partial(className),
473)
474],
475ts.factory.createTypeReferenceNode(className),
476ts.factory.createBlock(
477[
478...additionalStatements,
479callInstantiate
480],
481true
482)
483)
484
485return prependMemoComment(memoFunction)
486}
487
488/*
489Creates something like:
490export function DialogExample(initializer: any = {}) {
491return { build: bindCustomDialog(DialogExampleImpl, initializer), buildOptions: initializer };
492}
493*/
494createCustomDialogConstructor(node: ts.StructDeclaration) {
495return ts.factory.createFunctionDeclaration(
496undefined,
497[Export()],
498undefined,
499node.name,
500undefined,
501[
502parameter(
503id("initializer"),
504Any(),
505ts.factory.createObjectLiteralExpression()
506)
507],
508undefined,
509ts.factory.createBlock(
510[
511ts.factory.createReturnStatement(
512ts.factory.createObjectLiteralExpression([
513ts.factory.createPropertyAssignment("build",
514ts.factory.createCallExpression(
515id(this.importer.withAdaptorImport("bindCustomDialog")),
516undefined,
517[
518customDialogImplName(node.name)!,
519id("initializer")
520]
521)
522),
523ts.factory.createPropertyAssignment("buildOptions",
524id("initializer"))
525])
526)
527],
528true
529)
530)
531
532}
533
534createInitializerMethod(
535className: ts.Identifier,
536propertyTranslators: PropertyTranslator[]
537): ts.MethodDeclaration {
538const parameters = [
539prependDoubleLineMemoComment(
540optionalParameter(
541"content",
542voidLambdaType(),
543)
544),
545optionalParameter(
546initializers(),
547partial(className),
548)
549]
550const initializations = filterDefined(
551propertyTranslators.map(it => it.translateToInitialization())
552)
553const buildParams = propertyTranslators.filter(it => it instanceof BuilderParam)
554if (buildParams.length > 0) {
555const field = createThisFieldAccess(backingField(buildParams[0].propertyName))
556initializations.push(ts.factory.createIfStatement(
557ts.factory.createBinaryExpression(
558ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, field),
559ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
560id("content")
561),
562assignment(field, id("content"))
563))
564}
565
566return ts.factory.createMethodDeclaration(
567undefined,
568undefined,
569RewriteNames.InitializeStruct,
570undefined,
571undefined,
572parameters,
573Void(),
574ts.factory.createBlock(initializations, true)
575)
576}
577
578createTopLevelInitialization(node: ts.StructDeclaration): ts.ExpressionStatement {
579const routerPage = this.entryTracker.sourceFileToRoute(this.sourceFile)
580return ts.factory.createExpressionStatement(
581ts.factory.createCallExpression(
582id(this.importer.withOhosImport("ohos.router", "registerArkuiEntry")),
583undefined,
584[
585id(ts.idText(node.name!)),
586ts.factory.createStringLiteral(routerPage),
587]
588)
589)
590}
591
592createEntryPointAlias(node: ts.StructDeclaration): ts.Statement {
593return ts.factory.createVariableStatement(
594[Export()],
595ts.factory.createVariableDeclarationList(
596[
597ts.factory.createVariableDeclaration(
598id("__Entry"),
599undefined,
600undefined,
601id(ts.idText(node.name!)
602)
603)
604],
605ts.NodeFlags.Const
606)
607)
608}
609
610translateStructToClass(node: ts.StructDeclaration): ts.ClassDeclaration {
611const className = this.translateComponentName(adaptorClassName(node.name)) // TODO make me string for proper reuse
612const baseClassName = this.importer.withAdaptorImport("ArkStructBase")
613
614let entryLocalStorage: ts.Expression | undefined = undefined
615
616if (hasDecorator(node, "CustomDialog")) {
617this.topLevelMemoFunctions.push(
618this.createTopLevelMemo(node, true),
619this.createCustomDialogConstructor(node)
620)
621} else {
622this.topLevelMemoFunctions.push(
623this.createTopLevelMemo(node, false)
624)
625}
626
627if (hasDecorator(node, EntryDecorator)) {
628this.topLevelInitialization.push(
629this.createTopLevelInitialization(node),
630this.createEntryPointAlias(node)
631)
632const args = findDecoratorArguments(filterDecorators(node), EntryDecorator, 0)
633switch (args?.length) {
634case 0:
635break
636case 1:
637entryLocalStorage = args[0]
638break
639default:
640throw new Error("Entry must have only one name, but got " + args?.length)
641}
642
643if (!node.name) throw new Error("Expected @Entry struct to have a name")
644this.entryTracker.addEntry(ts.idText(node.name), this.sourceFile)
645}
646
647const inheritance = ts.factory.createHeritageClause(
648ts.SyntaxKind.ExtendsKeyword,
649[ts.factory.createExpressionWithTypeArguments(
650id(baseClassName),
651[
652ts.factory.createTypeReferenceNode(
653adaptorComponentName(ts.idText(node.name!)),
654)
655]
656)]
657)
658
659const entryLocalStorageProperty = ts.factory.createPropertyDeclaration(
660undefined,
661[Private()],
662LocalStoragePropertyName,
663undefined,
664undefined,
665entryLocalStorage ?? ts.factory.createNewExpression(
666id(this.importer.withAdaptorImport("LocalStorage")),
667undefined,
668[]
669)
670)
671
672const propertyTranslators = filterDefined(
673node.members.map(it => classifyProperty(it, this.importer, this.typechecker))
674)
675
676const translatedMembers = this.translateStructMembers(node, propertyTranslators)
677
678const createdClass = ts.factory.createClassDeclaration(
679filterModifiers(node),
680className,
681node.typeParameters,
682[inheritance],
683[
684entryLocalStorageProperty,
685this.createInitializerMethod(className!, propertyTranslators),
686...translatedMembers,
687]
688)
689
690return createdClass
691}
692
693findEtsAdaptorName(name: ts.LeftHandSideExpression): ts.LeftHandSideExpression {
694if (ts.isIdentifier(name)) {
695const newName = adaptorEtsName(name)
696this.importer.addAdaptorImport(ts.idText(newName))
697return newName
698} else {
699return name
700}
701}
702
703findEtsAdaptorClassName(name: ts.LeftHandSideExpression): ts.Identifier {
704if (ts.isIdentifier(name)) {
705const newName = adaptorClassName(name)!
706this.importer.addAdaptorImport(ts.idText(newName))
707return newName
708} else {
709throw new Error("expected ETS name to be an Identifier, got: " + ts.SyntaxKind[name.kind])
710}
711}
712
713createContentLambda(node: ts.EtsComponentExpression): ts.Expression {
714if (!node.body?.statements || node.body.statements.length == 0) {
715return undefinedValue()
716}
717
718const contentLambdaBody = ts.factory.createBlock(
719node.body?.statements,
720true
721)
722
723return ts.factory.createArrowFunction(
724undefined,
725undefined,
726[],
727undefined,
728ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
729contentLambdaBody
730)
731}
732
733createEtsInstanceLambda(node: ts.EtsComponentExpression): ts.Expression {
734// Either a lambda or undefined literal.
735const instanceArgument = node.arguments[0]
736
737if (isUndefined(instanceArgument)) {
738return instanceArgument
739}
740
741return this.createInstanceLambda(node, this.findEtsAdaptorClassName(node.expression))
742}
743
744createBuilderLambdaInstanceLambda(node: ts.EtsComponentExpression|ts.CallExpression, parameterTypeName?: ts.Identifier): ts.Expression {
745// Either a lambda or undefined literal.
746const instanceArgument = node.arguments[0]
747
748if (isUndefined(instanceArgument)) {
749return instanceArgument
750}
751
752return this.createInstanceLambda(node, undefined)
753}
754
755createInstanceLambda(node: ts.EtsComponentExpression|ts.CallExpression, parameterTypeName?: ts.Identifier): ts.Expression {
756// Either a lambda or undefined literal.
757const instanceArgument = node.arguments[0]
758
759if (isUndefined(instanceArgument)) {
760return instanceArgument
761}
762
763const parameterName = parameterNameIdentifier(node.expression)
764
765const lambdaParameter = parameter(
766parameterName,
767parameterTypeName ? ts.factory.createTypeReferenceNode(parameterTypeName) : undefined
768)
769
770const instanceLambdaBody = ts.factory.createBlock(
771[
772ts.factory.createExpressionStatement(instanceArgument)
773],
774true
775)
776
777return ts.factory.createArrowFunction(
778undefined,
779undefined,
780[lambdaParameter],
781undefined,
782ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
783instanceLambdaBody
784)
785}
786
787translateEtsComponent(node: ts.EtsComponentExpression, newName: ts.LeftHandSideExpression): ts.CallExpression {
788const newArguments = [
789this.createEtsInstanceLambda(node),
790this.createContentLambda(node),
791...node.arguments.slice(1)
792]
793
794return ts.factory.createCallExpression(
795newName,
796node.typeArguments,
797newArguments
798)
799}
800
801
802
803translateBuiltinEtsComponent(node: ts.EtsComponentExpression): ts.CallExpression {
804const newName = this.findEtsAdaptorName(node.expression)
805return this.translateEtsComponent(node, newName)
806}
807
808translateUserEtsComponent(node: ts.EtsComponentExpression): ts.CallExpression {
809return this.translateEtsComponent(node, node.expression)
810}
811
812transformBuilderLambdaCall(node: ts.CallExpression): ts.CallExpression {
813const originalCall = ts.getOriginalNode(node) as ts.CallExpression
814const newName = this.callTable.builderLambdas.get(originalCall)
815if (!newName) return node
816
817const newArguments = [
818this.createBuilderLambdaInstanceLambda(node),
819...node.arguments.slice(1)
820]
821
822return ts.factory.updateCallExpression(
823node,
824id(newName),
825node.typeArguments,
826newArguments
827)
828}
829
830transformStructCall(node: ts.CallExpression): ts.CallExpression {
831const newArguments = [
832undefinedValue(),
833undefinedValue(),
834...node.arguments
835]
836
837return ts.factory.updateCallExpression(
838node,
839//id(newName),
840node.expression,
841node.typeArguments,
842newArguments
843)
844}
845
846// This is a heuristics to understand if the given property call
847// is a style setting call.
848isStyleSettingMethodCall(node: ts.CallExpression): boolean {
849const property = node.expression
850if (!property || !ts.isPropertyAccessExpression(property)) return false
851const name = property.name
852if (!name || !ts.isIdentifier(name)) return false
853
854const declarations = getDeclarationsByNode(this.typechecker, name)
855
856// TODO: handle multiple declarations
857const declaration = declarations[0]
858
859if (!declaration || !ts.isMethodDeclaration(declaration)) return false
860const returnType = declaration.type
861if (!returnType || !ts.isTypeReferenceNode(returnType)) return false
862const returnTypeName = returnType.typeName
863if (!returnTypeName || !ts.isIdentifier(returnTypeName)) return false
864const parent = declaration.parent
865if (!parent || !ts.isClassDeclaration(parent)) return false
866const parentName = parent.name
867if (!parentName || !ts.isIdentifier(parentName)) return false
868const parentNameString = ts.idText(parentName)
869
870const ohosDeclaredClass =
871parentNameString.endsWith("Attribute") ||
872parentNameString == "CommonMethod"
873
874return ohosDeclaredClass
875}
876
877// TODO: Somehow eTS compiler produces style setting methods with a type parameter.
878fixEmptyTypeArgs(node: ts.CallExpression): ts.CallExpression {
879if (this.isStyleSettingMethodCall(node)) {
880return ts.factory.updateCallExpression(node, node.expression, undefined, node.arguments)
881}
882return node
883}
884
885importIfEnum(node: ts.PropertyAccessExpression): ts.PropertyAccessExpression {
886const name = node.expression
887if (!ts.isIdentifier(name)) return node
888const receiverDeclarations = getDeclarationsByNode(this.typechecker, node.expression)
889const anyDeclaration = receiverDeclarations[0]
890if (anyDeclaration && ts.isEnumDeclaration(anyDeclaration)) {
891this.importer.addAdaptorImport(ts.idText(name))
892}
893
894// Just return the node itself.
895return node
896}
897
898appendTopLevelMemoFunctions(file: ts.SourceFile): ts.SourceFile {
899return ts.factory.updateSourceFile(file,
900[...file.statements, ...this.topLevelMemoFunctions, ...this.topLevelInitialization],
901file.isDeclarationFile,
902file.referencedFiles,
903file.typeReferenceDirectives,
904file.hasNoDefaultLib,
905file.libReferenceDirectives
906)
907}
908
909isDollarFieldAccess(node: ts.Expression): boolean {
910if (!ts.isPropertyAccessExpression(node)) return false
911const name = node.name
912if (!name) return false
913if (!ts.isIdentifier(name)) return false
914
915const receiver = node.expression
916if (!receiver) return false
917if (receiver.kind != ts.SyntaxKind.ThisKeyword) return false
918
919const nameString = ts.idText(name)
920return nameString.startsWith("$")
921}
922
923translateDollarFieldAccess(node: ts.PropertyAccessExpression): ts.PropertyAccessExpression {
924return ts.factory.createPropertyAccessExpression(
925node.expression,
926backingField(ts.idText(node.name).substring(1))
927)
928}
929
930isDollarFieldAssignment(node: ts.PropertyAssignment): boolean {
931if (!ts.isPropertyAccessExpression(node.initializer)) return false
932return this.isDollarFieldAccess(node.initializer)
933}
934
935translateDollarFieldAssignment(node: ts.PropertyAssignment): ts.PropertyAssignment {
936if (!ts.isIdentifier(node.name)) return node
937
938const initializer = node.initializer
939if (this.isDollarFieldAccess(initializer)) {
940const newInitializer = this.translateDollarFieldAccess(initializer as ts.PropertyAccessExpression)
941return ts.factory.createPropertyAssignment(backingFieldName(node.name), newInitializer)
942}
943
944return node
945}
946
947translateClass(node: ts.ClassDeclaration): ts.ClassDeclaration {
948const newMembers = node.members.flatMap(it => {
949if (!ts.isPropertyDeclaration(it) || !isState(it)) {
950return it
951}
952return new ClassState(it, this.importer).translateMember()
953})
954
955const createdClass = ts.factory.updateClassDeclaration(
956node,
957node.modifiers,
958node.name,
959node.typeParameters,
960node.heritageClauses,
961newMembers
962)
963return createdClass
964}
965
966isUserEts(node: ts.EtsComponentExpression): boolean {
967const nameId = node.expression as ts.Identifier
968const name = ts.idText(nameId)
969
970// Special handling for synthetic names
971if (this.callTable.lazyCalls.has(nameId)) return false
972
973if (isBuiltinComponentName(this.ctx, name) &&
974!hasLocalDeclaration(this.typechecker, nameId)
975) return false
976
977return true
978}
979
980visitor(beforeChildren: ts.Node): ts.Node {
981const node = this.visitEachChild(beforeChildren)
982if (ts.isStructDeclaration(node)) {
983return this.translateStructToClass(node)
984} else if (ts.isClassDeclaration(node)) {
985return this.translateClass(node)
986} else if (isGlobalBuilder(node)) {
987return this.translateGlobalBuilder(node as ts.FunctionDeclaration)
988} else if (ts.isEtsComponentExpression(node)) {
989if (this.isUserEts(node)) {
990return this.translateUserEtsComponent(node)
991} else {
992return this.translateBuiltinEtsComponent(node)
993}
994} else if (ts.isImportDeclaration(node)) {
995const newNode = this.translateImportDeclaration(node)
996return this.visitEachChild(newNode)
997} else if (ts.isCallExpression(node) && isBuilderLambdaCall(this.callTable, node)) {
998return this.transformBuilderLambdaCall(node as ts.CallExpression)
999} else if (ts.isCallExpression(node) && isStructCall(this.callTable, node)) {
1000return this.transformStructCall(node as ts.CallExpression)
1001} else if (ts.isCallExpression(node)) {
1002return this.fixEmptyTypeArgs(node)
1003} else if (ts.isPropertyAssignment(node) && this.isDollarFieldAssignment(node)) {
1004return this.translateDollarFieldAssignment(node)
1005} else if (ts.isSourceFile(node)) {
1006return this.appendTopLevelMemoFunctions(node)
1007}
1008return node
1009}
1010}
1011