• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-2025 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 '@koalaui/ets-tsc'
17import * as path from 'path'
18import { getDeclarationsByNode, getDecorator, hasDecorator, id, isCallDecorator, isDecorator, prependComment, Void } from './ApiUtils'
19import { Importer } from "./Importer";
20
21export const ComponentDecorator = "Component"
22export const EntryDecorator = "Entry"
23export const ReusableDecorator = "Reusable"
24export const LocalStoragePropertyName = "_entry_local_storage_"
25
26export const StateDecorator = "State"
27export const PropDecorator = "Prop"
28export const LinkDecorator = "Link"
29export const ObjectLinkDecorator = "ObjectLink"
30export const ProvideDecorator = "Provide"
31export const ConsumeDecorator = "Consume"
32export const StorageLinkDecorator = "StorageLink"
33export const StoragePropDecorator = "StorageProp"
34export const LocalStorageLinkDecorator = "LocalStorageLink"
35export const LocalStoragePropDecorator = "LocalStorageProp"
36export const BuilderParamDecorator = "BuilderParam"
37export const BuilderDecorator = "Builder"
38export const BuilderLambdaDecorator = "BuilderLambda"
39export const WatchDecorator = "Watch"
40export const CustomDialogDecorator = "CustomDialog"
41
42export const StylesDecorator = "Styles"
43export const ExtendDecorator = "Extend"
44export const AnimatableExtendDecorator = "AnimatableExtend"
45export const ArkCommonMethodInterface = "ArkCommonMethodInterface"
46export const ArkCommonMethodComponent = "ArkCommonMethodComponent"
47export const T_TypeParameter = "T"
48export const styledInstance = mangle("instance")
49export const CommonInstance = "CommonInstance"
50export const Instance = "Instance"
51export const DollarDollar = "$$"
52
53export enum SyncedPropertyConstructor {
54    propState = "propState",
55    objectLink = "objectLinkState"
56}
57
58export enum StateImplementation {
59    SyncedProperty = "SyncedProperty",
60    MutableState = "MutableState",
61}
62
63export enum RewriteNames {
64    Initializers = "initializers",
65    InitializeStruct = "__initializeStruct",
66    UpdateStruct = "__updateStruct",
67    ToRecord = "__toRecord",
68    ApplyStyle = "__applyStyle",
69    ApplyAnimatableExtend = "__applyAnimatableExtend"
70}
71
72export class NameTable {
73    structs: string[] = []
74    dollars: string[] = []
75}
76
77export class IssueTable {
78    newEtsWithProperty: number[] = []
79    newEts: number[] = []
80    builderLambda: number[] = []
81}
82
83export class CallTable {
84    builderLambdas = new Map<ts.CallExpression, string>()
85    globalBuilderCalls = new Set<ts.CallExpression>()
86    legacyCalls = new Set<ts.CallExpression>()
87    structCalls = new Set<ts.CallExpression>()
88    lazyCalls = new Set<ts.Identifier>()
89}
90
91export function prependMemoComment<T extends ts.Node>(node: T): T {
92    return prependComment(node, "* @memo ")
93}
94
95export function prependMemoStable<T extends ts.Node>(node: T): T {
96    return prependComment(node, "* @memo:stable ")
97}
98
99export function prependMemoCommentIfArkoala<T extends ts.Node>(node: T, importer: Importer): T {
100    return importer.isArkoala()
101        ? prependMemoComment(node)
102        : node
103}
104
105export function prependDoubleLineMemoComment<T extends ts.Node>(node: T): T {
106    return prependComment(prependComment(node, ""), "* @memo ")
107}
108
109export function relativeToSource(sourcePath: string, sourceDir: string): string {
110    return path.relative(sourceDir, sourcePath)
111}
112
113export function emitStartupFile(entryFile: string, sourceDir: string, destinationDir: string) {
114    const absoluteSourceDir = path.resolve(sourceDir)
115    const absoluteEntryFile = path.resolve(entryFile)
116    const relativeEntryFile = path.relative(absoluteSourceDir, absoluteEntryFile)
117    if (!relativeEntryFile.endsWith(".ets")) return
118
119    const importPath = relativeEntryFile.substring(0, relativeEntryFile.length - 4)
120    console.log("IMPORT FROM: " + importPath)
121
122    const mainFile = path.join(destinationDir, "main.ts")
123    console.log("STARTUP: " + mainFile)
124
125}
126
127export function typeParameterExtendsType(name: string, superName: string, superArguments: string[]) {
128    return ts.factory.createTypeParameterDeclaration(
129        undefined,
130        id(name),
131        ts.factory.createTypeReferenceNode(
132            id(superName),
133            superArguments.map(
134                it => ts.factory.createTypeReferenceNode(
135                    id(it)
136                )
137            )
138        ),
139        undefined
140    )
141}
142
143export function mangleIfBuild(name: ts.PropertyName): ts.PropertyName {
144    if (!ts.isIdentifier(name)) return name
145    const stringName = ts.idText(name)
146    if (stringName === "build") {
147        return id(mangle("build"))
148    } else {
149        return name
150    }
151}
152
153export function isNamedDeclaration(node: ts.Node): node is ts.NamedDeclaration {
154    return ("name" in node)
155}
156
157/** @returns true if the given node represents an identifier */
158export function isTextIdentifier(node: ts.Node): node is ts.Identifier | ts.PrivateIdentifier {
159    return ts.isIdentifier(node) || ts.isPrivateIdentifier(node)
160}
161
162/** @returns text identifier from the specified node */
163export function idTextOrError(node: ts.Node): string {
164    if (isTextIdentifier(node)) return ts.idText(node)
165    throw new Error("Expected an identifier, got: " + ts.SyntaxKind[node.kind])
166}
167
168export function asString(node: ts.Node | undefined): string {
169    if (node === undefined) return "undefined node"
170    if (isTextIdentifier(node)) return ts.idText(node)
171    if (ts.isEtsComponentExpression(node)) return `EtsComponentExpression(${ts.idText(node.expression as ts.Identifier)}`
172    if (isNamedDeclaration(node)) {
173        if (node.name === undefined) {
174            return `${ts.SyntaxKind[node.kind]}(undefined name)`
175        } else {
176            return `${ts.SyntaxKind[node.kind]}(${asString(node.name)})`
177        }
178    } else {
179        return `${ts.SyntaxKind[node.kind]}`
180    }
181}
182
183export function adaptorName(original: string): string {
184    return `Ark${original}`
185}
186
187export function adaptorComponentName(original: string): string {
188    return `Ark${original}Component`
189}
190
191export function etsAttributeName(original: string): string {
192    const attribute = `${original}Attribute`
193    return attribute.startsWith("Lazy") ? attribute.substring(4) : attribute
194}
195
196export function backingField(originalName: string): string {
197    return mangle(`backing_${originalName}`)
198}
199
200export function backingFieldName(originalName: ts.Identifier | ts.PrivateIdentifier): ts.Identifier {
201    return id(backingField(ts.idText(originalName)))
202}
203
204export function adaptorEtsName(name: ts.Identifier): ts.Identifier {
205    return id(adaptorName(ts.idText(name)))
206}
207
208export function adaptorEtsAttributeName(name: ts.Identifier): ts.Identifier {
209    return id(etsAttributeName(ts.idText(name)))
210}
211
212export function adaptorClassName(name: ts.Identifier): ts.Identifier
213export function adaptorClassName(name: undefined): undefined
214export function adaptorClassName(name: ts.Identifier | undefined): ts.Identifier | undefined
215export function adaptorClassName(name: ts.Identifier | undefined): ts.Identifier | undefined {
216    if (!name) return undefined
217    return id(adaptorComponentName(ts.idText(name)))
218}
219
220export function customDialogImplName(name: ts.Identifier | undefined): ts.Identifier | undefined {
221    if (!name) return undefined
222    return id(ts.idText(name) + "Impl")
223}
224
225export function deduceBuilderLambdaName(functionDeclaration: ts.FunctionDeclaration): string {
226    const decorator = getDecorator(functionDeclaration, BuilderLambdaDecorator)
227    if (!decorator) throw new Error("Expected a decorator")
228
229    if (ts.isCallExpression(decorator.expression)) {
230        const name = decorator.expression.arguments[0]!
231        if (ts.isIdentifier(name)) {
232            return ts.idText(name)
233        }
234        if (ts.isStringLiteral(name)) {
235            return name.text
236        }
237    }
238    throw new Error("Unexpected decorator kind: " + asString(decorator.expression))
239}
240
241export function findBuilderLambdaRedirect(typechecker: ts.TypeChecker, node: ts.Node): string | undefined {
242    if (!ts.isCallExpression(node)) return undefined
243    const func = node.expression
244    if (!ts.isIdentifier(func)) return undefined
245
246    const declarations = getDeclarationsByNode(typechecker, func)
247
248    if (declarations.length == 0) return undefined
249    const firstDeclaration = declarations[0]
250    if (!firstDeclaration) return undefined
251    if (!isBuilderLambda(firstDeclaration)) return undefined
252
253    return deduceBuilderLambdaName(firstDeclaration as ts.FunctionDeclaration)
254}
255
256export function isBuilderLambdaCall(callTable: CallTable, node: ts.CallExpression): boolean {
257    const originalNode = ts.getOriginalNode(node) as ts.CallExpression
258    return callTable.builderLambdas.has(originalNode)
259}
260
261export function isStructCall(callTable: CallTable, node: ts.CallExpression): boolean {
262    const originalNode = ts.getOriginalNode(node) as ts.CallExpression
263    return callTable.structCalls.has(originalNode)
264}
265
266export function deduceProvideConsumeName(property: ts.PropertyDeclaration, name: string): string {
267    const decorator = getDecorator(property, name)
268    if (!decorator) throw new Error("Expected a decorator")
269
270    // @Provide foo
271    if (ts.isIdentifier(decorator.expression)) {
272        return idTextOrError(property.name)
273    }
274    // @Provide("bar") foo && @Provide({ allowOverride: 'bar'}) bar
275    if (ts.isCallExpression(decorator.expression)) {
276        const arg = decorator.expression.arguments[0]!
277        if (ts.isIdentifier(arg)) {
278            return ts.idText(arg)
279        }
280        if (ts.isStringLiteral(arg)) {
281            return arg.text
282        }
283        // @Provide({ allowOverride: 'bar'}) bar
284        if (ts.isObjectLiteralExpression(arg)) {
285            const propertiesName = (arg as ts.ObjectLiteralExpression).properties
286                .filter(
287                    (property) =>
288                        ts.isPropertyAssignment(property) &&
289                        property.name.getText() === "allowOverride"
290                )
291            if (propertiesName.length > 0) {
292                const arg = propertiesName[0] as ts.PropertyAssignment
293                if (ts.isStringLiteral(arg.initializer)) {
294                    return arg.initializer.text
295                }
296            }
297        }
298    }
299
300    throw new Error("Unexpected decorator kind: " + asString(decorator.expression))
301}
302
303export function deduceProvideName(property: ts.PropertyDeclaration) {
304    return deduceProvideConsumeName(property, ProvideDecorator)
305}
306
307export function deduceConsumeName(property: ts.PropertyDeclaration) {
308    return deduceProvideConsumeName(property, ConsumeDecorator)
309}
310
311export function getAnnotationArgument(decorator: ts.Decorator): ts.Identifier | undefined {
312    if (!ts.isCallExpression(decorator.expression)) return undefined
313    const args = decorator.expression.arguments
314    if (args.length > 1) return undefined
315    const argument = args[0]
316    if (!ts.isIdentifier(argument)) return undefined
317    return argument
318}
319
320export function isExtend(decorator: ts.Decorator): boolean {
321    return isCallDecorator(decorator, ExtendDecorator)
322}
323
324export function isState(property: ts.PropertyDeclaration): boolean {
325    return hasDecorator(property, StateDecorator)
326}
327
328export function isLink(property: ts.PropertyDeclaration): boolean {
329    return hasDecorator(property, LinkDecorator)
330}
331
332export function isProp(property: ts.PropertyDeclaration): boolean {
333    return hasDecorator(property, PropDecorator)
334}
335
336export function isObjectLink(property: ts.PropertyDeclaration): boolean {
337    return hasDecorator(property, ObjectLinkDecorator)
338}
339
340export function isConsume(property: ts.PropertyDeclaration): boolean {
341    return hasDecorator(property, ConsumeDecorator)
342}
343
344export function isStorageProp(property: ts.PropertyDeclaration): boolean {
345    return hasDecorator(property, StoragePropDecorator)
346}
347
348export function isStorageLink(property: ts.PropertyDeclaration): boolean {
349    return hasDecorator(property, StorageLinkDecorator)
350}
351
352export function isLocalStorageProp(property: ts.PropertyDeclaration): boolean {
353    return hasDecorator(property, LocalStoragePropDecorator)
354}
355
356export function isLocalStorageLink(property: ts.PropertyDeclaration): boolean {
357    return hasDecorator(property, LocalStorageLinkDecorator)
358}
359
360export function isBuilder(property: ts.MethodDeclaration): boolean {
361    return hasDecorator(property, BuilderDecorator)
362}
363
364export function isBuilderParam(property: ts.PropertyDeclaration): boolean {
365    return hasDecorator(property, BuilderParamDecorator)
366}
367
368export function isProvide(property: ts.PropertyDeclaration): boolean {
369    return hasDecorator(property, ProvideDecorator)
370}
371
372export function dropBuilder(modifierLikes: readonly ts.ModifierLike[] | undefined): ts.ModifierLike[] | undefined {
373    return dropDecorators(modifierLikes, BuilderDecorator)
374}
375
376export function dropStylesLike(modifierLikes: readonly ts.ModifierLike[] | undefined): ts.ModifierLike[] | undefined {
377    return dropDecorators(modifierLikes, StylesDecorator, ExtendDecorator, AnimatableExtendDecorator)
378}
379
380export function dropDecorators(modifierLikes: readonly ts.ModifierLike[] | undefined, ...decorators: (string | undefined)[]): ts.ModifierLike[] | undefined {
381    const defined = decorators.filter((it): it is string => it !== undefined)
382    return modifierLikes?.filter(it => defined.every(decorator => !isDecorator(it, decorator)))
383}
384
385export function filterDecorators(node: ts.Node): readonly ts.Decorator[] | undefined {
386    return ts.getAllDecorators(node)
387}
388
389export function filterModifiers(node: ts.Node): readonly ts.Modifier[] | undefined {
390    if (!ts.canHaveModifiers(node)) {
391        return undefined
392    }
393    return ts.getModifiers(node)
394}
395
396export function contextLocalStateOf(name: string, initializer: ts.Expression, type?: ts.TypeNode): ts.Expression {
397    return ts.factory.createCallExpression(
398        id("contextLocalStateOf"),
399        type ? [type] : undefined,
400        [
401            ts.factory.createStringLiteral(name),
402            ts.factory.createArrowFunction(
403                undefined,
404                undefined,
405                [],
406                undefined,
407                ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
408                initializer
409            )
410        ]
411    )
412}
413
414export function isGlobalBuilder(node: ts.Node): boolean {
415    return ts.isFunctionDeclaration(node) && hasDecorator(node, BuilderDecorator)
416}
417
418export function isBuilderLambda(node: ts.Node): boolean {
419    return ts.isFunctionDeclaration(node) && hasDecorator(node, BuilderLambdaDecorator)
420}
421
422export function provideConsumeVariableName(decorator: string, prefix: string, property: ts.PropertyDeclaration): ts.Identifier {
423    if (!ts.isIdentifier(property.name) && !ts.isPrivateIdentifier(property.name)) {
424        throw new Error("Expected an identifier")
425    }
426
427    return id(prefix + deduceProvideConsumeName(property, decorator))
428}
429
430export function consumeVariableName(consume: ts.PropertyDeclaration): ts.Identifier {
431    return provideConsumeVariableName(ConsumeDecorator, "__consume_", consume)
432}
433
434export function provideVariableName(provide: ts.PropertyDeclaration): ts.Identifier {
435    return provideConsumeVariableName(ProvideDecorator, "__provide_", provide)
436}
437
438export function createProvideInitializerField(provide: ts.PropertyDeclaration): ts.PropertyAssignment {
439    return ts.factory.createPropertyAssignment(
440        backingFieldName(provide.name as ts.Identifier),
441        provideVariableName(provide)
442    )
443}
444
445export function createConsumeInitializerField(consume: ts.PropertyDeclaration): ts.PropertyAssignment {
446    return ts.factory.createPropertyAssignment(
447        backingFieldName(consume.name as ts.Identifier),
448        consumeVariableName(consume)
449    )
450}
451
452export function filterDecoratedProperties(members: ts.NodeArray<ts.ClassElement>, decoratorName: string): ts.PropertyDeclaration[] {
453    return members.filter(property => ts.isPropertyDeclaration(property) && hasDecorator(property, decoratorName)) as ts.PropertyDeclaration[]
454}
455
456export function filterLinks(members: ts.NodeArray<ts.ClassElement>): ts.PropertyDeclaration[] {
457    return members.filter(it =>
458        ts.isPropertyDeclaration(it) && isLink(it)
459    ) as ts.PropertyDeclaration[]
460}
461
462export function filterProps(members: ts.NodeArray<ts.ClassElement>): ts.PropertyDeclaration[] {
463    return members.filter(it =>
464        ts.isPropertyDeclaration(it) && isProp(it)
465    ) as ts.PropertyDeclaration[]
466}
467
468export function filterObjectLinks(members: ts.NodeArray<ts.ClassElement>): ts.PropertyDeclaration[] {
469    return members.filter(it =>
470        ts.isPropertyDeclaration(it) && isObjectLink(it)
471    ) as ts.PropertyDeclaration[]
472}
473
474export function filterConsumes(members: ts.NodeArray<ts.ClassElement>): ts.PropertyDeclaration[] {
475    return members.filter(it =>
476        ts.isPropertyDeclaration(it) && isConsume(it)
477    ) as ts.PropertyDeclaration[]
478}
479
480export function filterProvides(members: ts.NodeArray<ts.ClassElement>): ts.PropertyDeclaration[] {
481    return members.filter(it =>
482        ts.isPropertyDeclaration(it) && isProvide(it)
483    ) as ts.PropertyDeclaration[]
484}
485
486/** @returns a nullable accessor: `expression?.name` */
487export function createNullableAccessor(expression: ts.Expression, name: string): ts.Expression {
488    return ts.factory.createPropertyAccessChain(expression, ts.factory.createToken(ts.SyntaxKind.QuestionDotToken), name)
489}
490
491/** @returns a not-null accessor: `expression!.name!` */
492export function createNotNullAccessor(expression: ts.Expression, name: string): ts.Expression {
493    return ts.factory.createNonNullExpression(ts.factory.createPropertyAccessExpression(ts.factory.createNonNullExpression(expression), name))
494}
495
496/** @returns a multiline block with the given statements */
497export function createBlock(...statements: ts.Statement[]): ts.Block {
498    return ts.factory.createBlock(statements, true)
499}
500
501/** @returns a nullish coalescing expression with safe right part: `left ?? (right)` */
502export function createNullishCoalescing(left: ts.Expression, right: ts.Expression): ts.Expression {
503    return ts.factory.createBinaryExpression(left, ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), ts.factory.createParenthesizedExpression(right))
504}
505
506export function createValueAccessor(expression: ts.Expression): ts.Expression {
507    return ts.factory.createPropertyAccessExpression(expression, "value")
508}
509
510export function filterDefined<T>(value: (T | undefined)[]): T[] {
511    return value.filter((it: T | undefined): it is T => it != undefined)
512}
513
514export function collect<T>(...value: (ReadonlyArray<T> | T | undefined)[]): T[] {
515    const empty: (T | undefined)[] = []
516    return filterDefined(empty.concat(...value))
517}
518
519export function initializers() {
520    return id(RewriteNames.Initializers)
521}
522
523export function isDefined<T>(value: T | undefined | null): value is T {
524    return value !== undefined && value !== null
525}
526
527export function extendsLikeFunctionName(propertyName: string, componentName: string): string {
528    return propertyName + "__" + componentName
529}
530
531export function hasLocalDeclaration(typechecker: ts.TypeChecker, node: ts.Identifier): boolean {
532    const declarations = getDeclarationsByNode(typechecker, node)
533    return (declarations.length == 1 && declarations[0].getSourceFile() == node.getSourceFile())
534}
535
536export function isBuiltinComponentName(ctx: ts.TransformationContext, name: string) {
537    return ctx.getCompilerOptions().ets?.components.includes(name)
538}
539
540export function voidLambdaType() {
541    return ts.factory.createFunctionTypeNode(
542        undefined,
543        [],
544        Void()
545    )
546}
547
548export function assertUnreachable(...args: never): never {
549    throw new Error(`never`)
550}
551
552export function throwError(message: string): never {
553    throw new Error(message)
554}
555
556// Produce a name outside of user space
557export function mangle(value: string): string {
558    return `__${value}`
559}
560
561export function buildBuilderArgument(): string {
562    return mangle("builder")
563}
564
565export function commonMethodComponentId(importer: Importer): ts.Identifier {
566    return id(importer.withAdaptorImport(ArkCommonMethodComponent))
567}
568
569export function commonMethodComponentType(importer: Importer): ts.TypeReferenceNode {
570    return ts.factory.createTypeReferenceNode(importer.withAdaptorImport(ArkCommonMethodComponent))
571}
572
573export function getSingleExpression(block?: ts.Block): ts.Expression | undefined {
574    const statement = getSingleStatement(block)
575    return statement && ts.isExpressionStatement(statement) ? statement.expression:undefined
576}
577
578export function getSingleStatement(block?: ts.Block): ts.Statement | undefined {
579    return block && block.statements.length === 1 ? block.statements[0] : undefined
580}
581