• 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 */
15import * as arkts from '@koalaui/libarkts';
16import { getCommonPath } from '../path';
17const common = require(getCommonPath());
18const UniqueId = common.UniqueId;
19
20export enum RuntimeNames {
21    __CONTEXT = '__context',
22    __ID = '__id',
23    __KEY = '__key',
24    ANNOTATION_BUILDER = 'Builder',
25    ANNOTATION = 'memo',
26    ANNOTATION_ENTRY = 'memo_entry',
27    ANNOTATION_INTRINSIC = 'memo_intrinsic',
28    ANNOTATION_STABLE = 'memo_stable',
29    ANNOTATION_SKIP = 'memo_skip',
30    COMPUTE = 'compute',
31    CONTEXT = '__memo_context',
32    CONTEXT_TYPE = '__memo_context_type',
33    MEMO_IMPORT_NAME = 'arkui.stateManagement.runtime',
34    GENSYM = 'gensym%%_',
35    ID = '__memo_id',
36    ID_TYPE = '__memo_id_type',
37    INTERNAL_PARAMETER_STATE = 'param',
38    INTERNAL_SCOPE = 'scope',
39    INTERNAL_VALUE = 'cached',
40    INTERNAL_VALUE_NEW = 'recache',
41    INTERNAL_VALUE_OK = 'unchanged',
42    PARAMETER = '__memo_parameter',
43    SCOPE = '__memo_scope',
44    THIS = 'this',
45    VALUE = 'value',
46    EQUAL_T = '=t',
47}
48
49export interface ReturnTypeInfo {
50    node: arkts.TypeNode | undefined;
51    isMemo?: boolean;
52    isVoid?: boolean;
53    isStableThis?: boolean;
54}
55
56export interface ParamInfo {
57    ident: arkts.Identifier;
58    param: arkts.ETSParameterExpression;
59}
60
61export interface MemoInfo {
62    name?: string;
63    isMemo: boolean;
64}
65
66function baseName(path: string): string {
67    return path.replace(/^.*\/(.*)$/, '$1');
68}
69
70export class PositionalIdTracker {
71    // Global for the whole program.
72    static callCount: number = 0;
73
74    // Set `stable` to true if you want to have more predictable values.
75    // For example for tests.
76    // Don't use it in production!
77    constructor(public filename: string, public stableForTests: boolean = false) {
78        if (stableForTests) PositionalIdTracker.callCount = 0;
79    }
80
81    sha1Id(callName: string, fileName: string): string {
82        const uniqId = new UniqueId();
83        uniqId.addString('memo call uniqid');
84        uniqId.addString(fileName);
85        uniqId.addString(callName);
86        uniqId.addI32(PositionalIdTracker.callCount++);
87        return uniqId.compute().substring(0, 7);
88    }
89
90    stringId(callName: string, fileName: string): string {
91        return `${PositionalIdTracker.callCount++}_${callName}_id_DIRNAME/${fileName}`;
92    }
93
94    id(callName: string = ''): arkts.NumberLiteral | arkts.StringLiteral {
95        const fileName = this.stableForTests ? baseName(this.filename) : this.filename;
96
97        const positionId = this.stableForTests ? this.stringId(callName, fileName) : this.sha1Id(callName, fileName);
98
99        return this.stableForTests
100            ? arkts.factory.createStringLiteral(positionId)
101            : arkts.factory.createNumericLiteral(parseInt(positionId, 16));
102    }
103}
104
105export function isMemoAnnotation(node: arkts.AnnotationUsage, memoName: RuntimeNames): boolean {
106    return node.expr !== undefined && arkts.isIdentifier(node.expr) && node.expr.name === memoName;
107}
108
109export type MemoAstNode =
110    | arkts.ScriptFunction
111    | arkts.ETSParameterExpression
112    | arkts.ClassProperty
113    | arkts.TSTypeAliasDeclaration
114    | arkts.ETSFunctionType
115    | arkts.ArrowFunctionExpression
116    | arkts.ETSTypeReference
117    | arkts.VariableDeclaration;
118
119export function hasMemoAnnotation<T extends MemoAstNode>(node: T): boolean {
120    return node.annotations.some(
121        (it) => isMemoAnnotation(it, RuntimeNames.ANNOTATION) || isMemoAnnotation(it, RuntimeNames.ANNOTATION_BUILDER)
122    );
123}
124
125export function hasMemoIntrinsicAnnotation<T extends MemoAstNode>(node: T): boolean {
126    return node.annotations.some((it) => isMemoAnnotation(it, RuntimeNames.ANNOTATION_INTRINSIC));
127}
128
129export function hasMemoEntryAnnotation<T extends MemoAstNode>(node: T): boolean {
130    return node.annotations.some((it) => isMemoAnnotation(it, RuntimeNames.ANNOTATION_ENTRY));
131}
132
133export function hasMemoStableAnnotation(node: arkts.ClassDefinition): boolean {
134    return node.annotations.some((it) => isMemoAnnotation(it, RuntimeNames.ANNOTATION_STABLE));
135}
136
137export function hasMemoSkipAnnotation(node: arkts.ETSParameterExpression): boolean {
138    return node.annotations.some((it) => isMemoAnnotation(it, RuntimeNames.ANNOTATION_SKIP));
139}
140
141export function removeMemoAnnotation<T extends MemoAstNode>(node: T): T {
142    const newAnnotations: arkts.AnnotationUsage[] = node.annotations.filter(
143        (it) => !isMemoAnnotation(it, RuntimeNames.ANNOTATION) && !isMemoAnnotation(it, RuntimeNames.ANNOTATION_STABLE)
144    );
145    if (arkts.isEtsParameterExpression(node)) {
146        node.annotations = newAnnotations;
147        return node;
148    }
149    return node.setAnnotations(newAnnotations) as T;
150}
151
152/**
153 * TODO:
154 * @deprecated
155 */
156export function isSyntheticReturnStatement(node: arkts.AstNode): boolean {
157    return isIfStatementWithSyntheticReturn(node) || isSimpleSyntheticReturn(node) || isSyntheticReturnInBlock(node);
158}
159
160function isIfStatementWithSyntheticReturn(node: arkts.AstNode): boolean {
161    return (
162        arkts.isIfStatement(node) &&
163        !!node.test &&
164        arkts.isMemberExpression(node.test) &&
165        arkts.isIdentifier(node.test.object) &&
166        node.test.object.name === RuntimeNames.SCOPE &&
167        arkts.isIdentifier(node.test.property) &&
168        node.test.property.name === RuntimeNames.INTERNAL_VALUE_OK &&
169        (arkts.isBlockStatement(node.consequent) || arkts.isReturnStatement(node.consequent))
170    );
171}
172
173function isSimpleSyntheticReturn(node: arkts.AstNode): boolean {
174    return (
175        arkts.isReturnStatement(node) &&
176        !!node.argument &&
177        arkts.isMemberExpression(node.argument) &&
178        arkts.isIdentifier(node.argument.object) &&
179        node.argument.object.name === RuntimeNames.SCOPE &&
180        arkts.isIdentifier(node.argument.property) &&
181        node.argument.property.name === RuntimeNames.INTERNAL_VALUE
182    );
183}
184
185function isSyntheticReturnInBlock(node: arkts.AstNode): boolean {
186    if (!arkts.isBlockStatement(node) || node.statements.length !== 2) {
187        return false;
188    }
189    if (!arkts.isReturnStatement(node.statements[1])) {
190        return false;
191    }
192    const isReturnThis: boolean = !!node.statements[1].argument && arkts.isThisExpression(node.statements[1].argument);
193    const isReturnVoid: boolean = node.statements[1].argument === undefined;
194
195    return (
196        arkts.isMemberExpression(node.statements[0]) &&
197        arkts.isIdentifier(node.statements[0].object) &&
198        node.statements[0].object.name === RuntimeNames.SCOPE &&
199        arkts.isIdentifier(node.statements[0].property) &&
200        node.statements[0].property.name === RuntimeNames.INTERNAL_VALUE &&
201        (isReturnThis || isReturnVoid)
202    );
203}
204
205/**
206 * TODO:
207 * @deprecated
208 */
209export function isMemoParametersDeclaration(node: arkts.AstNode): boolean {
210    return (
211        arkts.isVariableDeclaration(node) &&
212        node.declarators.every((it) => it.name.name.startsWith(RuntimeNames.PARAMETER))
213    );
214}
215
216/**
217 * TODO: change this to TypeNodeGetType to check void type
218 */
219export function isVoidType(typeNode: arkts.TypeNode | undefined): boolean {
220    return typeNode?.dumpSrc() === 'void';
221}
222
223/**
224 * es2panda API is weird here
225 *
226 * @deprecated
227 */
228export function castParameters(params: readonly arkts.Expression[]): arkts.ETSParameterExpression[] {
229    return params as arkts.ETSParameterExpression[];
230}
231
232/**
233 * es2panda API is weird here
234 *
235 * @deprecated
236 */
237export function castFunctionExpression(value: arkts.Expression | undefined): arkts.FunctionExpression {
238    return value as unknown as arkts.FunctionExpression;
239}
240
241/**
242 * es2panda API is weird here
243 *
244 * @deprecated
245 */
246export function castArrowFunctionExpression(value: arkts.Expression | undefined): arkts.ArrowFunctionExpression {
247    return value as unknown as arkts.ArrowFunctionExpression;
248}
249
250/**
251 * es2panda API is weird here
252 *
253 * @deprecated
254 */
255export function castIdentifier(value: arkts.AstNode | undefined): arkts.Identifier {
256    return value as unknown as arkts.Identifier;
257}
258
259/**
260 * es2panda API is weird here
261 *
262 * @deprecated
263 */
264export function castOverloadsToMethods(overloads: arkts.AstNode[]): readonly arkts.MethodDefinition[] {
265    return overloads as unknown as readonly arkts.MethodDefinition[];
266}
267
268export function isStandaloneArrowFunction(node: arkts.AstNode): node is arkts.ArrowFunctionExpression {
269    if (!arkts.isArrowFunctionExpression(node)) return false;
270
271    // handling anonymous arrow function call
272    if (!!node.parent && arkts.isCallExpression(node.parent) && node.parent.expression.peer === node.peer) {
273        return true;
274    }
275
276    return (
277        !!node.parent &&
278        !arkts.isVariableDeclarator(node.parent) &&
279        !arkts.isClassProperty(node.parent) &&
280        !(arkts.isCallExpression(node.parent) && node.parent.expression)
281    );
282}
283
284export function isFunctionProperty(node: arkts.AstNode): node is arkts.Property {
285    return (
286        arkts.isProperty(node) &&
287        !!node.key &&
288        arkts.isIdentifier(node.key) &&
289        !!node.value &&
290        arkts.isArrowFunctionExpression(node.value)
291    );
292}
293
294export function isThisAttributeAssignment(node: arkts.AstNode): node is arkts.AssignmentExpression {
295    if (!arkts.isAssignmentExpression(node)) {
296        return false;
297    }
298    if (!node.left || !node.right) {
299        return false;
300    }
301    const isAssignOperator = node.operatorType === arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION;
302    const isThisAttribute =
303        arkts.isMemberExpression(node.left) &&
304        arkts.isThisExpression(node.left.object) &&
305        arkts.isIdentifier(node.left.property);
306    return isAssignOperator && isThisAttribute;
307}
308
309export function findThisAttribute(node: arkts.AstNode): arkts.Identifier | undefined {
310    if (!arkts.isMemberExpression(node) || !arkts.isIdentifier(node.property)) {
311        return undefined;
312    }
313    return node.property;
314}
315
316export function isMemoThisAttribute(node: arkts.Identifier, value: arkts.ArrowFunctionExpression): boolean {
317    let isMemo: boolean = isMemoArrowFunction(value);
318    if (isMemo) {
319        return true;
320    }
321    const decl: arkts.AstNode | undefined = getDeclResolveAlias(node);
322    if (!decl) {
323        return false;
324    }
325    if (arkts.isClassProperty(decl)) {
326        isMemo ||= isMemoClassProperty(decl);
327    } else if (arkts.isMethodDefinition(decl)) {
328        isMemo ||= isMemoDeclaredMethod(decl);
329    }
330    return isMemo;
331}
332
333export function isMemoClassProperty(node: arkts.ClassProperty): boolean {
334    let isMemo = findMemoFromTypeAnnotation(node.typeAnnotation);
335    if (node.value) {
336        isMemo ||=
337            arkts.isArrowFunctionExpression(node.value) &&
338            (hasMemoAnnotation(node.value) || hasMemoIntrinsicAnnotation(node.value));
339    }
340    isMemo ||= hasMemoAnnotation(node) || hasMemoIntrinsicAnnotation(node);
341    return isMemo;
342}
343
344export function isMemoMethodDefinition(node: arkts.MethodDefinition): boolean {
345    return (
346        hasMemoAnnotation(node.scriptFunction) ||
347        hasMemoIntrinsicAnnotation(node.scriptFunction) ||
348        hasMemoEntryAnnotation(node.scriptFunction)
349    );
350}
351
352export function isMemoArrowFunction(node: arkts.ArrowFunctionExpression): boolean {
353    return hasMemoAnnotation(node) || hasMemoIntrinsicAnnotation(node);
354}
355
356export function isMemoTSTypeAliasDeclaration(node: arkts.TSTypeAliasDeclaration): boolean {
357    let isMemo = findMemoFromTypeAnnotation(node.typeAnnotation);
358    isMemo ||= hasMemoAnnotation(node) || hasMemoIntrinsicAnnotation(node);
359    return isMemo;
360}
361
362export function isMemoETSParameterExpression(param: arkts.ETSParameterExpression): boolean {
363    const type = param.identifier.typeAnnotation;
364    if (!type) {
365        return false;
366    }
367    let isMemo: boolean = hasMemoAnnotation(param) || hasMemoIntrinsicAnnotation(param);
368    isMemo ||= findMemoFromTypeAnnotation(type);
369    let decl: arkts.AstNode | undefined;
370    if (
371        arkts.isETSTypeReference(type) &&
372        !!type.part &&
373        !!type.part.name &&
374        !!(decl = getDeclResolveAlias(type.part.name))
375    ) {
376        if (arkts.isTSTypeAliasDeclaration(decl)) {
377            isMemo ||= hasMemoAnnotation(decl) || hasMemoIntrinsicAnnotation(decl);
378            isMemo ||= findMemoFromTypeAnnotation(decl.typeAnnotation);
379            return isMemo;
380        }
381    }
382    return isMemo;
383}
384
385export function isMemoVariableDeclaration(node: arkts.VariableDeclaration): boolean {
386    return hasMemoAnnotation(node) || hasMemoIntrinsicAnnotation(node);
387}
388
389export function isMemoVariableDeclarator(node: arkts.VariableDeclarator): boolean {
390    let isMemo: boolean = false;
391    if (!!node.name.typeAnnotation) {
392        isMemo ||= findMemoFromTypeAnnotation(node.name.typeAnnotation);
393    }
394    if (!!node.initializer && arkts.isArrowFunctionExpression(node.initializer)) {
395        isMemo ||= isMemoArrowFunction(node.initializer);
396    }
397    if (!!node.parent && arkts.isVariableDeclaration(node.parent)) {
398        isMemo ||= isMemoVariableDeclaration(node.parent);
399    }
400    return isMemo;
401}
402
403export function isMemoProperty(node: arkts.Property, value: arkts.ArrowFunctionExpression): boolean {
404    let isMemo: boolean = isMemoArrowFunction(value);
405    if (isMemo) {
406        return true;
407    }
408    let decl: arkts.AstNode | undefined;
409    if (!node.key || !(decl = getDeclResolveAlias(node.key))) {
410        return false;
411    }
412    if (arkts.isMethodDefinition(decl)) {
413        isMemo ||= isMemoDeclaredMethod(decl);
414    }
415    return isMemo;
416}
417
418export function isMemoDeclaredMethod(decl: arkts.MethodDefinition): boolean {
419    if (
420        decl.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET &&
421        findMemoFromTypeAnnotation(decl.scriptFunction.returnTypeAnnotation)
422    ) {
423        return true;
424    }
425    return !hasMemoEntryAnnotation(decl.scriptFunction) && isMemoMethodDefinition(decl);
426}
427
428export function isDeclaredMethodWithMemoParams(decl: arkts.MethodDefinition): boolean {
429    if (decl.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET) {
430        return false;
431    }
432    return decl.scriptFunction.params.some((param) => {
433        return arkts.isEtsParameterExpression(param) && isMemoETSParameterExpression(param);
434    });
435}
436
437export function isMemoDeclaredIdentifier(decl: arkts.Identifier): boolean {
438    if (findMemoFromTypeAnnotation(decl.typeAnnotation)) {
439        return true;
440    }
441    if (!!decl.parent && arkts.isVariableDeclarator(decl.parent)) {
442        return isMemoVariableDeclarator(decl.parent);
443    }
444    return false;
445}
446
447export function isMemoDeclaredClassProperty(decl: arkts.ClassProperty): boolean {
448    return isMemoClassProperty(decl);
449}
450
451export function findMemoFromTypeAnnotation(typeAnnotation: arkts.AstNode | undefined): boolean {
452    if (!typeAnnotation) {
453        return false;
454    }
455    if (arkts.isETSTypeReference(typeAnnotation) && !!typeAnnotation.part && !!typeAnnotation.part.name) {
456        let decl: arkts.AstNode | undefined = arkts.getDecl(typeAnnotation.part.name);
457        if (!decl || !arkts.isTSTypeAliasDeclaration(decl)) {
458            return false;
459        }
460        let isMemo: boolean = hasMemoAnnotation(decl) || hasMemoIntrinsicAnnotation(decl);
461        if (!isMemo && !!decl.typeAnnotation) {
462            isMemo = findMemoFromTypeAnnotation(decl.typeAnnotation);
463        }
464        return isMemo;
465    } else if (arkts.isETSFunctionType(typeAnnotation)) {
466        return hasMemoAnnotation(typeAnnotation) || hasMemoIntrinsicAnnotation(typeAnnotation);
467    } else if (arkts.isETSUnionType(typeAnnotation)) {
468        return typeAnnotation.types.some(
469            (type) => arkts.isETSFunctionType(type) && (hasMemoAnnotation(type) || hasMemoIntrinsicAnnotation(type))
470        );
471    }
472    return false;
473}
474
475export function findReturnTypeFromTypeAnnotation(
476    typeAnnotation: arkts.AstNode | undefined
477): arkts.TypeNode | undefined {
478    if (!typeAnnotation) {
479        return undefined;
480    }
481    if (arkts.isETSTypeReference(typeAnnotation) && !!typeAnnotation.part && !!typeAnnotation.part.name) {
482        let decl: arkts.AstNode | undefined = arkts.getDecl(typeAnnotation.part.name);
483        if (!!decl && arkts.isTSTypeAliasDeclaration(decl)) {
484            return findReturnTypeFromTypeAnnotation(decl.typeAnnotation);
485        }
486        return undefined;
487    }
488    if (arkts.isETSFunctionType(typeAnnotation)) {
489        return typeAnnotation.returnType;
490    }
491    if (arkts.isETSUnionType(typeAnnotation)) {
492        return typeAnnotation.types.find((type) => arkts.isETSFunctionType(type))?.returnType;
493    }
494    return undefined;
495}
496
497export function getDeclResolveAlias(node: arkts.AstNode): arkts.AstNode | undefined {
498    const decl = arkts.getDecl(node);
499    if (!!decl && !!decl.parent && arkts.isIdentifier(decl) && arkts.isVariableDeclarator(decl.parent)) {
500        if (!!decl.parent.initializer && arkts.isIdentifier(decl.parent.initializer)) {
501            return getDeclResolveAlias(decl.parent.initializer);
502        }
503        if (!!decl.parent.initializer && arkts.isMemberExpression(decl.parent.initializer)) {
504            return getDeclResolveAlias(decl.parent.initializer.property);
505        }
506    }
507    return decl;
508}
509
510export function mayAddLastReturn(node: arkts.BlockStatement): boolean {
511    return (
512        node.statements.length === 0 ||
513        (!arkts.isReturnStatement(node.statements[node.statements.length - 1]) &&
514            !arkts.isThrowStatement(node.statements[node.statements.length - 1]))
515    );
516}
517
518export function fixGensymParams(params: ParamInfo[], body: arkts.BlockStatement): number {
519    let gensymParamsCount = 0;
520    for (let i = 0; i < params.length; i++) {
521        if (params[i].ident.name.startsWith(RuntimeNames.GENSYM)) {
522            if (gensymParamsCount >= body.statements.length) {
523                throw new Error(`Expected ${params[i].ident.name} replacement to original parameter`);
524            }
525            const declaration = body.statements[gensymParamsCount];
526            if (!arkts.isVariableDeclaration(declaration)) {
527                throw new Error(`Expected ${params[i].ident.name} replacement to original parameter`);
528            }
529            if (!arkts.isIdentifier(declaration.declarators[0].name)) {
530                throw new Error(`Expected ${params[i].ident.name} replacement to original parameter`);
531            }
532            params[i].ident = declaration.declarators[0].name;
533            gensymParamsCount++;
534        }
535    }
536    return gensymParamsCount;
537}
538
539export function isUnmemoizedInFunction(params?: readonly arkts.Expression[]): boolean {
540    const _params = params ?? [];
541    const first = _params.at(0);
542    const isContextAdded =
543        !!first && arkts.isEtsParameterExpression(first) && first.identifier.name === RuntimeNames.CONTEXT;
544    const second = _params.at(1);
545    const isIdAdded = !!second && arkts.isEtsParameterExpression(second) && second.identifier.name === RuntimeNames.ID;
546    return isContextAdded && isIdAdded;
547}
548
549export function buildReturnTypeInfo(
550    returnType: arkts.TypeNode | undefined,
551    isMemo?: boolean,
552    isStableThis?: boolean
553): ReturnTypeInfo {
554    const newReturnType = !!returnType
555        ? returnType.clone()
556        : arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID);
557    return {
558        node: newReturnType,
559        isMemo,
560        isVoid: isVoidType(newReturnType),
561        isStableThis,
562    };
563}
564
565export function buildeParamInfos(parameters: readonly arkts.ETSParameterExpression[]): ParamInfo[] {
566    return [
567        ...parameters
568            .filter((it) => !hasMemoSkipAnnotation(it))
569            .map((it) => {
570                return { ident: it.identifier, param: it };
571            }),
572    ];
573}
574
575export function parametersBlockHasReceiver(params: readonly arkts.Expression[]): boolean {
576    return params.length > 0 && arkts.isEtsParameterExpression(params[0]) && isThisParam(params[0]);
577}
578
579export function parametrizedNodeHasReceiver(node: arkts.ScriptFunction | arkts.ETSFunctionType | undefined): boolean {
580    if (node === undefined) {
581        return false;
582    }
583    return parametersBlockHasReceiver(node.params);
584}
585
586function isThisParam(node: arkts.Expression | undefined): boolean {
587    if (node === undefined || !arkts.isEtsParameterExpression(node)) {
588        return false;
589    }
590    return node.identifier?.isReceiver ?? false;
591}
592