From fd99b729fcc70dc32277700c08c6e75ae0f313b3 Mon Sep 17 00:00:00 2001 From: ekkoruse Date: Mon, 9 Jun 2025 17:33:17 +0800 Subject: [PATCH] ui cache Signed-off-by: Jiakai Shi Change-Id: I673b8d3986e68fe876caa21ca07ae89a7a0e0dbb --- arkui-plugins/common/predefines.ts | 3 + .../memo-plugins/function-transformer.ts | 30 + arkui-plugins/memo-plugins/index.ts | 4 + .../memo-plugins/memo-cache-factory.ts | 360 +++++++++ arkui-plugins/memo-plugins/memo-factory.ts | 37 +- .../memo-plugins/parameter-transformer.ts | 5 +- arkui-plugins/memo-plugins/utils.ts | 94 ++- .../demo/mock/memo/lambdas/with-receiver.ets | 11 +- .../mock/memo/methods/non-void-method.ets | 7 + .../test/ut/common/annotation.test.ts | 23 +- .../argument-call.test.ts | 12 +- .../declare-and-call.test.ts | 10 +- .../inner-functions.test.ts | 18 +- .../non-void-return-type.test.ts | 18 +- .../type-reference.test.ts | 23 +- .../void-return-type.test.ts | 6 +- .../lambda-literals/argument-call.test.ts | 36 +- .../function-with-receiver.test.ts | 10 +- .../lambda-literals/trailing-lambdas.test.ts | 22 +- .../lambda-literals/void-lambda.test.ts | 4 +- .../lambda-literals/with-receiver.test.ts | 68 +- .../method-declarations/argument-call.test.ts | 16 +- .../method-declarations/callable.test.ts | 12 +- .../declare-and-call.test.ts | 8 +- .../internal-calls.test.ts | 20 +- .../non-void-method.test.ts | 23 +- .../method-declarations/void-method.test.ts | 18 +- .../class-constructor.test.ts | 12 +- .../class-properties.test.ts | 12 +- .../property-declarations/interfaces.test.ts | 18 +- arkui-plugins/test/utils/artkts-config.ts | 2 + arkui-plugins/test/utils/compile.ts | 170 ++++- arkui-plugins/test/utils/global.ts | 26 +- arkui-plugins/test/utils/plugin-tester.ts | 25 +- .../utils/plugins/before-memo-no-recheck.ts | 47 ++ .../plugins/builder-lambda-no-recheck.ts | 2 +- arkui-plugins/test/utils/plugins/index.ts | 1 + .../test/utils/plugins/memo-no-recheck.ts | 2 + arkui-plugins/test/utils/processor-builder.ts | 29 + .../test/utils/processors/base-processor.ts | 65 ++ .../test/utils/processors/master-processor.ts | 102 +++ .../utils/{ => processors}/task-processor.ts | 86 +-- arkui-plugins/test/utils/shared-types.ts | 21 +- .../builder-lambda-translators/factory.ts | 186 +++-- .../builder-lambda-translators/utils.ts | 51 +- .../ui-plugins/checked-transformer.ts | 46 +- .../ui-plugins/component-transformer.ts | 78 +- .../ui-plugins/entry-translators/factory.ts | 4 +- arkui-plugins/ui-plugins/index.ts | 10 +- .../memo-translators/function-collector.ts | 286 +++++++ .../memo-translators/memo-visitor.ts | 46 ++ .../ui-plugins/memo-translators/utils.ts | 719 ++++++++++++++++++ .../property-translators/builderParam.ts | 9 +- .../property-translators/factory.ts | 3 +- .../ui-plugins/property-translators/utils.ts | 13 +- .../ui-plugins/struct-translators/factory.ts | 138 ++-- .../struct-translators/struct-transformer.ts | 19 +- .../ui-plugins/struct-translators/utils.ts | 53 +- arkui-plugins/ui-plugins/ui-factory.ts | 57 +- arkui-plugins/ui-plugins/utils.ts | 162 ++-- koala-wrapper/native/include/common.h | 5 + koala-wrapper/native/src/bridges.cc | 121 ++- koala-wrapper/src/Es2pandaNativeModule.ts | 20 +- koala-wrapper/src/arkts-api/class-by-peer.ts | 2 +- .../src/arkts-api/factory/nodeFactory.ts | 8 + koala-wrapper/src/arkts-api/index.ts | 1 + .../node-utilities/ArrowFunctionExpression.ts | 9 +- .../node-utilities/CallExpression.ts | 9 +- .../arkts-api/node-utilities/ClassProperty.ts | 9 +- .../node-utilities/ETSFunctionType.ts | 9 +- .../node-utilities/ETSParameterExpression.ts | 12 +- .../arkts-api/node-utilities/ETSUnionType.ts | 9 +- .../arkts-api/node-utilities/Identifier.ts | 9 +- .../node-utilities/MethodDefinition.ts | 9 +- .../src/arkts-api/node-utilities/Property.ts | 10 +- .../node-utilities/ReturnStatement.ts | 9 +- .../node-utilities/ScriptFunction.ts | 9 +- .../node-utilities/TSClassImplements.ts | 31 + .../node-utilities/TSTypeAliasDeclaration.ts | 9 +- .../node-utilities/VariableDeclarator.ts | 9 +- koala-wrapper/src/arkts-api/peers/Context.ts | 14 +- koala-wrapper/src/arkts-api/types.ts | 8 + .../src/arkts-api/utilities/nodeCache.ts | 102 +++ .../src/arkts-api/utilities/public.ts | 22 +- koala-wrapper/src/arkts-api/visitor.ts | 19 + koala-wrapper/src/generated/index.ts | 1 + 86 files changed, 3136 insertions(+), 737 deletions(-) create mode 100644 arkui-plugins/memo-plugins/memo-cache-factory.ts create mode 100644 arkui-plugins/test/utils/plugins/before-memo-no-recheck.ts create mode 100644 arkui-plugins/test/utils/processor-builder.ts create mode 100644 arkui-plugins/test/utils/processors/base-processor.ts create mode 100644 arkui-plugins/test/utils/processors/master-processor.ts rename arkui-plugins/test/utils/{ => processors}/task-processor.ts (87%) create mode 100644 arkui-plugins/ui-plugins/memo-translators/function-collector.ts create mode 100644 arkui-plugins/ui-plugins/memo-translators/memo-visitor.ts create mode 100644 arkui-plugins/ui-plugins/memo-translators/utils.ts create mode 100644 koala-wrapper/src/arkts-api/node-utilities/TSClassImplements.ts create mode 100644 koala-wrapper/src/arkts-api/utilities/nodeCache.ts diff --git a/arkui-plugins/common/predefines.ts b/arkui-plugins/common/predefines.ts index 8825a0df1..7032c45e6 100644 --- a/arkui-plugins/common/predefines.ts +++ b/arkui-plugins/common/predefines.ts @@ -50,7 +50,10 @@ export enum BindableDecl { export enum StructDecoratorNames { ENTRY = 'Entry', COMPONENT = 'Component', + COMPONENT_V2 = 'ComponentV2', RESUABLE = 'Reusable', + RESUABLE_V2 = 'ReusableV2', + CUSTOM_LAYOUT = 'CustomLayout', } export enum DecoratorNames { diff --git a/arkui-plugins/memo-plugins/function-transformer.ts b/arkui-plugins/memo-plugins/function-transformer.ts index fb059b409..4e0327d51 100644 --- a/arkui-plugins/memo-plugins/function-transformer.ts +++ b/arkui-plugins/memo-plugins/function-transformer.ts @@ -54,6 +54,7 @@ import { ParameterTransformer } from './parameter-transformer'; import { ReturnTransformer } from './return-transformer'; import { SignatureTransformer } from './signature-transformer'; import { moveToFront } from '../common/arkts-utils'; +import { rewriteByType } from './memo-cache-factory'; interface ScopeInfo extends MemoInfo { regardAsSameScope?: boolean; @@ -64,6 +65,7 @@ export interface FunctionTransformerOptions extends VisitorOptions { parameterTransformer: ParameterTransformer; returnTransformer: ReturnTransformer; signatureTransformer: SignatureTransformer; + useCache?: boolean; } export class FunctionTransformer extends AbstractVisitor { @@ -71,6 +73,7 @@ export class FunctionTransformer extends AbstractVisitor { private readonly parameterTransformer: ParameterTransformer; private readonly returnTransformer: ReturnTransformer; private readonly signatureTransformer: SignatureTransformer; + private readonly useCache: boolean; /* Tracking whether should import `__memo_context_type` and `__memo_id_type` */ private modified = false; @@ -81,6 +84,7 @@ export class FunctionTransformer extends AbstractVisitor { this.parameterTransformer = options.parameterTransformer; this.returnTransformer = options.returnTransformer; this.signatureTransformer = options.signatureTransformer; + this.useCache = !!options.useCache; } private scopes: ScopeInfo[] = []; @@ -652,7 +656,33 @@ export class FunctionTransformer extends AbstractVisitor { ); } + private visitorWithCache(beforeChildren: arkts.AstNode): arkts.AstNode { + const node = this.visitEachChild(beforeChildren); + if (arkts.NodeCache.getInstance().has(node)) { + const value = arkts.NodeCache.getInstance().get(node)!; + if (rewriteByType.has(value.type)) { + // console.log(`[CAN MEMO] type: ${value.type} node: `, node.dumpSrc()); + const newNode = rewriteByType.get(value.type)!(node, value.metadata); + this.modified = true; + return newNode + } else { + console.log(`[CANNOT MEMO] type: ${value.type} node: `, node.dumpSrc()); + } + } + // else if (node.dumpSrc().startsWith('@memo() ((gensym%%_1?:')) { + // const type = arkts.arktsGlobal.generatedEs2panda._AstNodeTypeConst(arkts.arktsGlobal.context, node.peer); + // console.log(`[CANNOT FIND MEMO] ptr: ${node.peer}, type: ${type}, node: `, node.dumpSrc()); + // } + if (arkts.isEtsScript(node) && this.modified) { + factory.createContextTypesImportDeclaration(this.program); + } + return node; + } + visitor(beforeChildren: arkts.AstNode): arkts.AstNode { + if (this.useCache) { + return this.visitorWithCache(beforeChildren); + } this.enter(beforeChildren); const node = this.visitEachChild(beforeChildren); this.exit(beforeChildren); diff --git a/arkui-plugins/memo-plugins/index.ts b/arkui-plugins/memo-plugins/index.ts index aa5c2de65..7cd4a249b 100644 --- a/arkui-plugins/memo-plugins/index.ts +++ b/arkui-plugins/memo-plugins/index.ts @@ -45,6 +45,7 @@ export function unmemoizeTransform(): Plugins { ); arkts.Performance.getInstance().createEvent('memo-checked'); + // arkts.NodeCache.getInstance().visualize(); const positionalIdTracker = new PositionalIdTracker(arkts.getFileName(), false); const parameterTransformer = new ParameterTransformer({ @@ -57,6 +58,7 @@ export function unmemoizeTransform(): Plugins { parameterTransformer, returnTransformer, signatureTransformer, + useCache: arkts.NodeCache.getInstance().isCollected() }); const programVisitor = new ProgramVisitor({ @@ -70,6 +72,8 @@ export function unmemoizeTransform(): Plugins { program = programVisitor.programVisitor(program); script = program.astNode; + arkts.NodeCache.getInstance().clear(); + arkts.Performance.getInstance().stopEvent('memo-checked', false); debugLog('[AFTER MEMO SCRIPT] script: ', script.dumpSrc()); diff --git a/arkui-plugins/memo-plugins/memo-cache-factory.ts b/arkui-plugins/memo-plugins/memo-cache-factory.ts new file mode 100644 index 000000000..6d72da120 --- /dev/null +++ b/arkui-plugins/memo-plugins/memo-cache-factory.ts @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as arkts from '@koalaui/libarkts'; +import { factory } from './memo-factory'; +import { + filterMemoSkipParams, + findLocalReturnTypeFromTypeAnnotation, + findUnmemoizedScopeInFunctionBody, + fixGensymParams, + getFunctionParamsBeforeUnmemoized, + hasMemoEntryAnnotation, + isVoidType, + mayAddLastReturn, + parametrizedNodeHasReceiver, + ParamInfo, + PositionalIdTracker, +} from './utils'; + +export class cacheFactory { + static rewriteUnionType(node: arkts.ETSUnionType, metadata?: arkts.AstNodeCacheValueMetadata): arkts.ETSUnionType { + // console.log("[rewriteUnionType] node BEFORE: ", node.dumpSrc()); + const newNode = arkts.factory.updateUnionType( + node, + node.types.map((t) => { + if (arkts.isETSFunctionType(t)) { + return cacheFactory.rewriteFunctionType(t, metadata); + } else if (arkts.isETSUnionType(t)) { + return cacheFactory.rewriteUnionType(t, metadata); + } + return t; + }) + ); + // console.log("[rewriteUnionType] node AFTER: ", newNode.dumpSrc()); + return newNode; + } + + static rewriteFunctionType( + node: arkts.ETSFunctionType, + metadata?: arkts.AstNodeCacheValueMetadata + ): arkts.ETSFunctionType { + // console.log("[rewriteFunctionType] node BEFORE: ", node.dumpSrc()); + const hasReceiver = metadata?.hasReceiver ?? parametrizedNodeHasReceiver(node); + const newNode = factory.updateFunctionTypeWithMemoParameters(node, hasReceiver); + // console.log("[rewriteFunctionType] node AFTER: ", newNode.dumpSrc()); + return newNode; + } + + /** + * @internal + */ + static rewriteType( + node: arkts.TypeNode | undefined, + metadata?: arkts.AstNodeCacheValueMetadata + ): arkts.TypeNode | undefined { + let newNodeType = node; + if (!!newNodeType && arkts.isETSFunctionType(newNodeType)) { + newNodeType = cacheFactory.rewriteFunctionType(newNodeType, metadata); + } else if (!!newNodeType && arkts.isETSUnionType(newNodeType)) { + newNodeType = cacheFactory.rewriteUnionType(newNodeType, metadata); + } + return newNodeType; + } + + static rewriteTypeAlias( + node: arkts.TSTypeAliasDeclaration, + metadata?: arkts.AstNodeCacheValueMetadata + ): arkts.TSTypeAliasDeclaration { + if (!node.typeAnnotation) { + return node; + } + const newNodeType = cacheFactory.rewriteType(node.typeAnnotation); + return arkts.factory.updateTSTypeAliasDeclaration(node, node.id, node.typeParams, newNodeType); + } + + static rewriteParameter( + node: arkts.ETSParameterExpression, + metadata?: arkts.AstNodeCacheValueMetadata + ): arkts.ETSParameterExpression { + if (!node.type && !node.initializer) { + return node; + } + node.type = cacheFactory.rewriteType(node.type as arkts.TypeNode); + return arkts.factory.updateParameterDeclaration(node, node.identifier, node.initializer); + } + + static rewriteProperty(node: arkts.Property, metadata?: arkts.AstNodeCacheValueMetadata): arkts.Property { + if (!node.value || !arkts.isArrowFunctionExpression(node.value)) { + return node; + } + return arkts.factory.updateProperty(node, node.key, cacheFactory.rewriteArrowFunction(node.value, metadata)); + } + + static rewriteClassProperty( + node: arkts.ClassProperty, + metadata?: arkts.AstNodeCacheValueMetadata + ): arkts.ClassProperty { + const newType = !!node.typeAnnotation ? cacheFactory.rewriteType(node.typeAnnotation) : undefined; + const newValue = + !!node.value && arkts.isArrowFunctionExpression(node.value) + ? cacheFactory.rewriteArrowFunction(node.value, metadata) + : node.value; + return arkts.factory.updateClassProperty(node, node.key, newValue, newType, node.modifiers, node.isComputed); + } + + static rewriteArrowFunction( + node: arkts.ArrowFunctionExpression, + metadata?: arkts.AstNodeCacheValueMetadata, + expectReturn?: arkts.TypeNode + ): arkts.ArrowFunctionExpression { + // console.log("[rewriteArrowFunction] node BEFORE: ", node.dumpSrc()); + const newNode = arkts.factory.updateArrowFunction( + node, + cacheFactory.rewriteScriptFunction(node.scriptFunction, metadata, expectReturn) + ); + // console.log("[rewriteArrowFunction] node AFTER: ", newNode.dumpSrc()); + return newNode; + } + + /** + * @internal + */ + static rewriteScriptFunctionBody( + node: arkts.ScriptFunction, + body: arkts.BlockStatement, + callName?: string, + hasReceiver?: boolean, + expectReturn?: arkts.TypeNode + ): arkts.BlockStatement { + // console.log("[REWRITE SCRIPT FUNCTION] node: ", node.dumpSrc()); + const _hasReceiver = hasReceiver ?? node.hasReceiver; + const _callName = callName ?? node.id?.name; + const parameters = getFunctionParamsBeforeUnmemoized(node.params, _hasReceiver); + const filteredParameters = filterMemoSkipParams(parameters); + const declaredParams: ParamInfo[] = filteredParameters.map((p) => { + const param = p as arkts.ETSParameterExpression; + return { ident: param.identifier, param }; + }); + const _gensymCount = fixGensymParams(declaredParams, body); + if (findUnmemoizedScopeInFunctionBody(body, _gensymCount)) { + return body; + } + const returnType = + node.returnTypeAnnotation ?? + expectReturn ?? + arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID); + const _isVoidReturn = isVoidType(returnType); + const scopeDeclaration = factory.createScopeDeclaration( + returnType, + PositionalIdTracker.getInstance(arkts.getFileName()).id(_callName), + declaredParams.length + ); + const memoParametersDeclaration = node.params.length + ? factory.createMemoParameterDeclaration(declaredParams.map((p) => p.ident.name)) + : undefined; + const syntheticReturnStatement = factory.createSyntheticReturnStatement(false); // TODO: find isStableThis + const unchangedCheck = factory.createIfStatementWithSyntheticReturnStatement( + syntheticReturnStatement, + _isVoidReturn + ); + const lastReturn = mayAddLastReturn(body) + ? factory.createWrappedReturnStatement(factory.createRecacheCall(), _isVoidReturn) + : undefined; + return arkts.factory.updateBlock(body, [ + ...body.statements.slice(0, _gensymCount), + scopeDeclaration, + ...(!!memoParametersDeclaration ? [memoParametersDeclaration] : []), + unchangedCheck, + ...body.statements.slice(_gensymCount), + ...(!!lastReturn ? [lastReturn] : []), + ]); + } + + static rewriteScriptFunction( + node: arkts.ScriptFunction, + metadata?: arkts.AstNodeCacheValueMetadata, + expectReturn?: arkts.TypeNode + ): arkts.ScriptFunction { + // console.log("[rewriteScriptFunction] node BEFORE: ", node.dumpSrc()); + const _callName = metadata?.callName; + const _hasReceiver = metadata?.hasReceiver ?? node.hasReceiver; + const _isSetter = !!metadata?.isSetter; + const _isGetter = !!metadata?.isGetter; + const _hasMemoEntry = !!metadata?.hasMemoEntry; + const _hasMemoIntrinsic = !!metadata?.hasMemoIntrinsic; + const isDecl = arkts.hasModifierFlag(node, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE); + // console.log('[rewriteScriptFunction] node: ', node.dumpSrc()); + // console.log('[rewriteScriptFunction] _isSetter: ', _isSetter); + // console.log('[rewriteScriptFunction] _isGetter: ', _isGetter); + let newParams: readonly arkts.Expression[] = node.params; + if (!_isSetter && !_isGetter) { + newParams = factory.createHiddenParameterIfNotAdded(node.params, node.hasReceiver); + } else if (_isSetter && node.params.length > 0) { + if (_hasReceiver && node.params.length === 2) { + newParams = [ + node.params.at(0)!, + cacheFactory.rewriteParameter(node.params.at(1)! as arkts.ETSParameterExpression), + ]; + } else { + newParams = [cacheFactory.rewriteParameter(node.params.at(0)! as arkts.ETSParameterExpression)]; + // console.log('[rewriteScriptFunction] SETTER param.at(0): ', newParams.at(0)!.dumpSrc()); + } + } + let newReturnType: arkts.TypeNode | undefined; + if (!!node.returnTypeAnnotation && _isGetter) { + newReturnType = cacheFactory.rewriteType(node.returnTypeAnnotation, { hasReceiver: _hasReceiver }); + } else { + newReturnType = node.returnTypeAnnotation; + } + // const newParams = !isSetter && !isGetter ? factory.createHiddenParameterIfNotAdded(node.params, node.hasReceiver) : node.params; + // const newReturnType = !!node.returnTypeAnnotation && isGetter ? cacheFactory.rewriteType(node.returnTypeAnnotation) : node.returnTypeAnnotation; // TODO: return type can be @memo + // TODO: getter/setter + const shouldRewriteBody = + !_isSetter && !_isGetter && !isDecl && !!node.body && arkts.isBlockStatement(node.body); + const newBody = shouldRewriteBody + ? cacheFactory.rewriteScriptFunctionBody(node, node.body, _callName, _hasReceiver, expectReturn) + : node.body; // TODO: change body for normal class + return arkts.factory.updateScriptFunction( + node, + newBody, + arkts.factory.createFunctionSignature(node.typeParams, newParams, newReturnType, _hasReceiver), + node.flags, + node.modifiers + ); + } + + static rewriteMethodDefinition( + node: arkts.MethodDefinition, + metadata?: arkts.AstNodeCacheValueMetadata + ): arkts.MethodDefinition { + // TODO: getter/setter + const isSetter = node.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; + const isGetter = node.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; + if (node.overloads.length > 0) { + node.setOverloads(node.overloads.map((o) => cacheFactory.rewriteMethodDefinition(o, metadata))); + } + return arkts.factory.updateMethodDefinition( + node, + node.kind, + node.name, + cacheFactory.rewriteScriptFunction(node.scriptFunction, { + callName: node.name.name, + ...metadata, + isSetter, + isGetter, + }), + node.modifiers, + false + ); + } + + static rewriteCallExpression( + node: arkts.CallExpression, + metadata?: arkts.AstNodeCacheValueMetadata + ): arkts.CallExpression { + let callName: string | undefined = metadata?.callName; + if (!!callName && arkts.isIdentifier(node.expression)) { + callName = node.expression.name; + } else if ( + !!callName && + arkts.isMemberExpression(node.expression) && + arkts.isIdentifier(node.expression.property) + ) { + callName = node.expression.property.name; + } + const hasReceiver = metadata?.hasReceiver; + // console.log(`[rewriteCallExpression BEFORE] node: `, node.dumpSrc()); + // console.log(`[rewriteCallExpression BEFORE] call isReceiver: `, hasReceiver); + const newNode = factory.insertHiddenArgumentsToCall( + node, + PositionalIdTracker.getInstance(arkts.getFileName()).id(callName), + hasReceiver + ); + // console.log(`[rewriteCallExpression AFTER] node: `, newNode.dumpSrc()); + return newNode; + } + + static rewriteIdentifier( + node: arkts.Identifier, + metadata?: arkts.AstNodeCacheValueMetadata + ): arkts.Identifier | arkts.MemberExpression { + if (!node.name.startsWith('gensym%%') && !node.name.startsWith('gensym__')) { + return factory.createMemoParameterAccess(node.name); + } + return node; + } + + static rewriteReturnStatement( + node: arkts.ReturnStatement, + metadata?: arkts.AstNodeCacheValueMetadata + ): arkts.ReturnStatement | arkts.BlockStatement { + return factory.createWrappedReturnStatement(factory.createRecacheCall(node.argument), !node.argument); + } + + static rewriteVariableDeclarator( + node: arkts.VariableDeclarator, + metadata?: arkts.AstNodeCacheValueMetadata + ): arkts.VariableDeclarator { + const expectReturnType = findLocalReturnTypeFromTypeAnnotation(node.name.typeAnnotation); + const variableType = cacheFactory.rewriteType(node.name.typeAnnotation); + let initializer = node.initializer; + if (!!initializer && arkts.isConditionalExpression(initializer) && !!initializer.alternate) { + let alternate = initializer.alternate; + if (arkts.isTSAsExpression(alternate)) { + alternate = arkts.factory.updateTSAsExpression( + alternate, + !!alternate.expr && arkts.isArrowFunctionExpression(alternate.expr) + ? cacheFactory.rewriteArrowFunction(alternate.expr, metadata, expectReturnType) + : alternate.expr, + cacheFactory.rewriteType(alternate.typeAnnotation), + alternate.isConst + ); + } else if (arkts.isArrowFunctionExpression(alternate)) { + alternate = cacheFactory.rewriteArrowFunction(alternate, metadata, expectReturnType); + } + initializer = arkts.factory.updateConditionalExpression( + initializer, + initializer.test, + initializer.consequent, + alternate + ); + } else if (!!initializer && arkts.isArrowFunctionExpression(initializer)) { + initializer = cacheFactory.rewriteArrowFunction(initializer, metadata, expectReturnType) + } + return arkts.factory.updateVariableDeclarator( + node, + node.flag, + arkts.factory.updateIdentifier(node.name, node.name.name, variableType), + initializer + ); + } +} + +export const rewriteByType = new Map arkts.AstNode>([ + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_UNION_TYPE, cacheFactory.rewriteUnionType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_FUNCTION_TYPE, cacheFactory.rewriteFunctionType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_ALIAS_DECLARATION, cacheFactory.rewriteTypeAlias], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_PARAMETER_EXPRESSION, cacheFactory.rewriteParameter], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_PROPERTY, cacheFactory.rewriteClassProperty], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ARROW_FUNCTION_EXPRESSION, cacheFactory.rewriteArrowFunction], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_SCRIPT_FUNCTION, cacheFactory.rewriteScriptFunction], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_METHOD_DEFINITION, cacheFactory.rewriteMethodDefinition], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CALL_EXPRESSION, cacheFactory.rewriteCallExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_IDENTIFIER, cacheFactory.rewriteIdentifier], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_RETURN_STATEMENT, cacheFactory.rewriteReturnStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_VARIABLE_DECLARATOR, cacheFactory.rewriteVariableDeclarator], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_PROPERTY, cacheFactory.rewriteProperty], +]); diff --git a/arkui-plugins/memo-plugins/memo-factory.ts b/arkui-plugins/memo-plugins/memo-factory.ts index 37eed959b..a9d43b591 100644 --- a/arkui-plugins/memo-plugins/memo-factory.ts +++ b/arkui-plugins/memo-plugins/memo-factory.ts @@ -17,12 +17,12 @@ import * as arkts from '@koalaui/libarkts'; import { fixGensymParams, buildeParamInfos, - isUnmemoizedInFunction, + isUnmemoizedInFunctionParams, mayAddLastReturn, ParamInfo, ReturnTypeInfo, RuntimeNames, - parametrizedNodeHasReceiver + parametrizedNodeHasReceiver, } from './utils'; import { moveToFront } from '../common/arkts-utils'; @@ -88,7 +88,7 @@ export class factory { hasReceiver: boolean = false ): readonly arkts.Expression[] { const _params = params ?? []; - if (isUnmemoizedInFunction(_params)) { + if (isUnmemoizedInFunctionParams(_params)) { return _params; } let newParams: arkts.Expression[] = [...factory.createHiddenParameters(), ..._params]; @@ -97,12 +97,15 @@ export class factory { } return newParams; } - static updateFunctionTypeWithMemoParameters(type: arkts.ETSFunctionType): arkts.ETSFunctionType { + static updateFunctionTypeWithMemoParameters( + type: arkts.ETSFunctionType, + hasReceiver: boolean = false + ): arkts.ETSFunctionType { return arkts.factory.updateFunctionType( type, arkts.factory.createFunctionSignature( undefined, - factory.createHiddenParameterIfNotAdded(type.params), + factory.createHiddenParameterIfNotAdded(type.params, hasReceiver), type.returnType, false ), @@ -298,6 +301,18 @@ export class factory { returnStatement ); } + static createWrappedReturnStatement( + argument: arkts.Expression, + isReturnVoid: boolean + ): arkts.ReturnStatement | arkts.BlockStatement { + if (!isReturnVoid) { + return arkts.factory.createReturnStatement(argument); + } + return arkts.factory.createBlock([ + arkts.factory.createExpressionStatement(argument), + arkts.factory.createReturnStatement(), + ]); + } // Compute static createLambdaWrapper(node: arkts.Expression): arkts.ArrowFunctionExpression { @@ -389,11 +404,13 @@ export class factory { static insertHiddenArgumentsToCall( node: arkts.CallExpression, - hash: arkts.NumberLiteral | arkts.StringLiteral + hash: arkts.NumberLiteral | arkts.StringLiteral, + hasReceiver?: boolean ): arkts.CallExpression { - return arkts.factory.updateCallExpression(node, node.expression, node.typeArguments, [ - ...factory.createHiddenArguments(hash), - ...node.arguments, - ]); + let updatedArguments = [...factory.createHiddenArguments(hash), ...node.arguments]; + if (!!hasReceiver) { + updatedArguments = moveToFront(updatedArguments, 2); + } + return arkts.factory.updateCallExpression(node, node.expression, node.typeArguments, updatedArguments); } } diff --git a/arkui-plugins/memo-plugins/parameter-transformer.ts b/arkui-plugins/memo-plugins/parameter-transformer.ts index 061e5b80b..8aef0822c 100644 --- a/arkui-plugins/memo-plugins/parameter-transformer.ts +++ b/arkui-plugins/memo-plugins/parameter-transformer.ts @@ -22,8 +22,7 @@ import { findReturnTypeFromTypeAnnotation, isMemoETSParameterExpression, isMemoParametersDeclaration, - isUnmemoizedInFunction, - isVoidType, + isUnmemoizedInFunctionParams, MemoInfo, ParamInfo, PositionalIdTracker, @@ -140,7 +139,7 @@ export class ParameterTransformer extends AbstractVisitor { if (!scriptFunction.body || !arkts.isBlockStatement(scriptFunction.body)) { return initializer; } - if (isUnmemoizedInFunction(scriptFunction.params)) { + if (isUnmemoizedInFunctionParams(scriptFunction.params)) { return initializer; } const returnTypeInfo: ReturnTypeInfo = buildReturnTypeInfo( diff --git a/arkui-plugins/memo-plugins/utils.ts b/arkui-plugins/memo-plugins/utils.ts index 51a17d032..7237af5a3 100644 --- a/arkui-plugins/memo-plugins/utils.ts +++ b/arkui-plugins/memo-plugins/utils.ts @@ -41,7 +41,7 @@ export enum RuntimeNames { SCOPE = '__memo_scope', THIS = 'this', VALUE = 'value', - EQUAL_T = '=t' + EQUAL_T = '=t', } export interface ReturnTypeInfo { @@ -69,6 +69,15 @@ export class PositionalIdTracker { // Global for the whole program. static callCount: number = 0; + private static instance: PositionalIdTracker; + + static getInstance(fileName: string): PositionalIdTracker { + if (!this.instance) { + this.instance = new PositionalIdTracker(fileName); + } + return this.instance; + } + // Set `stable` to true if you want to have more predictable values. // For example for tests. // Don't use it in production! @@ -485,6 +494,21 @@ export function findReturnTypeFromTypeAnnotation( return undefined; } +export function findLocalReturnTypeFromTypeAnnotation( + typeAnnotation: arkts.AstNode | undefined +): arkts.TypeNode | undefined { + if (!typeAnnotation) { + return undefined; + } + if (arkts.isETSFunctionType(typeAnnotation)) { + return typeAnnotation.returnType; + } + if (arkts.isETSUnionType(typeAnnotation)) { + return typeAnnotation.types.find((type) => arkts.isETSFunctionType(type))?.returnType; + } + return undefined; +} + export function getDeclResolveAlias(node: arkts.AstNode): arkts.AstNode | undefined { const decl = arkts.getDecl(node); if (!!decl && !!decl.parent && arkts.isIdentifier(decl) && arkts.isVariableDeclarator(decl.parent)) { @@ -499,11 +523,17 @@ export function getDeclResolveAlias(node: arkts.AstNode): arkts.AstNode | undefi } export function mayAddLastReturn(node: arkts.BlockStatement): boolean { - return ( - node.statements.length === 0 || - (!arkts.isReturnStatement(node.statements[node.statements.length - 1]) && - !arkts.isThrowStatement(node.statements[node.statements.length - 1])) - ); + if (node.statements.length === 0) { + return true; + } + const lastStatement = node.statements[node.statements.length - 1]; + if (arkts.isBlockStatement(lastStatement)) { + return mayAddLastReturn(lastStatement); + } + if (arkts.isReturnStatement(lastStatement) || arkts.isThrowStatement(lastStatement)) { + return false; + } + return true; } export function fixGensymParams(params: ParamInfo[], body: arkts.BlockStatement): number { @@ -527,16 +557,52 @@ export function fixGensymParams(params: ParamInfo[], body: arkts.BlockStatement) return gensymParamsCount; } -export function isUnmemoizedInFunction(params?: readonly arkts.Expression[]): boolean { +export function isMemoContextParamAdded(param: arkts.Expression): boolean { + return arkts.isEtsParameterExpression(param) && param.identifier.name === RuntimeNames.CONTEXT; +} + +export function isMemoIdParamAdded(param: arkts.Expression): boolean { + return arkts.isEtsParameterExpression(param) && param.identifier.name === RuntimeNames.ID; +} + +export function isUnmemoizedInFunctionParams(params?: readonly arkts.Expression[], hasReceiver?: boolean): boolean { const _params = params ?? []; - const first = _params.at(0); - const isContextAdded = - !!first && arkts.isEtsParameterExpression(first) && first.identifier.name === RuntimeNames.CONTEXT; - const second = _params.at(1); - const isIdAdded = !!second && arkts.isEtsParameterExpression(second) && second.identifier.name === RuntimeNames.ID; + const startIndex = hasReceiver ? 1 : 0; + const isContextAdded = !!_params.at(startIndex) && isMemoContextParamAdded(_params.at(startIndex)!); + const isIdAdded = !!_params.at(startIndex + 1) && isMemoIdParamAdded(_params.at(startIndex + 1)!); return isContextAdded && isIdAdded; } +export function getFunctionParamsBeforeUnmemoized( + params?: readonly arkts.Expression[], + hasReceiver?: boolean +): readonly arkts.Expression[] { + const _params = params ?? []; + if (isUnmemoizedInFunctionParams(_params, hasReceiver)) { + if (!!hasReceiver) { + return [_params.at(0)!, ..._params.slice(3)]; + } + return _params.slice(2); + } + return _params; +} + +export function findUnmemoizedScopeInFunctionBody( + body: arkts.BlockStatement, + gensymCount: number = 0 +): boolean { + const startIndex = gensymCount; + if (body.statements.length < startIndex + 1) { + return false; + } + const statement = body.statements.at(startIndex)!; + if (!arkts.isVariableDeclaration(statement)) { + return false; + } + const declarator = statement.declarators.at(0)!; + return declarator.name.name === RuntimeNames.SCOPE; +} + export function buildReturnTypeInfo( returnType: arkts.TypeNode | undefined, isMemo?: boolean, @@ -580,3 +646,7 @@ function isThisParam(node: arkts.Expression | undefined): boolean { } return node.identifier?.isReceiver ?? false; } + +export function filterMemoSkipParams(params: readonly arkts.Expression[]): readonly arkts.Expression[] { + return params.filter((p) => !hasMemoSkipAnnotation(p as arkts.ETSParameterExpression)); +} diff --git a/arkui-plugins/test/demo/mock/memo/lambdas/with-receiver.ets b/arkui-plugins/test/demo/mock/memo/lambdas/with-receiver.ets index b767256cc..556aba2b5 100644 --- a/arkui-plugins/test/demo/mock/memo/lambdas/with-receiver.ets +++ b/arkui-plugins/test/demo/mock/memo/lambdas/with-receiver.ets @@ -19,16 +19,23 @@ class Person { constructor() {} } +@memo function fullName(this: Person, @memo arg?: () => void): void { return; } class A {} +@memo type F1 = (this: A, @memo arg?: () => void) => void; + +@memo type F2 = (a: A, @memo arg?: () => void) => void; +@memo function foo(this: A, @memo arg?: () => void): void {} + +@memo function goo(a: A, @memo arg?: () => void): void {} @memo @@ -37,11 +44,7 @@ function goo(a: A, @memo arg?: () => void): void {} x.fullName(() => {}); let f1: F1 = foo; - f1 = goo; - let f2: F2 = goo; - f2 = foo; - f1 = f2; let a = new A(); a.f1(() => {}); diff --git a/arkui-plugins/test/demo/mock/memo/methods/non-void-method.ets b/arkui-plugins/test/demo/mock/memo/methods/non-void-method.ets index fb3868ecc..4e8053d50 100644 --- a/arkui-plugins/test/demo/mock/memo/methods/non-void-method.ets +++ b/arkui-plugins/test/demo/mock/memo/methods/non-void-method.ets @@ -17,6 +17,7 @@ import { memo, __memo_context_type, __memo_id_type } from "arkui.stateManagement @Retention({policy:"SOURCE"}) @interface memo_intrinsic {} @Retention({policy:"SOURCE"}) @interface memo_entry {} +@Retention({policy:"SOURCE"}) @interface memo_skip {} export declare function __context(): __memo_context_type export declare function __id(): __memo_id_type @@ -55,6 +56,12 @@ class Test { return entry() } } + + @memo memo_skip_args(arg1: number, @memo_skip arg2: string, @memo_skip arg3: @memo () => void): string { + let a = arg1; + arg3(); + return arg2; + } } class Use { diff --git a/arkui-plugins/test/ut/common/annotation.test.ts b/arkui-plugins/test/ut/common/annotation.test.ts index d7640d7af..663454255 100644 --- a/arkui-plugins/test/ut/common/annotation.test.ts +++ b/arkui-plugins/test/ut/common/annotation.test.ts @@ -17,13 +17,14 @@ import * as arkts from '@koalaui/libarkts'; import path from 'path'; import { PluginTester } from '../../utils/plugin-tester'; import { BuildConfig, PluginTestContext } from '../../utils/shared-types'; -import { recheck } from '../../utils/plugins'; import { mockBuildConfig } from '../../utils/artkts-config'; import { parseDumpSrc } from '../../utils/parse-string'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../utils/path-config'; import { annotation } from '../../../common/arkts-utils'; import { PluginContext, Plugins } from '../../../common/plugin-context'; import { AbstractVisitor } from '../../../common/abstract-visitor'; +import { ProgramVisitor } from '../../../common/program-visitor'; +import { EXTERNAL_SOURCE_PREFIX_NAMES } from '../../../common/predefines'; const COMMON_UTILS_DIR_PATH: string = 'common-utils'; @@ -122,7 +123,14 @@ function addAnnotationTransform(this: PluginContext): arkts.EtsScript | undefine if (!!contextPtr) { let program = arkts.getOrUpdateGlobalContext(contextPtr).program; const annotationAdder = new AnnotationVisitor(); - annotationAdder.visitor(program.astNode); + const programVisitor = new ProgramVisitor({ + pluginName: addAnnotationTransform.name, + state: arkts.Es2pandaContextState.ES2PANDA_STATE_ERROR, // ignored + visitors: [annotationAdder], + skipPrefixNames: EXTERNAL_SOURCE_PREFIX_NAMES, + pluginContext: this, + }); + program = programVisitor.programVisitor(program); script = program.astNode; return script; } @@ -135,7 +143,14 @@ function removeAnnotationTransform(this: PluginContext): arkts.EtsScript | undef if (!!contextPtr) { let program = arkts.getOrUpdateGlobalContext(contextPtr).program; const annotationAdder = new AnnotationVisitor(true); - annotationAdder.visitor(program.astNode); + const programVisitor = new ProgramVisitor({ + pluginName: addAnnotationTransform.name, + state: arkts.Es2pandaContextState.ES2PANDA_STATE_ERROR, // ignored + visitors: [annotationAdder], + skipPrefixNames: EXTERNAL_SOURCE_PREFIX_NAMES, + pluginContext: this, + }); + program = programVisitor.programVisitor(program); script = program.astNode; return script; } @@ -198,7 +213,7 @@ function testCheckAnnotation(this: PluginTestContext): void { pluginTester.run( 'annotation', - [addAnnotation, removeAnnotation, recheck], + [addAnnotation, removeAnnotation], { 'parsed:add-annotation': [testParseAnnotation], 'checked:add-annotation': [testCheckAnnotation], diff --git a/arkui-plugins/test/ut/memo-plugins/function-declarations/argument-call.test.ts b/arkui-plugins/test/ut/memo-plugins/function-declarations/argument-call.test.ts index c24e48d0a..1506b9ca1 100644 --- a/arkui-plugins/test/ut/memo-plugins/function-declarations/argument-call.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/function-declarations/argument-call.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const FUNCTION_DIR_PATH: string = 'memo/functions'; @@ -32,7 +32,7 @@ const expectedScript: string = ` import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} -function memo_arg_call(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg1: number, arg2: ((x: number)=> number), @memo() arg3: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, x: number)=> number), arg4?: ((x: number)=> number), @memo() arg5?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, x: number)=> number)): void { +@memo() function memo_arg_call(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg1: number, arg2: ((x: number)=> number), @memo() arg3: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, x: number)=> number), arg4?: ((x: number)=> number), @memo() arg5?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, x: number)=> number)) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 5); const __memo_parameter_arg1 = __memo_scope.param(0, arg1), __memo_parameter_arg2 = __memo_scope.param(1, arg2), __memo_parameter_arg3 = __memo_scope.param(2, arg3), __memo_parameter_arg4 = __memo_scope.param(3, arg4), __memo_parameter_arg5 = __memo_scope.param(4, arg5); if (__memo_scope.unchanged) { @@ -50,7 +50,7 @@ function memo_arg_call(__memo_context: __memo_context_type, __memo_id: __memo_id return; } } -function memo_arg_call_with_lowering(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg1: number, arg4?: ((x: number)=> number), @memo() arg5?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, x: number)=> number)): void { +@memo() function memo_arg_call_with_lowering(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg1: number, arg4?: ((x: number)=> number), @memo() arg5?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, x: number)=> number)) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 3); const __memo_parameter_arg1 = __memo_scope.param(0, arg1), __memo_parameter_arg4 = __memo_scope.param(1, arg4), __memo_parameter_arg5 = __memo_scope.param(2, arg5); if (__memo_scope.unchanged) { @@ -70,9 +70,9 @@ function memo_arg_call_with_lowering(__memo_context: __memo_context_type, __memo return; } } -function args_with_default_values(__memo_context: __memo_context_type, __memo_id: __memo_id_type, gensym%%_?: int, @memo() gensym%%_?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> int), gensym%%_?: int, arg4?: int): void { +@memo() function args_with_default_values(__memo_context: __memo_context_type, __memo_id: __memo_id_type, gensym%%_?: int, @memo() gensym%%_?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> int), gensym%%_?: int, arg4?: int): void { let arg1: int = (((gensym%%_) !== (undefined)) ? gensym%%_ : (10 as int)); - let arg2: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> int) = (((gensym%%_) !== (undefined)) ? gensym%%_ : (((__memo_context: __memo_context_type, __memo_id: __memo_id_type): int => { + let arg2: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> int) = (((gensym%%_) !== (undefined)) ? gensym%%_ : (((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { return __memo_scope.cached; @@ -101,7 +101,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform argument calls in functions', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/function-declarations/declare-and-call.test.ts b/arkui-plugins/test/ut/memo-plugins/function-declarations/declare-and-call.test.ts index 23d779dfb..a1eb3091e 100644 --- a/arkui-plugins/test/ut/memo-plugins/function-declarations/declare-and-call.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/function-declarations/declare-and-call.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const FUNCTION_DIR_PATH: string = 'memo/functions'; @@ -34,7 +34,7 @@ const expectedScript: string = ` import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} -@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { +@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -47,7 +47,7 @@ function main() {} } }); @memo() function funcA(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void -function funcB(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { +@memo() function funcB(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -60,7 +60,7 @@ function funcB(__memo_context: __memo_context_type, __memo_id: __memo_id_type): } } class A { - public foo(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public foo(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -82,7 +82,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform declare functions and calls', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/function-declarations/inner-functions.test.ts b/arkui-plugins/test/ut/memo-plugins/function-declarations/inner-functions.test.ts index a921f408a..a73b950b6 100644 --- a/arkui-plugins/test/ut/memo-plugins/function-declarations/inner-functions.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/function-declarations/inner-functions.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const FUNCTION_DIR_PATH: string = 'memo/functions'; @@ -34,7 +34,7 @@ const expectedScript: string = ` import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} -function foo(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { +@memo() function foo(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -45,13 +45,13 @@ function foo(__memo_context: __memo_context_type, __memo_id: __memo_id_type): vo return; } } -function bar(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { +@memo() function bar(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; return; } - const qux = @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + const qux = @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -70,14 +70,14 @@ function bar(__memo_context: __memo_context_type, __memo_id: __memo_id_type): vo } } class A { - public goo(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public goo(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { - __memo_scope.cached; - return; + __memo_scope.cached; + return; } let func = (() => {}); - let func2 = @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + let func2 = @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -104,7 +104,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform inner functions', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/function-declarations/non-void-return-type.test.ts b/arkui-plugins/test/ut/memo-plugins/function-declarations/non-void-return-type.test.ts index 8d94ec992..c59453f5f 100644 --- a/arkui-plugins/test/ut/memo-plugins/function-declarations/non-void-return-type.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/function-declarations/non-void-return-type.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const FUNCTION_DIR_PATH: string = 'memo/functions'; @@ -34,28 +34,28 @@ const expectedScript: string = ` import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} -function funcNum(__memo_context: __memo_context_type, __memo_id: __memo_id_type): number { +@memo() function funcNum(__memo_context: __memo_context_type, __memo_id: __memo_id_type): number { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { return __memo_scope.cached; } return __memo_scope.recache(1); } -function funcStr(__memo_context: __memo_context_type, __memo_id: __memo_id_type): string { +@memo() function funcStr(__memo_context: __memo_context_type, __memo_id: __memo_id_type): string { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { return __memo_scope.cached; } return __memo_scope.recache(\"1\"); } -function funcBool(__memo_context: __memo_context_type, __memo_id: __memo_id_type): boolean { +@memo() function funcBool(__memo_context: __memo_context_type, __memo_id: __memo_id_type): boolean { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { return __memo_scope.cached; } return __memo_scope.recache(false); } -function funcA(__memo_context: __memo_context_type, __memo_id: __memo_id_type): A { +@memo() function funcA(__memo_context: __memo_context_type, __memo_id: __memo_id_type): A { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { return __memo_scope.cached; @@ -64,21 +64,21 @@ function funcA(__memo_context: __memo_context_type, __memo_id: __memo_id_type): str: \"1\", }); } -function funcB(__memo_context: __memo_context_type, __memo_id: __memo_id_type): B { +@memo() function funcB(__memo_context: __memo_context_type, __memo_id: __memo_id_type): B { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { return __memo_scope.cached; } return __memo_scope.recache(((str: string) => {})); } -function funcC(__memo_context: __memo_context_type, __memo_id: __memo_id_type): C { +@memo() function funcC(__memo_context: __memo_context_type, __memo_id: __memo_id_type): C { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { return __memo_scope.cached; } return __memo_scope.recache(new C(\"1\")); } -function funcD(__memo_context: __memo_context_type, __memo_id: __memo_id_type): (()=> void) { +@memo() function funcD(__memo_context: __memo_context_type, __memo_id: __memo_id_type): (()=> void) { const __memo_scope = __memo_context.scope<(()=> void)>(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { return __memo_scope.cached; @@ -104,7 +104,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform functions with non-void return type', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/function-declarations/type-reference.test.ts b/arkui-plugins/test/ut/memo-plugins/function-declarations/type-reference.test.ts index 0490eef81..eca62211f 100644 --- a/arkui-plugins/test/ut/memo-plugins/function-declarations/type-reference.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/function-declarations/type-reference.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const FUNCTION_DIR_PATH: string = 'memo/functions'; @@ -35,12 +35,23 @@ import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} @memo() export function A(__memo_context: __memo_context_type, __memo_id: __memo_id_type): Attribute -function func(__memo_context: __memo_context_type, __memo_id: __memo_id_type): ItemBuilder { +@memo() function func(__memo_context: __memo_context_type, __memo_id: __memo_id_type): ItemBuilder { const __memo_scope = __memo_context.scope>(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { return __memo_scope.cached; } - return __memo_scope.recache(((__memo_context: __memo_context_type, __memo_id: __memo_id_type, item: Item): void => {})); + return __memo_scope.recache(((__memo_context: __memo_context_type, __memo_id: __memo_id_type, item: Item): void => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); + const __memo_parameter_item = __memo_scope.param(0, item); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + { + __memo_scope.recache(); + return; + } + })); } @memo() type ItemBuilder = ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, item: Item)=> void); interface Item { @@ -51,13 +62,13 @@ interface Attribute { @memo() each(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() itemGenerator: ItemBuilder): Attribute } class B { - public build(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public build(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; return; } - A(__memo_context, ((__memo_id) + ())).each(__memo_context, ((__memo_id) + ()), ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, ri: Item): void => { + A(__memo_context, ((__memo_id) + ())).each(__memo_context, ((__memo_id) + ()), ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, ri: Item) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_ri = __memo_scope.param(0, ri); if (__memo_scope.unchanged) { @@ -84,7 +95,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform functions with type reference', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/function-declarations/void-return-type.test.ts b/arkui-plugins/test/ut/memo-plugins/function-declarations/void-return-type.test.ts index b3e729785..fd2f3cb88 100644 --- a/arkui-plugins/test/ut/memo-plugins/function-declarations/void-return-type.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/function-declarations/void-return-type.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const FUNCTION_DIR_PATH: string = 'memo/functions'; @@ -34,7 +34,7 @@ const expectedScript: string = ` import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} -function func(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { +@memo() function func(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -53,7 +53,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform functions with void return type', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/lambda-literals/argument-call.test.ts b/arkui-plugins/test/ut/memo-plugins/lambda-literals/argument-call.test.ts index c94fe1dd9..18b3a26e3 100644 --- a/arkui-plugins/test/ut/memo-plugins/lambda-literals/argument-call.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/lambda-literals/argument-call.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const LAMBDA_DIR_PATH: string = 'memo/lambdas'; @@ -34,10 +34,20 @@ import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} ((arg: (()=> void)) => {})((() => {})); -((arg: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)) => {})(@memo() (() => {})); +((arg: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)) => {})(@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + { + __memo_scope.recache(); + return; + } +})); ((gensym%%_1?: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)) => { - let arg: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) = (((gensym%%_1) !== (undefined)) ? gensym%%_1 : (((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + let arg: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) = (((gensym%%_1) !== (undefined)) ? gensym%%_1 : (((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + (201676739)), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -48,7 +58,7 @@ function main() {} return; } }) as @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void))); -})(@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { +})(@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + (209782503)), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -60,14 +70,14 @@ function main() {} } })); -@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { +@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; return; } - @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, gensym%%_?: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void => { - let arg: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) = (((gensym%%_) !== (undefined)) ? gensym%%_ : (((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, gensym%%_?: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)) => { + let arg: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) = (((gensym%%_) !== (undefined)) ? gensym%%_ : (((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -88,7 +98,7 @@ function main() {} __memo_scope.recache(); return; } - })(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + })(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -105,13 +115,13 @@ function main() {} } }); -@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { +@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; return; } - let goo = @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, gensym%%_?: string): void => { + let goo = @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, gensym%%_?: string) => { let name: string = (((gensym%%_) !== (undefined)) ? gensym%%_ : (\"old\" as string)); const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_name = __memo_scope.param(0, name); @@ -133,7 +143,7 @@ function main() {} (() => { let foo = ((gensym%%_?: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)) => { - let arg: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) = (((gensym%%_) !== (undefined)) ? gensym%%_ : (((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + let arg: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) = (((gensym%%_) !== (undefined)) ? gensym%%_ : (((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -145,7 +155,7 @@ function main() {} } }) as @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void))); }); - foo(@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + foo(@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -165,7 +175,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform argument calls in lambdas', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/lambda-literals/function-with-receiver.test.ts b/arkui-plugins/test/ut/memo-plugins/lambda-literals/function-with-receiver.test.ts index 415423f6e..60d5c5985 100644 --- a/arkui-plugins/test/ut/memo-plugins/lambda-literals/function-with-receiver.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/lambda-literals/function-with-receiver.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; import { Plugins } from '../../../../common/plugin-context'; import { uiTransform } from '../../../../ui-plugins'; @@ -43,7 +43,7 @@ import { memo as memo } from "arkui.stateManagement.runtime"; function main() {} -function foo1(this: B, __memo_context: __memo_context_type, __memo_id: __memo_id_type, str: string): void { +@memo() function foo1(this: B, __memo_context: __memo_context_type, __memo_id: __memo_id_type, str: string): void { const __memo_scope = __memo_context.scope(((__memo_id) + (38567515)), 2); const __memo_parameter_this = __memo_scope.param(0, this), __memo_parameter_str = __memo_scope.param(1, str); if (__memo_scope.unchanged) { @@ -57,7 +57,7 @@ function foo1(this: B, __memo_context: __memo_context_type, __memo_id: __memo_id } } -function foo2(this: B, __memo_context: __memo_context_type, __memo_id: __memo_id_type, str: string): B { +@memo() function foo2(this: B, __memo_context: __memo_context_type, __memo_id: __memo_id_type, str: string): B { const __memo_scope = __memo_context.scope(((__memo_id) + (167482260)), 2); const __memo_parameter_this = __memo_scope.param(0, this), __memo_parameter_str = __memo_scope.param(1, str); if (__memo_scope.unchanged) { @@ -68,7 +68,7 @@ function foo2(this: B, __memo_context: __memo_context_type, __memo_id: __memo_id } class B { - public internal_call(__memo_context: __memo_context_type, __memo_id: __memo_id_type): B { + @memo() public internal_call(__memo_context: __memo_context_type, __memo_id: __memo_id_type): B { const __memo_scope = __memo_context.scope(((__memo_id) + (146437675)), 0); if (__memo_scope.unchanged) { return __memo_scope.cached; @@ -87,7 +87,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform lambdas about function with receiver feature', - [parsedTransform, memoNoRecheck, recheck], + [parsedTransform, beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/lambda-literals/trailing-lambdas.test.ts b/arkui-plugins/test/ut/memo-plugins/lambda-literals/trailing-lambdas.test.ts index ffb1c2cfa..b2465acc7 100644 --- a/arkui-plugins/test/ut/memo-plugins/lambda-literals/trailing-lambdas.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/lambda-literals/trailing-lambdas.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const LAMBDA_DIR_PATH: string = 'memo/lambdas'; @@ -34,7 +34,7 @@ const expectedScript: string = ` import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} -@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { +@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -44,7 +44,7 @@ function main() {} a.foo(__memo_context, ((__memo_id) + ()), (() => { console.log(); })); - a.goo(((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + a.goo(((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -56,7 +56,7 @@ function main() {} return; } })); - a.koo(__memo_context, ((__memo_id) + ()), ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + a.koo(__memo_context, ((__memo_id) + ()), ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -71,7 +71,7 @@ function main() {} bar(__memo_context, ((__memo_id) + ()), (() => { console.log(); })); - par(((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + par(((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -83,7 +83,7 @@ function main() {} return; } })); - kar(__memo_context, ((__memo_id) + ()), ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + kar(__memo_context, ((__memo_id) + ()), ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -100,7 +100,7 @@ function main() {} return; } }); -function bar(__memo_context: __memo_context_type, __memo_id: __memo_id_type, f?: (()=> void)): void { +@memo() function bar(__memo_context: __memo_context_type, __memo_id: __memo_id_type, f?: (()=> void)): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_f = __memo_scope.param(0, f); if (__memo_scope.unchanged) { @@ -113,7 +113,7 @@ function bar(__memo_context: __memo_context_type, __memo_id: __memo_id_type, f?: } } function par(f?: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void {} -function kar(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() f?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void { +@memo() function kar(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() f?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_f = __memo_scope.param(0, f); if (__memo_scope.unchanged) { @@ -126,7 +126,7 @@ function kar(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @me } } class A { - public foo(__memo_context: __memo_context_type, __memo_id: __memo_id_type, p?: (()=> void)): void { + @memo() public foo(__memo_context: __memo_context_type, __memo_id: __memo_id_type, p?: (()=> void)): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_p = __memo_scope.param(0, p); if (__memo_scope.unchanged) { @@ -139,7 +139,7 @@ class A { } } public goo(@memo() p?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void {} - public koo(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() p?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void { + @memo() public koo(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() p?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_p = __memo_scope.param(0, p); if (__memo_scope.unchanged) { @@ -161,7 +161,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform trailing lambdas', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/lambda-literals/void-lambda.test.ts b/arkui-plugins/test/ut/memo-plugins/lambda-literals/void-lambda.test.ts index f49946e13..37b1c1365 100644 --- a/arkui-plugins/test/ut/memo-plugins/lambda-literals/void-lambda.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/lambda-literals/void-lambda.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const LAMBDA_DIR_PATH: string = 'memo/lambdas'; @@ -65,7 +65,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform lambdas with void return type', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/lambda-literals/with-receiver.test.ts b/arkui-plugins/test/ut/memo-plugins/lambda-literals/with-receiver.test.ts index ce9477f75..748b1dd4b 100644 --- a/arkui-plugins/test/ut/memo-plugins/lambda-literals/with-receiver.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/lambda-literals/with-receiver.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const LAMBDA_DIR_PATH: string = 'memo/lambdas'; @@ -33,15 +33,17 @@ const pluginTester = new PluginTester('test memo lambda', buildConfig); const expectedScript: string = ` import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; import { memo as memo } from \"arkui.stateManagement.runtime\"; + function main() {} -@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + +@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; return; } let x = new Person(); - fullName(x, ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + fullName(x, __memo_context, ((__memo_id) + ()), ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -53,12 +55,9 @@ function main() {} } })); let f1: F1 = foo; - f1 = goo; let f2: F2 = goo; - f2 = foo; - f1 = f2; let a = new A(); - f1(a, ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + f1(a, __memo_context, ((__memo_id) + ()), ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -69,7 +68,7 @@ function main() {} return; } })); - f1(a, ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + f1(a, __memo_context, ((__memo_id) + ()), ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -80,7 +79,7 @@ function main() {} return; } })); - f2(a, ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + f2(__memo_context, ((__memo_id) + ()), a, ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -96,19 +95,56 @@ function main() {} return; } }); -function fullName(this: Person, @memo() arg?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void { - return; + +@memo() function fullName(this: Person, __memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() arg?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 2); + const __memo_parameter_this = __memo_scope.param(0, this), __memo_parameter_arg = __memo_scope.param(1, arg); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + { + __memo_scope.recache(); + return; + } +} + +@memo() function foo(this: A, __memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() arg?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 2); + const __memo_parameter_this = __memo_scope.param(0, this), __memo_parameter_arg = __memo_scope.param(1, arg); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + { + __memo_scope.recache(); + return; + } } -function foo(this: A, @memo() arg?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void {} -function goo(a: A, @memo() arg?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void {} + +@memo() function goo(__memo_context: __memo_context_type, __memo_id: __memo_id_type, a: A, @memo() arg?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 2); + const __memo_parameter_a = __memo_scope.param(0, a), __memo_parameter_arg = __memo_scope.param(1, arg); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + { + __memo_scope.recache(); + return; + } +} + class Person { public constructor() {} } + class A { public constructor() {} } -type F1 = ((this: A, @memo() arg?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void))=> void); -type F2 = ((a: A, @memo() arg?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void))=> void); + +@memo() type F1 = ((this: A, __memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() arg?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void))=> void); +@memo() type F2 = ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, a: A, @memo() arg?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void))=> void); `; function testMemoTransformer(this: PluginTestContext): void { @@ -117,7 +153,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform lambdas with receiver', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/method-declarations/argument-call.test.ts b/arkui-plugins/test/ut/memo-plugins/method-declarations/argument-call.test.ts index a4326ac1e..dfbc5547d 100644 --- a/arkui-plugins/test/ut/memo-plugins/method-declarations/argument-call.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/method-declarations/argument-call.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const METHOD_DIR_PATH: string = 'memo/methods'; @@ -33,7 +33,7 @@ import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} class Test { - public lambda_arg(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void { + @memo() public lambda_arg(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_arg = __memo_scope.param(0, arg); if (__memo_scope.unchanged) { @@ -45,7 +45,7 @@ class Test { return; } } - public lambda_arg_with_arg(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, value: string)=> string)): void { + @memo() public lambda_arg_with_arg(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type, value: string)=> string)) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_arg = __memo_scope.param(0, arg); if (__memo_scope.unchanged) { @@ -57,7 +57,7 @@ class Test { return; } } - public memo_content(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() content: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void { + @memo() public memo_content(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() content: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_content = __memo_scope.param(0, content); if (__memo_scope.unchanged) { @@ -70,7 +70,7 @@ class Test { return; } } - public compute_test(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() arg1: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined, arg2: (()=> void) | undefined, content: (()=> void) | undefined): void { + @memo() public compute_test(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @memo() arg1: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined, arg2: (()=> void) | undefined, content: (()=> void) | undefined): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 3); const __memo_parameter_arg1 = __memo_scope.param(0, arg1), __memo_parameter_arg2 = __memo_scope.param(1, arg2), __memo_parameter_content = __memo_scope.param(2, content); if (__memo_scope.unchanged) { @@ -85,7 +85,7 @@ class Test { public constructor() {} } class Use { - public test(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public test(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -111,7 +111,7 @@ class Use { } return __memo_scope.recache(__memo_parameter_value.value); })); - test.compute_test(__memo_context, ((__memo_id) + ()), ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + test.compute_test(__memo_context, ((__memo_id) + ()), ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -137,7 +137,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform argument calls in methods', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/method-declarations/callable.test.ts b/arkui-plugins/test/ut/memo-plugins/method-declarations/callable.test.ts index 77b786332..8852d486e 100644 --- a/arkui-plugins/test/ut/memo-plugins/method-declarations/callable.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/method-declarations/callable.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const METHOD_DIR_PATH: string = 'memo/methods'; @@ -32,14 +32,14 @@ const expectedScript: string = ` import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} -@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { +@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; return; } A.$_invoke(__memo_context, ((__memo_id) + ())); - B.$_invoke(((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + B.$_invoke(((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -62,7 +62,7 @@ function main() {} } }); class A { - public static $_invoke(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public static $_invoke(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -80,7 +80,7 @@ class B { public constructor() {} } class C { - public static $_instantiate(__memo_context: __memo_context_type, __memo_id: __memo_id_type, factory: (()=> C)): C { + @memo() public static $_instantiate(__memo_context: __memo_context_type, __memo_id: __memo_id_type, factory: (()=> C)): C { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_factory = __memo_scope.param(0, factory); if (__memo_scope.unchanged) { @@ -104,7 +104,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform callable class', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/method-declarations/declare-and-call.test.ts b/arkui-plugins/test/ut/memo-plugins/method-declarations/declare-and-call.test.ts index 5a23c9b83..309f637f8 100644 --- a/arkui-plugins/test/ut/memo-plugins/method-declarations/declare-and-call.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/method-declarations/declare-and-call.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const METHOD_DIR_PATH: string = 'memo/methods'; @@ -34,7 +34,7 @@ const expectedScript: string = ` import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} -@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { +@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -54,7 +54,7 @@ declare abstract class A { public constructor() {} } class AA extends A { - public x(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public x(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -75,7 +75,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform declare methods and calls', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/method-declarations/internal-calls.test.ts b/arkui-plugins/test/ut/memo-plugins/method-declarations/internal-calls.test.ts index 022c01ac0..3f6b21331 100644 --- a/arkui-plugins/test/ut/memo-plugins/method-declarations/internal-calls.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/method-declarations/internal-calls.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const METHOD_DIR_PATH: string = 'memo/methods'; @@ -36,7 +36,7 @@ export function __context(): __memo_context_type export function __id(): __memo_id_type type MemoType = @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void); class Test { - public void_method(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public void_method(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -47,7 +47,7 @@ class Test { return; } } - public internal_call(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public internal_call(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -59,7 +59,7 @@ class Test { return; } } - public method_with_internals(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public method_with_internals(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -73,7 +73,7 @@ class Test { } } public memo_lambda() { - @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -85,7 +85,7 @@ class Test { } }); } - public memo_variables(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public memo_variables(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -120,7 +120,7 @@ class Test { return; } } - public args_with_default_values(__memo_context: __memo_context_type, __memo_id: __memo_id_type, gensym%%_1?: int, gensym%%_2?: (()=> int), gensym%%_3?: int, arg4?: int): void { + @memo() public args_with_default_values(__memo_context: __memo_context_type, __memo_id: __memo_id_type, gensym%%_1?: int, gensym%%_2?: (()=> int), gensym%%_3?: int, arg4?: int): void { let arg1: int = (((gensym%%_1) !== (undefined)) ? gensym%%_1 : (10 as int)); let arg2: (()=> int) = (((gensym%%_2) !== (undefined)) ? gensym%%_2 : ((() => { return 20; @@ -139,7 +139,7 @@ class Test { return; } } - public optional_args(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg1?: int, arg2?: (()=> int)): void { + @memo() public optional_args(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg1?: int, arg2?: (()=> int)) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 2); const __memo_parameter_arg1 = __memo_scope.param(0, arg1), __memo_parameter_arg2 = __memo_scope.param(1, arg2); if (__memo_scope.unchanged) { @@ -155,7 +155,7 @@ class Test { return; } } - public type_alias(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: MemoType): void { + @memo() public type_alias(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: MemoType) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_arg = __memo_scope.param(0, arg); if (__memo_scope.unchanged) { @@ -178,7 +178,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform inner calls in methods', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/method-declarations/non-void-method.test.ts b/arkui-plugins/test/ut/memo-plugins/method-declarations/non-void-method.test.ts index 962129e7d..e81bf673c 100644 --- a/arkui-plugins/test/ut/memo-plugins/method-declarations/non-void-method.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/method-declarations/non-void-method.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const METHOD_DIR_PATH: string = 'memo/methods'; @@ -36,8 +36,9 @@ export function __context(): __memo_context_type export function __id(): __memo_id_type @Retention({policy:"SOURCE"}) @interface memo_intrinsic {} @Retention({policy:"SOURCE"}) @interface memo_entry {} +@Retention({policy:"SOURCE"}) @interface memo_skip {} class Test { - public void_method(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public void_method(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -48,7 +49,7 @@ class Test { return; } } - public string_method_with_return(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: string): string { + @memo() public string_method_with_return(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: string): string { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_arg = __memo_scope.param(0, arg); if (__memo_scope.unchanged) { @@ -56,7 +57,7 @@ class Test { } return __memo_scope.recache(__memo_parameter_arg.value); } - public method_with_type_parameter(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: T): T { + @memo() public method_with_type_parameter(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: T): T { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_arg = __memo_scope.param(0, arg); if (__memo_scope.unchanged) { @@ -84,10 +85,20 @@ class Test { return entry(__memo_context, ((__memo_id) + ())); } } + @memo() public memo_skip_args(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg1: number, @memo_skip() arg2: string, @memo_skip() arg3: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): string { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); + const __memo_parameter_arg1 = __memo_scope.param(0, arg1); + if (__memo_scope.unchanged) { + return __memo_scope.cached; + } + let a = __memo_parameter_arg1.value; + arg3(__memo_context, ((__memo_id) + ())); + return __memo_scope.recache(arg2); + } public constructor() {} } class Use { - public test(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public test(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -111,7 +122,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform methods with non-void return type', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/method-declarations/void-method.test.ts b/arkui-plugins/test/ut/memo-plugins/method-declarations/void-method.test.ts index e29eb8cc1..41e73d5bb 100644 --- a/arkui-plugins/test/ut/memo-plugins/method-declarations/void-method.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/method-declarations/void-method.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const METHOD_DIR_PATH: string = 'memo/methods'; @@ -40,7 +40,7 @@ class A { public constructor() {} } class Test { - public void_method(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public void_method(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -51,7 +51,7 @@ class Test { return; } } - public a_method_with_implicit_return_type(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public a_method_with_implicit_return_type(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -62,7 +62,7 @@ class Test { return; } } - public void_method_with_arg(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: string): void { + @memo() public void_method_with_arg(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: string) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_arg = __memo_scope.param(0, arg); if (__memo_scope.unchanged) { @@ -74,7 +74,7 @@ class Test { return; } } - public void_method_with_return(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: string): void { + @memo() public void_method_with_return(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: string) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_arg = __memo_scope.param(0, arg); if (__memo_scope.unchanged) { @@ -86,7 +86,7 @@ class Test { return; } } - public static static_method_with_type_parameter(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: T): void { + @memo() public static static_method_with_type_parameter(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: T): void { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_arg = __memo_scope.param(0, arg); if (__memo_scope.unchanged) { @@ -98,7 +98,7 @@ class Test { return; } } - public obj_arg(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: A): void { + @memo() public obj_arg(__memo_context: __memo_context_type, __memo_id: __memo_id_type, arg: A) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); const __memo_parameter_arg = __memo_scope.param(0, arg); if (__memo_scope.unchanged) { @@ -113,7 +113,7 @@ class Test { public constructor() {} } class Use { - public test(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public test(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -143,7 +143,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform methods with void return type', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/property-declarations/class-constructor.test.ts b/arkui-plugins/test/ut/memo-plugins/property-declarations/class-constructor.test.ts index 49a170e20..af8cde937 100644 --- a/arkui-plugins/test/ut/memo-plugins/property-declarations/class-constructor.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/property-declarations/class-constructor.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const PROPERTY_DIR_PATH: string = 'memo/properties'; @@ -34,14 +34,14 @@ const expectedScript: string = ` import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} -@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { +@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; return; } let a = new AA({ - a: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + a: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -59,7 +59,7 @@ function main() {} } }); interface A { - @memo() set a(a: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void + @memo() set a(a: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)) @memo() get a(): ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) } class AA { @@ -71,7 +71,7 @@ class AA { this.a = ({let gensym%%_ = arg; (((gensym%%_) == (null)) ? undefined : gensym%%_.a)}); } - public build(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public build(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -93,7 +93,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform properties in class constructor', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/property-declarations/class-properties.test.ts b/arkui-plugins/test/ut/memo-plugins/property-declarations/class-properties.test.ts index 31092a975..b0c65607e 100644 --- a/arkui-plugins/test/ut/memo-plugins/property-declarations/class-properties.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/property-declarations/class-properties.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const PROPERTY_DIR_PATH: string = 'memo/properties'; @@ -38,7 +38,7 @@ class A { public arg: (()=> void); @memo() public memo_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void); @memo() public memo_optional_arg?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined; - @memo() public memo_union_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined = ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + @memo() public memo_union_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined = ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -52,7 +52,7 @@ class A { public arg_memo_type: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void); public constructor() { this.arg = (() => {}); - this.memo_arg = ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + this.memo_arg = ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -63,7 +63,7 @@ class A { return; } }); - this.arg_memo_type = ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + this.arg_memo_type = ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -75,7 +75,7 @@ class A { } }); } - public build(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + @memo() public build(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -98,7 +98,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform properties in class', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/ut/memo-plugins/property-declarations/interfaces.test.ts b/arkui-plugins/test/ut/memo-plugins/property-declarations/interfaces.test.ts index d58f3d7bc..3164742a0 100644 --- a/arkui-plugins/test/ut/memo-plugins/property-declarations/interfaces.test.ts +++ b/arkui-plugins/test/ut/memo-plugins/property-declarations/interfaces.test.ts @@ -18,7 +18,7 @@ import { PluginTester } from '../../../utils/plugin-tester'; import { mockBuildConfig } from '../../../utils/artkts-config'; import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../utils/path-config'; import { parseDumpSrc } from '../../../utils/parse-string'; -import { memoNoRecheck, recheck } from '../../../utils/plugins'; +import { beforeMemoNoRecheck, memoNoRecheck, recheck } from '../../../utils/plugins'; import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; const PROPERTY_DIR_PATH: string = 'memo/properties'; @@ -32,7 +32,7 @@ const expectedScript: string = ` import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; import { memo as memo } from \"arkui.stateManagement.runtime\"; function main() {} -@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { +@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -40,7 +40,7 @@ function main() {} } let a: A = { arg: (() => {}), - memo_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + memo_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -51,7 +51,7 @@ function main() {} return; } }), - memo_union_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + memo_union_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -62,7 +62,7 @@ function main() {} return; } }), - arg_memo_type: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type): void => { + arg_memo_type: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); if (__memo_scope.unchanged) { __memo_scope.cached; @@ -82,11 +82,11 @@ function main() {} interface A { set arg(arg: (()=> void)) get arg(): (()=> void) - @memo() set memo_arg(memo_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void + @memo() set memo_arg(memo_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)) @memo() get memo_arg(): ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) - @memo() set memo_optional_arg(memo_optional_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined): void + @memo() set memo_optional_arg(memo_optional_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined) @memo() get memo_optional_arg(): ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined - @memo() set memo_union_arg(memo_union_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined): void + @memo() set memo_union_arg(memo_union_arg: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined) @memo() get memo_union_arg(): ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined set arg_memo_type(arg_memo_type: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)) get arg_memo_type(): @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) @@ -99,7 +99,7 @@ function testMemoTransformer(this: PluginTestContext): void { pluginTester.run( 'transform interface properties', - [memoNoRecheck, recheck], + [beforeMemoNoRecheck, memoNoRecheck, recheck], { 'checked:memo-no-recheck': [testMemoTransformer], }, diff --git a/arkui-plugins/test/utils/artkts-config.ts b/arkui-plugins/test/utils/artkts-config.ts index c218c9086..cbe7f33db 100644 --- a/arkui-plugins/test/utils/artkts-config.ts +++ b/arkui-plugins/test/utils/artkts-config.ts @@ -35,6 +35,7 @@ import { } from './path-config'; import { ArkTSConfigContextCache } from './cache'; import { BuildConfig, CompileFileInfo, DependentModule } from './shared-types'; +import { setUpSoPath } from './global'; export interface ArkTSConfigObject { compilerOptions: { @@ -200,6 +201,7 @@ class MockArktsConfigBuilder implements ArktsConfigBuilder { this.moduleInfos = new Map(); this.mergedAbcFile = path.resolve(this.outputDir, MOCK_OUTPUT_FILE_NAME); + setUpSoPath(this.pandaSdkPath); this.generateModuleInfos(); this.generateArkTSConfigForModules(); this.cacheArkTSConfig(); diff --git a/arkui-plugins/test/utils/compile.ts b/arkui-plugins/test/utils/compile.ts index 1c9640de4..bc00f4a17 100644 --- a/arkui-plugins/test/utils/compile.ts +++ b/arkui-plugins/test/utils/compile.ts @@ -15,11 +15,24 @@ import * as arkts from '@koalaui/libarkts'; import EventEmitter from 'events'; -import type { JobInfo, PluginTestContext, ProcessEvent, SingleProgramContext, TraceOptions } from './shared-types'; +import { + CompileStrategy, + JobInfo, + PluginTestContext, + ProcessEvent, + SingleProgramContext, + TraceOptions, +} from './shared-types'; import { MockPluginDriver, stateName } from './plugin-driver'; -import { createCacheContextFromFile, destroyContext, resetConfig } from './global'; +import { + createContextGenerateAbcForExternalSourceFiles, + createCacheContextFromFile, + destroyContext, + resetConfig, +} from './global'; import { PluginDriver } from './plugin-driver'; import { PluginState, PluginContext, PluginExecutor } from '../../common/plugin-context'; +import { concatObject } from './serializable'; function insertPlugin(driver: PluginDriver, plugin: PluginExecutor | undefined): boolean { const pluginContext: PluginContext = driver.getPluginContext(); @@ -29,40 +42,74 @@ function insertPlugin(driver: PluginDriver, plugin: PluginExecutor | undefined): return true; } +function collectPluginTextContextFromSourceProgram(program: arkts.Program, tracing: TraceOptions): PluginTestContext { + const pluginTestContext: PluginTestContext = {}; + const script: arkts.EtsScript = program.astNode; + pluginTestContext.scriptSnapshot = script.dumpSrc(); + return pluginTestContext; +} + +function collectPluginTextContextFromExternalSource( + externalSources: arkts.ExternalSource[], + tracing: TraceOptions, + matchSourceName: (name: string) => boolean, + useCache?: boolean +) { + let pluginTestContext: PluginTestContext = {}; + const filteredExternalSourceNames: string[] = [...tracing.externalSourceNames]; + const filteredExternalSources = externalSources.filter((source) => { + const name = source.getName(); + const sourceProgram: arkts.Program = source.programs[0]; + const shouldCollectByName = filteredExternalSourceNames.includes(name) || matchSourceName(name); + const shouldCollectByProgram = sourceProgram && (!useCache || sourceProgram.isASTLowered()); + return shouldCollectByName && shouldCollectByProgram; + }); + const declContexts: Record = {}; + filteredExternalSources.forEach((source) => { + const name: string = source.getName(); + const sourceProgram: arkts.Program = source.programs[0]; + if (matchSourceName(name)) { + pluginTestContext = concatObject( + pluginTestContext, + collectPluginTextContextFromSourceProgram(sourceProgram, tracing) + ); + } else { + const sourceTestContext: SingleProgramContext = {}; + const script: arkts.EtsScript = sourceProgram.astNode; + const scriptSnapshot = script.dumpSrc(); + sourceTestContext.scriptSnapshot = scriptSnapshot; + declContexts[name] = sourceTestContext; + } + }); + pluginTestContext.declContexts = declContexts; + return pluginTestContext; +} + function collectPluginTestContext( context: arkts.Context, - isExternal: boolean, - tracing: TraceOptions + compileStrategy: CompileStrategy, + tracing: TraceOptions, + matchSourceName: (name: string) => boolean ): PluginTestContext { - const pluginTestContext: PluginTestContext = {}; + const useCache: boolean = compileStrategy !== CompileStrategy.ABC_WTIH_EXTERNAL; + const canCollectSource: boolean = !useCache || compileStrategy === CompileStrategy.ABC; + const canCollectExternal: boolean = !useCache || compileStrategy === CompileStrategy.EXTERNAL; + let pluginTestContext: PluginTestContext = {}; try { const program: arkts.Program = arkts.getOrUpdateGlobalContext(context.peer).program; // TODO: add error/warning handling after plugin - if (!isExternal) { - const script: arkts.EtsScript = program.astNode; - pluginTestContext.scriptSnapshot = script.dumpSrc(); - } else { - const declContexts: Record = {}; + if (canCollectSource) { + pluginTestContext = concatObject( + pluginTestContext, + collectPluginTextContextFromSourceProgram(program, tracing) + ); + } + if (canCollectExternal) { const externalSources: arkts.ExternalSource[] = program.externalSources; - externalSources - .filter((source) => { - const sourceProgram: arkts.Program = source.programs[0]; - return ( - tracing.externalSourceNames.includes(source.getName()) && - sourceProgram && - sourceProgram.isASTLowered() - ); - }) - .forEach((source) => { - const sourceProgram: arkts.Program = source.programs[0]; - const sourceTestContext: SingleProgramContext = {}; - const name: string = source.getName(); - const script: arkts.EtsScript = sourceProgram.astNode; - const scriptSnapshot = script.dumpSrc(); - sourceTestContext.scriptSnapshot = scriptSnapshot; - declContexts[name] = sourceTestContext; - }); - pluginTestContext.declContexts = declContexts; + pluginTestContext = concatObject( + pluginTestContext, + collectPluginTextContextFromExternalSource(externalSources, tracing, matchSourceName, useCache) + ); } } catch (e) { // Do nothing @@ -71,12 +118,17 @@ function collectPluginTestContext( } } +function buildMatchNameFunc(prefix: string, suffix: string): (name: string) => boolean { + return (name: string): boolean => { + return name.startsWith(`${prefix}.`) && name.endsWith(`.${suffix}`); + }; +} + /** * @param emitter event emitter. * @param jobInfo job info. * @param state the current state. * @param context context for the single file. - * @param isExternal boolean indicates if plugin is compiling external sources. * @param stopAfter state that should stop after running plugins. * @returns boolean indicates whether should proceed to the next state. */ @@ -85,19 +137,21 @@ function runPluginsAtState( jobInfo: JobInfo, state: arkts.Es2pandaContextState, context: arkts.Context, - isExternal: boolean, tracing: TraceOptions, stopAfter?: PluginState ): boolean { const stateStr = stateName(state); const plugins = MockPluginDriver.getInstance().getSortedPlugins(state); + const packageName = jobInfo.buildConfig!.packageName; const fileName = jobInfo.compileFileInfo!.fileName; + const matchSourceName = buildMatchNameFunc(packageName, fileName); + const compileStrategy = jobInfo.isCompileAbc; if (plugins && plugins.length > 0) { plugins.forEach((plugin) => { insertPlugin(MockPluginDriver.getInstance(), plugin); const pluginName: string = plugin.name; const pluginStateId: `${PluginState}:${string}` = `${stateStr}:${pluginName}`; - const pluginTestContext = collectPluginTestContext(context, isExternal, tracing); + const pluginTestContext = collectPluginTestContext(context, compileStrategy, tracing, matchSourceName); emitter.emit('TASK_COLLECT', { jobId: jobInfo.id, pluginStateId, @@ -107,7 +161,7 @@ function runPluginsAtState( }); } const pluginStateId: PluginState = `${stateStr}`; - const pluginTestContext = collectPluginTestContext(context, isExternal, tracing); + const pluginTestContext = collectPluginTestContext(context, compileStrategy, tracing, matchSourceName); emitter.emit('TASK_COLLECT', { jobId: jobInfo.id, pluginStateId, @@ -145,6 +199,48 @@ function createContextForExternalCompilation(jobInfo: JobInfo): arkts.Context { return context; } +function compileAbcWithExternal(emitter: EventEmitter, jobInfo: JobInfo, tracing: TraceOptions): void { + MockPluginDriver.getInstance().initPlugins(jobInfo.plugins ?? []); + const context = createContextGenerateAbcForExternalSourceFiles(jobInfo.filePaths!); + MockPluginDriver.getInstance().getPluginContext().setContextPtr(context.peer); + const stopAfter = jobInfo.stopAfter!; + let shouldStop = false; + arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, context.peer); + shouldStop = runPluginsAtState( + emitter, + jobInfo, + arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, + context, + tracing, + stopAfter + ); + if (shouldStop) { + destroyContext(context); + MockPluginDriver.getInstance().clear(); + emitter.emit('TASK_FINISH', { jobId: 'compile-abc-with-external' }); + return; + } + arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, context.peer); + shouldStop = runPluginsAtState( + emitter, + jobInfo, + arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, + context, + tracing, + stopAfter + ); + if (shouldStop) { + destroyContext(context); + MockPluginDriver.getInstance().clear(); + emitter.emit('TASK_FINISH', { jobId: 'compile-abc-with-external' }); + return; + } + arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_BIN_GENERATED, context.peer); + destroyContext(context); + MockPluginDriver.getInstance().clear(); + emitter.emit('TASK_FINISH', { jobId: 'compile-abc-with-external' }); +} + function compileAbc(emitter: EventEmitter, jobInfo: JobInfo, tracing: TraceOptions): void { MockPluginDriver.getInstance().initPlugins(jobInfo.plugins ?? []); const context = createContextForAbcCompilation(jobInfo); @@ -157,7 +253,6 @@ function compileAbc(emitter: EventEmitter, jobInfo: JobInfo, traci jobInfo, arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, context, - false, tracing, stopAfter ); @@ -172,7 +267,6 @@ function compileAbc(emitter: EventEmitter, jobInfo: JobInfo, traci jobInfo, arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, context, - false, tracing, stopAfter ); @@ -191,12 +285,12 @@ function compileExternalProgram(emitter: EventEmitter, jobInfo: Jo const context = createContextForExternalCompilation(jobInfo); MockPluginDriver.getInstance().getPluginContext().setContextPtr(context.peer); arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, context.peer); - runPluginsAtState(emitter, jobInfo, arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, context, true, tracing); + runPluginsAtState(emitter, jobInfo, arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, context, tracing); arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, context.peer); - runPluginsAtState(emitter, jobInfo, arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, context, true, tracing); + runPluginsAtState(emitter, jobInfo, arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, context, tracing); arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_LOWERED, context.peer); destroyContext(context); MockPluginDriver.getInstance().clear(); } -export { compileAbc, compileExternalProgram }; +export { compileAbcWithExternal, compileAbc, compileExternalProgram }; diff --git a/arkui-plugins/test/utils/global.ts b/arkui-plugins/test/utils/global.ts index 99ac287e3..914efc643 100644 --- a/arkui-plugins/test/utils/global.ts +++ b/arkui-plugins/test/utils/global.ts @@ -16,7 +16,11 @@ import * as arkts from '@koalaui/libarkts'; import { CompileFileInfo } from './shared-types'; -function createGlobalConfig(fileInfo: CompileFileInfo, isDebug: boolean = true): arkts.Config { +function createGlobalConfig( + fileInfo: CompileFileInfo, + isDebug: boolean = true, + isUseCache: boolean = true +): arkts.Config { const config = [ '_', '--extension', @@ -32,14 +36,18 @@ function createGlobalConfig(fileInfo: CompileFileInfo, isDebug: boolean = true): } config.push(fileInfo.filePath); - arkts.MemInitialize(); + if (isUseCache) { + arkts.MemInitialize(); + } arkts.arktsGlobal.filePath = fileInfo.filePath; return resetConfig(config); } -function destroyGlobalConfig(config: arkts.Config): void { +function destroyGlobalConfig(config: arkts.Config, isUseCache: boolean = true): void { destroyConfig(config); - arkts.MemFinalize(); + if (isUseCache) { + arkts.MemFinalize(); + } } function createGlobalContextPtr(config: arkts.Config, files: string[]): number { @@ -59,6 +67,10 @@ function createCacheContextFromFile( return arkts.Context.createCacheContextFromFile(config.peer, filePath, globalContextPtr, isExternal); } +function createContextGenerateAbcForExternalSourceFiles(filePaths: string[]): arkts.Context { + return arkts.Context.createContextGenerateAbcForExternalSourceFiles(filePaths); +} + function resetContext(source: string): void { try { arkts.arktsGlobal.context; @@ -99,14 +111,20 @@ function destroyConfig(config: arkts.Config): void { } } +function setUpSoPath(pandaSdkPath: string): void { + arkts.arktsGlobal.es2panda._SetUpSoPath(pandaSdkPath); +} + export { createGlobalConfig, destroyGlobalConfig, createGlobalContextPtr, destroyGlobalContextPtr, createCacheContextFromFile, + createContextGenerateAbcForExternalSourceFiles, resetContext, resetConfig, destroyContext, destroyConfig, + setUpSoPath, }; diff --git a/arkui-plugins/test/utils/plugin-tester.ts b/arkui-plugins/test/utils/plugin-tester.ts index 8a64da3fc..b0f4c9f9c 100644 --- a/arkui-plugins/test/utils/plugin-tester.ts +++ b/arkui-plugins/test/utils/plugin-tester.ts @@ -20,14 +20,16 @@ import { CompileFileInfo, PluginStateId, PluginTestContext, + Processor, SingleProgramContext, TraceOptions, } from './shared-types'; import { HashGenerator } from './hash-generator'; -import { TaskProcessor } from './task-processor'; import { PluginTestContextCache } from './cache'; import { Plugins, PluginState } from '../../common/plugin-context'; import { concatObject } from './serializable'; +import { ProcessorBuilder } from './processor-builder'; +import { MasterProcessor } from './processors/master-processor'; type TestParams = Parameters; @@ -53,7 +55,7 @@ class PluginTester { private hashId: string; private describe: string; private configBuilder: ArktsConfigBuilder; - private taskProcessor?: TaskProcessor; + private taskProcessor?: Processor; private resolve?: Promise; constructor(describe: string, buildConfig?: BuildConfig) { @@ -97,12 +99,16 @@ class PluginTester { let declContexts: Record = {}; fileNames.forEach((fileName) => { const sourceKey = `${abcKey}:${fileName}`; - const sourceContext = PluginTestContextCache.getInstance().get(sourceKey)!; + const sourceContext = PluginTestContextCache.getInstance().get(sourceKey) ?? {}; + if (!!sourceContext.declContexts) { + declContexts = concatObject(declContexts, sourceContext.declContexts); + delete sourceContext.declContexts; + } sourceContexts[fileName] = sourceContext; const declKey = `${externalKey}:${fileName}`; - const declContext = PluginTestContextCache.getInstance().get(declKey)!; - declContexts = concatObject(declContexts, declContext.declContexts); + const declContext = PluginTestContextCache.getInstance().get(declKey) ?? {}; + declContexts = concatObject(declContexts, declContext.declContexts ?? {}); }); return { sourceContexts, declContexts }; @@ -165,7 +171,12 @@ class PluginTester { } private async compile(plugins: Plugins[], stopAfter?: PluginState, tracing?: TraceOptions): Promise { - this.taskProcessor = new TaskProcessor(this.hashId, this.configBuilder.buildConfig); + this.taskProcessor = ProcessorBuilder.build( + MasterProcessor, + this.hashId, + this.configBuilder.buildConfig, + tracing + ); return this.taskProcessor.invokeWorkers(plugins, stopAfter); } @@ -196,7 +207,7 @@ class PluginTester { that.clear(); }); - that.resolve = that.compile(plugins, options.stopAfter); + that.resolve = that.compile(plugins, options.stopAfter, options.tracing); that.compileTests(testName, pluginHooks); }); } diff --git a/arkui-plugins/test/utils/plugins/before-memo-no-recheck.ts b/arkui-plugins/test/utils/plugins/before-memo-no-recheck.ts new file mode 100644 index 000000000..2553fa47d --- /dev/null +++ b/arkui-plugins/test/utils/plugins/before-memo-no-recheck.ts @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as arkts from '@koalaui/libarkts'; +import { PluginContext, Plugins } from '../../../common/plugin-context'; +import { ProgramVisitor } from '../../../common/program-visitor'; +import { EXTERNAL_SOURCE_PREFIX_NAMES } from '../../../common/predefines'; +import { MemoVisitor } from '../../../ui-plugins/memo-translators/memo-visitor'; + +/** + * AfterCheck before-memo-visit and cache any node that should be unmemoized with no recheck AST. + */ +export const beforeMemoNoRecheck: Plugins = { + name: 'before-memo-no-recheck', + checked(this: PluginContext): arkts.EtsScript | undefined { + let script: arkts.EtsScript | undefined; + const contextPtr = this.getContextPtr() ?? arkts.arktsGlobal.compilerContext?.peer; + if (!!contextPtr) { + let program = arkts.getOrUpdateGlobalContext(contextPtr).program; + script = program.astNode; + const builderLambdaTransformer = new MemoVisitor(); + const programVisitor = new ProgramVisitor({ + pluginName: beforeMemoNoRecheck.name, + state: arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, + visitors: [builderLambdaTransformer], + skipPrefixNames: EXTERNAL_SOURCE_PREFIX_NAMES, + pluginContext: this, + }); + program = programVisitor.programVisitor(program); + script = program.astNode; + return script; + } + return script; + }, +}; \ No newline at end of file diff --git a/arkui-plugins/test/utils/plugins/builder-lambda-no-recheck.ts b/arkui-plugins/test/utils/plugins/builder-lambda-no-recheck.ts index f7d060b9f..a770c96b0 100644 --- a/arkui-plugins/test/utils/plugins/builder-lambda-no-recheck.ts +++ b/arkui-plugins/test/utils/plugins/builder-lambda-no-recheck.ts @@ -30,7 +30,7 @@ export const builderLambdaNoRecheck: Plugins = { if (!!contextPtr) { let program = arkts.getOrUpdateGlobalContext(contextPtr).program; script = program.astNode; - const builderLambdaTransformer = new BuilderLambdaTransformer(); + const builderLambdaTransformer = new BuilderLambdaTransformer(this.getProjectConfig()); const programVisitor = new ProgramVisitor({ pluginName: builderLambdaNoRecheck.name, state: arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, diff --git a/arkui-plugins/test/utils/plugins/index.ts b/arkui-plugins/test/utils/plugins/index.ts index 4858b84b2..ab232c217 100644 --- a/arkui-plugins/test/utils/plugins/index.ts +++ b/arkui-plugins/test/utils/plugins/index.ts @@ -17,6 +17,7 @@ export * from './struct-to-component'; // AfterCheck +export * from './before-memo-no-recheck'; export * from './builder-lambda-no-recheck'; export * from './memo-no-recheck'; export * from './struct-no-recheck'; diff --git a/arkui-plugins/test/utils/plugins/memo-no-recheck.ts b/arkui-plugins/test/utils/plugins/memo-no-recheck.ts index c953249a1..b75c99e1d 100644 --- a/arkui-plugins/test/utils/plugins/memo-no-recheck.ts +++ b/arkui-plugins/test/utils/plugins/memo-no-recheck.ts @@ -44,6 +44,7 @@ export const memoNoRecheck: Plugins = { parameterTransformer, returnTransformer, signatureTransformer, + useCache: arkts.NodeCache.getInstance().isCollected() }); const programVisitor = new ProgramVisitor({ pluginName: memoNoRecheck.name, @@ -53,6 +54,7 @@ export const memoNoRecheck: Plugins = { pluginContext: this, }); program = programVisitor.programVisitor(program); + arkts.NodeCache.getInstance().clear(); script = program.astNode; return script; } diff --git a/arkui-plugins/test/utils/processor-builder.ts b/arkui-plugins/test/utils/processor-builder.ts new file mode 100644 index 000000000..bb68e31e6 --- /dev/null +++ b/arkui-plugins/test/utils/processor-builder.ts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BuildConfig, Processor, TraceOptions } from './shared-types'; + +class ProcessorBuilder { + static build( + Processor: { new (hashId: string, buildConfig?: BuildConfig, tracing?: TraceOptions): Processor }, + hashId: string, + buildConfig?: BuildConfig, + tracing?: TraceOptions + ): Processor { + return new Processor(hashId, buildConfig, tracing); + } +} + +export { ProcessorBuilder }; diff --git a/arkui-plugins/test/utils/processors/base-processor.ts b/arkui-plugins/test/utils/processors/base-processor.ts new file mode 100644 index 000000000..d60d269e0 --- /dev/null +++ b/arkui-plugins/test/utils/processors/base-processor.ts @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Plugins, PluginState } from "common/plugin-context"; +import { mockBuildConfig } from "../artkts-config"; +import { ArkTSConfigContextCache } from "../cache"; +import { BuildConfig, CompileFileInfo, Processor, TraceOptions } from "../shared-types"; + +abstract class BaseProcessor implements Processor { + hashId: string; + buildConfig: BuildConfig; + tracing: TraceOptions; + cacheDir: string; + arktsConfigFile: string; + compileFiles: Map; + + constructor(hashId: string, buildConfig?: BuildConfig, tracing?: TraceOptions) { + this.hashId = hashId; + this.tracing = tracing ?? { externalSourceNames: [] }; + + const _buildConfig: BuildConfig = buildConfig ?? mockBuildConfig(); + this.buildConfig = _buildConfig; + this.cacheDir = _buildConfig.cachePath; + this.arktsConfigFile = this.getArktsConfigFile(); + this.compileFiles = this.getCompileFiles(); + } + + private getArktsConfigFile(): string { + const arktsConfigFile = ArkTSConfigContextCache.getInstance().get(this.hashId)?.arktsConfigFile; + if (!arktsConfigFile) { + const err = `[${this.hashId}] TaskProcessor cannot get arktsConfigFile`; + console.error(err); + throw new Error(err); + } + return arktsConfigFile; + } + + private getCompileFiles(): Map { + const compileFiles = ArkTSConfigContextCache.getInstance().get(this.hashId)?.compileFiles; + if (!compileFiles) { + const err = `[${this.hashId}] TaskProcessor cannot get compileFiles`; + console.error(err); + throw new Error(err); + } + return compileFiles; + } + + abstract invokeWorkers(plugins: Plugins[], stopAfter?: PluginState): Promise; + + abstract clear(): void; +} + +export { BaseProcessor }; \ No newline at end of file diff --git a/arkui-plugins/test/utils/processors/master-processor.ts b/arkui-plugins/test/utils/processors/master-processor.ts new file mode 100644 index 000000000..cbb148e39 --- /dev/null +++ b/arkui-plugins/test/utils/processors/master-processor.ts @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import EventEmitter from 'events'; +import { + BuildConfig, + CompileFileInfo, + CompileStrategy, + JobInfo, + PluginTestContext, + ProcessEvent, + TraceOptions, +} from '../shared-types'; +import { BaseProcessor } from './base-processor'; +import { PluginTestContextCache } from '../cache'; +import { concatObject, serializable } from '../serializable'; +import { Plugins, PluginState } from '../../../common/plugin-context'; +import { + createGlobalConfig, + destroyGlobalConfig, +} from '../global'; +import { compileAbcWithExternal } from '../compile'; + +class MasterProcessor extends BaseProcessor { + filePaths: string[]; + + readonly emitter: EventEmitter = new EventEmitter(); + + constructor(hashId: string, buildConfig?: BuildConfig, tracing?: TraceOptions) { + super(hashId, buildConfig, tracing); + this.filePaths = this.getCompileFilePaths(); + } + + private getCompileFilePaths(): string[] { + return Array.from(this.compileFiles.values()).map((fileInfo) => fileInfo.filePath); + } + + private subscribe(): void { + this.emitter.on('TASK_COLLECT', (msg) => { + const sourceType = 'abc'; + const pluginStateId = msg.pluginStateId; + const fileName = msg.fileName; + const pluginTestContext = msg.pluginTestContext as PluginTestContext; + const key = `${this.hashId}:${sourceType}:${pluginStateId}:${fileName}`; + let currentPluginTestContext; + if (PluginTestContextCache.getInstance().has(key)) { + const oldContext = PluginTestContextCache.getInstance().get(key)!; + currentPluginTestContext = concatObject(oldContext, pluginTestContext); + } else { + currentPluginTestContext = pluginTestContext; + } + PluginTestContextCache.getInstance().set(key, currentPluginTestContext); + }); + } + + private assignTask( + fileInfo: CompileFileInfo, + plugins: Plugins[], + stopAfter?: PluginState + ): void { + const jobInfo: JobInfo = { + id: 'compile-abc-with-external', + isCompileAbc: CompileStrategy.ABC_WTIH_EXTERNAL, + compileFileInfo: fileInfo, + buildConfig: serializable(this.buildConfig), + plugins, + stopAfter, + filePaths: this.filePaths + }; + compileAbcWithExternal(this.emitter, jobInfo, this.tracing); + } + + async invokeWorkers(plugins: Plugins[], stopAfter?: PluginState): Promise { + return new Promise((resolve) => { + const fileInfo: CompileFileInfo = this.compileFiles.values().next().value!; + const config = createGlobalConfig(fileInfo, true, false); + this.subscribe(); + this.emitter.on('TASK_FINISH', (msg) => { + console.log('All tasks completed. Exiting...'); + destroyGlobalConfig(config, false); + resolve(); + }); + this.assignTask(fileInfo, plugins, stopAfter); + }); + } + + clear(): void {} +} + +export { MasterProcessor }; diff --git a/arkui-plugins/test/utils/task-processor.ts b/arkui-plugins/test/utils/processors/task-processor.ts similarity index 87% rename from arkui-plugins/test/utils/task-processor.ts rename to arkui-plugins/test/utils/processors/task-processor.ts index afaa98fc9..5371d14a3 100644 --- a/arkui-plugins/test/utils/task-processor.ts +++ b/arkui-plugins/test/utils/processors/task-processor.ts @@ -18,28 +18,29 @@ import * as fs from 'fs'; import * as path from 'path'; import * as child_process from 'child_process'; import EventEmitter from 'events'; -import type { - BuildConfig, - CompileFileInfo, - JobInfo, - PluginTestContext, - ProcessEvent, - TraceOptions, -} from './shared-types'; -import { mockBuildConfig } from './artkts-config'; +import { + CompileStrategy, + type BuildConfig, + type CompileFileInfo, + type JobInfo, + type PluginTestContext, + type ProcessEvent, + type TraceOptions, +} from '../shared-types'; import { DECL_ETS_SUFFIX, ensurePathExists, getFileName, MOCK_DEP_INPUT_FILE_NAME, MOCK_FILE_DEP_FILE_NAME, -} from './path-config'; -import { ArkTSConfigContextCache, FileDependencyContextCache, PluginTestContextCache } from './cache'; -import { HashGenerator } from './hash-generator'; -import { createGlobalConfig, createGlobalContextPtr, destroyGlobalConfig, destroyGlobalContextPtr } from './global'; -import { Plugins, PluginState } from '../../common/plugin-context'; -import { concatObject, serializable } from './serializable'; -import { compileAbc, compileExternalProgram } from './compile'; +} from '../path-config'; +import { FileDependencyContextCache, PluginTestContextCache } from '../cache'; +import { HashGenerator } from '../hash-generator'; +import { createGlobalConfig, createGlobalContextPtr, destroyGlobalConfig, destroyGlobalContextPtr } from '../global'; +import { Plugins, PluginState } from '../../../common/plugin-context'; +import { concatObject, serializable } from '../serializable'; +import { compileAbc, compileExternalProgram } from '../compile'; +import { BaseProcessor } from './base-processor'; interface Job { id: string; @@ -230,17 +231,11 @@ function addJobToQueues(job: Job, queues: Queues): void { } } -class TaskProcessor { - hashId: string; - buildConfig: BuildConfig; - tracing: TraceOptions; - cacheDir: string; +class TaskProcessor extends BaseProcessor { entryFiles: Set; depAnalyzerPath: string; depInputFile: string; fileDepsInfoJson: string; - arktsConfigFile: string; - compileFiles: Map; jobMap: Record; jobQueues: Queues; @@ -248,18 +243,11 @@ class TaskProcessor { private worker!: WorkerInfo; constructor(hashId: string, buildConfig?: BuildConfig, tracing?: TraceOptions) { - this.hashId = hashId; - this.tracing = tracing ?? { externalSourceNames: [] }; - - const _buildConfig: BuildConfig = buildConfig ?? mockBuildConfig(); - this.buildConfig = _buildConfig; - this.cacheDir = _buildConfig.cachePath; - this.entryFiles = new Set(_buildConfig.compileFiles as string[]); - this.depAnalyzerPath = _buildConfig.depAnalyzerPath; - this.depInputFile = path.resolve(_buildConfig.cachePath, this.hashId, MOCK_DEP_INPUT_FILE_NAME); - this.fileDepsInfoJson = path.resolve(_buildConfig.cachePath, this.hashId, MOCK_FILE_DEP_FILE_NAME); - this.arktsConfigFile = this.getArktsConfigFile(); - this.compileFiles = this.getCompileFiles(); + super(hashId, buildConfig, tracing); + this.entryFiles = new Set(this.buildConfig.compileFiles as string[]); + this.depAnalyzerPath = this.buildConfig.depAnalyzerPath; + this.depInputFile = path.resolve(this.buildConfig.cachePath, this.hashId, MOCK_DEP_INPUT_FILE_NAME); + this.fileDepsInfoJson = path.resolve(this.buildConfig.cachePath, this.hashId, MOCK_FILE_DEP_FILE_NAME); this.generateFileDependencies(); this.cacheFileDependencies(); @@ -273,26 +261,6 @@ class TaskProcessor { FileDependencyContextCache.getInstance().set(this.hashId, { depInputFile, fileDepsInfoJson }); } - private getArktsConfigFile(): string { - const arktsConfigFile = ArkTSConfigContextCache.getInstance().get(this.hashId)?.arktsConfigFile; - if (!arktsConfigFile) { - const err = `[${this.hashId}] TaskProcessor cannot get arktsConfigFile`; - console.error(err); - throw new Error(err); - } - return arktsConfigFile; - } - - private getCompileFiles(): Map { - const compileFiles = ArkTSConfigContextCache.getInstance().get(this.hashId)?.compileFiles; - if (!compileFiles) { - const err = `[${this.hashId}] TaskProcessor cannot get compileFiles`; - console.error(err); - throw new Error(err); - } - return compileFiles; - } - private generateFileDependencies(): void { ensurePathExists(this.depInputFile); ensurePathExists(this.fileDepsInfoJson); @@ -431,13 +399,13 @@ class TaskProcessor { job = this.jobQueues.externalProgramQueue.shift()!; jobInfo = { id: job.id, - isCompileAbc: false, + isCompileAbc: CompileStrategy.EXTERNAL, }; } else if (this.jobQueues.abcQueue.length > 0) { job = this.jobQueues.abcQueue.shift()!; jobInfo = { id: job.id, - isCompileAbc: true, + isCompileAbc: CompileStrategy.ABC, }; } @@ -475,9 +443,9 @@ class TaskProcessor { private subscribe() { this.emitter.on('ASSIGN_TASK', (msg) => { const job = msg.jobInfo; - if (job.isCompileAbc) { + if (job.isCompileAbc === CompileStrategy.ABC) { compileAbc(this.emitter, job, this.tracing); - } else { + } else if (job.isCompileAbc === CompileStrategy.EXTERNAL) { compileExternalProgram(this.emitter, job, this.tracing); } this.emitter.emit('TASK_FINISH', { jobId: job.id }); diff --git a/arkui-plugins/test/utils/shared-types.ts b/arkui-plugins/test/utils/shared-types.ts index d713420e5..415e407be 100644 --- a/arkui-plugins/test/utils/shared-types.ts +++ b/arkui-plugins/test/utils/shared-types.ts @@ -76,12 +76,13 @@ export interface DependentModule { export interface JobInfo { id: string; - isCompileAbc: boolean; // TODO: change to enum + isCompileAbc: CompileStrategy; compileFileInfo?: CompileFileInfo; buildConfig?: BuildConfig; plugins?: Plugins[]; globalContextPtr?: number; stopAfter?: PluginState; + filePaths?: string[]; } export interface TraceOptions { @@ -119,3 +120,21 @@ export type ProcessEvents = export type ProcessEvent = { [E in ProcessEvents as E['type']]: Omit[]; }; + +export interface Processor { + hashId: string; + buildConfig: BuildConfig; + tracing: TraceOptions; + cacheDir: string; + arktsConfigFile: string; + compileFiles: Map; + + invokeWorkers(plugins: Plugins[], stopAfter?: PluginState): Promise; + clear(): void; +} + +export enum CompileStrategy { + ABC, + EXTERNAL, + ABC_WTIH_EXTERNAL, +} diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts index 7171c74dd..9ee7ab210 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts @@ -14,13 +14,7 @@ */ import * as arkts from '@koalaui/libarkts'; -import { - addMemoAnnotation, - BuilderLambdaNames, - CustomComponentNames, - findCanAddMemoFromParamExpression, - isCustomComponentAnnotation, -} from '../utils'; +import { BuilderLambdaNames, forEachArgWithParam } from '../utils'; import { backingField, filterDefined, removeAnnotationByName } from '../../common/arkts-utils'; import { BuilderLambdaDeclInfo, @@ -41,31 +35,17 @@ import { isStyleChainedCall, isStyleWithReceiverCall, builderLambdaType, + BuilderLambdaSecondLastArgInfo, + buildSecondLastArgInfo, } from './utils'; -import { isDecoratorIntrinsicAnnotation } from '../property-translators/utils'; +import { hasDecorator, isDecoratorIntrinsicAnnotation } from '../property-translators/utils'; import { factory as PropertyFactory } from '../property-translators/factory'; import { ProjectConfig } from '../../common/plugin-context'; -import { BindableDecl, DecoratorIntrinsicNames, StructDecoratorNames } from '../../common/predefines'; +import { BindableDecl, DecoratorIntrinsicNames, DecoratorNames } from '../../common/predefines'; import { ImportCollector } from '../../common/import-collector'; +import { addMemoAnnotation, collectMemoableInfoInParameter } from '../memo-translators/utils'; export class factory { - /** - * generate `reuseKey?: string` in `@ComponentBuilder` $_instantiate - */ - static createReusableKeyArgForCustomComponent(): arkts.ETSParameterExpression { - return arkts.factory - .createParameterDeclaration( - arkts.factory.createIdentifier( - 'reuseKey', - arkts.factory.createTypeReference( - arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('string')) - ) - ), - undefined - ) - .setOptional(true); - } - /** * generate `packageInfo: string` in `@ComponentBuilder` XComponent */ @@ -88,14 +68,15 @@ export class factory { node: arkts.MethodDefinition, prefixArgs: arkts.ETSParameterExpression[], newAnno: arkts.AnnotationUsage[], - newName: string | undefined + newName: string | undefined, + externalSourceName?: string ): arkts.MethodDefinition { const func: arkts.ScriptFunction = node.scriptFunction; - let newParams: arkts.Expression[]; + let newParams: arkts.Expression[] = []; if (func.params.length > 0) { - newParams = [...prefixArgs, ...func.params.slice(0, func.params.length - 1)]; - if (node.name.name === BuilderLambdaNames.ORIGIN_METHOD_NAME) { - newParams.push(this.createReusableKeyArgForCustomComponent()); + newParams.push(...prefixArgs, ...func.params.slice(0, func.params.length - 1)); + if (externalSourceName === 'arkui.component.xcomponent' && node.name.name === 'XComponent') { + newParams.push(this.createPackageInfoArgForXComponent()); } newParams.push(func.params.at(func.params.length - 1)!); } else { @@ -120,12 +101,24 @@ export class factory { node, node.kind, arkts.factory.updateIdentifier(node.name, newName ?? node.name.name), - updateFunc, + node.name.name === BuilderLambdaNames.ORIGIN_METHOD_NAME ? addMemoAnnotation(updateFunc) : updateFunc, node.modifiers, false ); } + /** + * update `@Builder` decorated parameter expression. + */ + static updateBuilderParameters(params: readonly arkts.Expression[]): arkts.Expression[] { + return params.map((item: arkts.Expression) => { + if (arkts.isEtsParameterExpression(item) && hasDecorator(item, DecoratorNames.BUILDER)) { + return addMemoAnnotation(item); + } + return item; + }); + } + /* * transform arguments in style node. */ @@ -229,9 +222,11 @@ export class factory { undefined ); + const returnStatement = arkts.factory.createReturnStatement(); + arkts.NodeCache.getInstance().collect(returnStatement); const body: arkts.BlockStatement = arkts.factory.createBlock([ arkts.factory.createExpressionStatement(lambdaBody), - arkts.factory.createReturnStatement(), + returnStatement, ]); const func = arkts.factory.createScriptFunction( @@ -246,7 +241,7 @@ export class factory { arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC ); - return arkts.factory.createArrowFunction(func); + return addMemoAnnotation(arkts.factory.createArrowFunction(func)); } /* @@ -269,25 +264,15 @@ export class factory { ), arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW ); + addMemoAnnotation(funcType); let parameter: arkts.ETSParameterExpression; - if (isFunctionCall) { - parameter = arkts.factory - .createParameterDeclaration( - arkts.factory.createIdentifier(BuilderLambdaNames.STYLE_PARAM_NAME, funcType), - undefined - ) - .setOptional(true); - } else { - const optionalFuncType = arkts.factory.createUnionType([funcType, arkts.factory.createETSUndefinedType()]); - parameter = arkts.factory.createParameterDeclaration( - arkts.factory.createIdentifier(BuilderLambdaNames.STYLE_PARAM_NAME, optionalFuncType), - undefined - ); - } - if (findCanAddMemoFromParamExpression(parameter)) { - addMemoAnnotation(parameter); - } + const optionalFuncType = arkts.factory.createUnionType([funcType, arkts.factory.createETSUndefinedType()]); + parameter = arkts.factory.createParameterDeclaration( + arkts.factory.createIdentifier(BuilderLambdaNames.STYLE_PARAM_NAME, optionalFuncType), + undefined + ); + arkts.NodeCache.getInstance().collect(parameter); return parameter; } @@ -379,16 +364,22 @@ export class factory { * If the corresponding argument is not provided, fill-in an `undefined` to it. */ static createOrUpdateArgInBuilderLambda( + fallback: arkts.AstNode | undefined, arg: arkts.Expression | undefined, projectConfig: ProjectConfig | undefined, typeName?: string, - fallback?: arkts.AstNode + canAddMemo?: boolean ): arkts.AstNode | undefined { if (!arg) { return fallback; } if (arkts.isArrowFunctionExpression(arg)) { - return this.processArgArrowFunction(arg, projectConfig); + const newNode = this.processArgArrowFunction(arg, projectConfig); + if (canAddMemo) { + // console.log("[DEBUG] newNode: ", newNode.dumpSrc()); + addMemoAnnotation(newNode); + } + return newNode; } // this is too optimistic to check if this is an options argument... if (arkts.isTSAsExpression(arg) || arkts.isObjectExpression(arg)) { @@ -397,6 +388,19 @@ export class factory { return arg; } + static createSecondLastArgInBuilderLambda(argInfo: BuilderLambdaSecondLastArgInfo): arkts.AstNode | undefined { + if (!!argInfo.isReusable && !!argInfo.reuseId) { + const reuseIdNode = arkts.factory.createStringLiteral(argInfo.reuseId); + return this.createOrUpdateArgInBuilderLambda(reuseIdNode, undefined, undefined); + } else if (!!argInfo.isXComponent && !!argInfo.packageInfo) { + const packageInfoNode = arkts.factory.createStringLiteral(argInfo.packageInfo); + return this.createOrUpdateArgInBuilderLambda(packageInfoNode, undefined, undefined); + } else if (!argInfo.isFunctionCall) { + return this.createOrUpdateArgInBuilderLambda(arkts.factory.createUndefinedLiteral(), undefined, undefined); + } + return undefined; + } + /** * transform arguments in a builder lambda call. */ @@ -408,50 +412,35 @@ export class factory { ): (arkts.AstNode | undefined)[] { const { isFunctionCall, params, returnType, moduleName } = declInfo; const type: arkts.Identifier | undefined = builderLambdaType(leaf); - let isReusable: boolean | undefined; - let reuseId: arkts.StringLiteral | undefined; - if (!isFunctionCall && !!type) { - const customComponentDecl = arkts.getDecl(type); - isReusable = - !!customComponentDecl && - arkts.isClassDefinition(customComponentDecl) && - customComponentDecl.annotations.some((anno) => - isCustomComponentAnnotation(anno, StructDecoratorNames.RESUABLE) - ); - reuseId = isReusable ? arkts.factory.createStringLiteral(type.name) : undefined; - } - const args: (arkts.AstNode | undefined)[] = [this.createStyleArgInBuilderLambda(lambdaBody, returnType, moduleName)]; - let index = 0; - while (index < params.length) { - if (isReusable && index === params.length - 1) { - args.push(this.createOrUpdateArgInBuilderLambda(undefined, undefined, undefined, reuseId)); - args.push( - this.createOrUpdateArgInBuilderLambda( - leaf.arguments.at(index), - projectConfig, - type?.name, - arkts.factory.createUndefinedLiteral() - ) - ); - } else if (type?.name === 'XComponent' && index === params.length - 1) { - let packageInfo: string = ''; - if (projectConfig?.bundleName && projectConfig?.moduleName) { - packageInfo = projectConfig?.bundleName + '/' + projectConfig?.moduleName; + const args: (arkts.AstNode | undefined)[] = [ + this.createStyleArgInBuilderLambda(lambdaBody, returnType, moduleName), + ]; + const secondLastArgInfo = buildSecondLastArgInfo(type, projectConfig, isFunctionCall); + const isTrailingCall = leaf.isTrailingCall; + forEachArgWithParam( + leaf.arguments, + params, + (arg, param, index) => { + let modifiedArg: arkts.AstNode | undefined; + if (index === params.length - 2 && !arg) { + modifiedArg = this.createSecondLastArgInBuilderLambda(secondLastArgInfo); } - const packageInfoNode = arkts.factory.createStringLiteral(packageInfo); - args.push( - this.createOrUpdateArgInBuilderLambda( - leaf.arguments.at(index), + if (!modifiedArg) { + const memoableInfo = collectMemoableInfoInParameter(param); + const canAddMemo = + (!!memoableInfo.hasBuilder || !!memoableInfo.hasMemo) && !!memoableInfo.hasProperType; + modifiedArg = this.createOrUpdateArgInBuilderLambda( + arkts.factory.createUndefinedLiteral(), + arg, projectConfig, type?.name, - packageInfoNode - ) - ); - } else { - args.push(this.createOrUpdateArgInBuilderLambda(leaf.arguments.at(index), projectConfig, type?.name)); - } - index++; - } + canAddMemo + ); + } + args.push(modifiedArg); + }, + { isTrailingCall } + ); return filterDefined(args); } @@ -548,12 +537,14 @@ export class factory { factory.transformBuilderLambdaMethodDecl(method) ); - return this.updateBuilderLambdaMethodDecl( + const newNode = this.updateBuilderLambdaMethodDecl( node, prefixArgs, removeAnnotationByName(func.annotations, BuilderLambdaNames.ANNOTATION_NAME), replaceBuilderLambdaDeclMethodName(node.name.name) ).setOverloads(newOverloads); + arkts.NodeCache.getInstance().collect(newNode); + return newNode; } /** @@ -635,6 +626,7 @@ export class factory { instanceCalls = instanceCalls.reverse(); this.updateAnimation(instanceCalls); lambdaBody = arkts.factory.createIdentifier(BuilderLambdaNames.STYLE_ARROW_PARAM_NAME); + arkts.NodeCache.getInstance().collect(lambdaBody); instanceCalls.forEach((callInfo) => { lambdaBody = this.createStyleLambdaBody(lambdaBody!, callInfo, projectConfig); }); @@ -646,7 +638,9 @@ export class factory { declInfo, projectConfig ); - return arkts.factory.updateCallExpression(node, replace, leaf.typeArguments, filterDefined(args)); + const newNode = arkts.factory.updateCallExpression(node, replace, leaf.typeArguments, filterDefined(args)); + arkts.NodeCache.getInstance().collect(newNode); + return newNode; } /* diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts index 7aac15fc1..2843c688f 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts @@ -15,10 +15,11 @@ import * as arkts from '@koalaui/libarkts'; import { isAnnotation, matchPrefix } from '../../common/arkts-utils'; -import { BuilderLambdaNames } from '../utils'; +import { BuilderLambdaNames, isCustomComponentAnnotation } from '../utils'; import { DeclarationCollector } from '../../common/declaration-collector'; -import { ARKUI_IMPORT_PREFIX_NAMES, BindableDecl, Dollars } from '../../common/predefines'; +import { ARKUI_IMPORT_PREFIX_NAMES, BindableDecl, Dollars, StructDecoratorNames } from '../../common/predefines'; import { ImportCollector } from '../../common/import-collector'; +import { ProjectConfig } from '../../common/plugin-context'; export type BuilderLambdaDeclInfo = { isFunctionCall: boolean; // isFunctionCall means it is from $_instantiate. @@ -34,6 +35,52 @@ export type InstanceCallInfo = { call: arkts.CallExpression; }; +export type BuilderLambdaArgInfo = { + isFunctionCall: boolean; +}; + +export type BuilderLambdaReusableArgInfo = { + isReusable?: boolean; + reuseId?: string; +}; + +export type BuilderLambdaXComponentArgInfo = { + isXComponent?: boolean; + packageInfo?: string; +}; + +export type BuilderLambdaSecondLastArgInfo = BuilderLambdaArgInfo & + BuilderLambdaReusableArgInfo & + BuilderLambdaXComponentArgInfo; + +export function buildSecondLastArgInfo( + type: arkts.Identifier | undefined, + projectConfig: ProjectConfig | undefined, + isFunctionCall: boolean +): BuilderLambdaSecondLastArgInfo { + let isReusable: boolean | undefined; + let reuseId: string | undefined; + let isXComponent: boolean | undefined; + let packageInfo: string = ''; + if (!isFunctionCall && !!type) { + const customComponentDecl = arkts.getDecl(type); + isReusable = + !!customComponentDecl && + arkts.isClassDefinition(customComponentDecl) && + customComponentDecl.annotations.some((anno) => + isCustomComponentAnnotation(anno, StructDecoratorNames.RESUABLE) + ); + reuseId = isReusable ? type.name : undefined; + } + if (type?.name === 'XComponent') { + isXComponent = true; + if (projectConfig?.bundleName && projectConfig?.moduleName) { + packageInfo = projectConfig?.bundleName + '/' + projectConfig?.moduleName; + } + } + return { isFunctionCall, isReusable, reuseId, isXComponent, packageInfo }; +} + /** * Used in finding "XXX" in BuilderLambda("XXX") * @deprecated diff --git a/arkui-plugins/ui-plugins/checked-transformer.ts b/arkui-plugins/ui-plugins/checked-transformer.ts index 357a41830..a8d75edca 100644 --- a/arkui-plugins/ui-plugins/checked-transformer.ts +++ b/arkui-plugins/ui-plugins/checked-transformer.ts @@ -19,24 +19,23 @@ import { factory as structFactory } from './struct-translators/factory'; import { factory as builderLambdaFactory } from './builder-lambda-translators/factory'; import { factory as entryFactory } from './entry-translators/factory'; import { AbstractVisitor } from '../common/abstract-visitor'; -import { - addMemoAnnotation, - collectCustomComponentScopeInfo, - CustomComponentNames, - isCustomComponentClass, -} from './utils'; -import { - CustomComponentScopeInfo, - ScopeInfoCollection, - findCanAddMemoFromArrowFunction, - isResourceNode, -} from './struct-translators/utils'; +import { collectCustomComponentScopeInfo, CustomComponentNames, isCustomComponentClass } from './utils'; +import { CustomComponentScopeInfo, ScopeInfoCollection, isResourceNode } from './struct-translators/utils'; import { isBuilderLambda, isBuilderLambdaMethodDecl } from './builder-lambda-translators/utils'; import { isEntryWrapperClass } from './entry-translators/utils'; import { ImportCollector } from '../common/import-collector'; import { DeclarationCollector } from '../common/declaration-collector'; import { PropertyCache } from './property-translators/utils'; import { isArkUICompatible, generateArkUICompatible } from './interop/interop'; +import { + addMemoAnnotation, + findCanAddMemoFromArrowFunction, + findCanAddMemoFromClassProperty, + findCanAddMemoFromMethod, + findCanAddMemoFromParameter, + findCanAddMemoFromProperty, + findCanAddMemoFromTypeAlias, +} from './memo-translators/utils'; export class CheckedTransformer extends AbstractVisitor { private scope: ScopeInfoCollection; @@ -50,6 +49,7 @@ export class CheckedTransformer extends AbstractVisitor { reset(): void { super.reset(); + // this.structTransformer.reset(); this.scope = { customComponents: [] }; PropertyCache.getInstance().reset(); ImportCollector.getInstance().reset(); @@ -88,7 +88,10 @@ export class CheckedTransformer extends AbstractVisitor { const lambda = builderLambdaFactory.transformBuilderLambda(beforeChildren, this.projectConfig); return this.visitEachChild(lambda); } else if (arkts.isMethodDefinition(beforeChildren) && isBuilderLambdaMethodDecl(beforeChildren)) { - const lambda = builderLambdaFactory.transformBuilderLambdaMethodDecl(beforeChildren, this.externalSourceName); + const lambda = builderLambdaFactory.transformBuilderLambdaMethodDecl( + beforeChildren, + this.externalSourceName + ); return this.visitEachChild(lambda); } const node = this.visitEachChild(beforeChildren); @@ -112,12 +115,21 @@ export class CheckedTransformer extends AbstractVisitor { return generateArkUICompatible(node as arkts.CallExpression); } else if (arkts.isTSInterfaceDeclaration(node)) { return structFactory.tranformInterfaceMembers(node, this.externalSourceName); + } else if (findCanAddMemoFromProperty(node)) { + addMemoAnnotation(node.value! as arkts.ArrowFunctionExpression); + } else if (findCanAddMemoFromClassProperty(node)) { + addMemoAnnotation(node); + } else if (findCanAddMemoFromTypeAlias(node)) { + addMemoAnnotation(node); + } else if (findCanAddMemoFromParameter(node)) { + addMemoAnnotation(node); + } else if (findCanAddMemoFromMethod(node)) { + addMemoAnnotation(node.scriptFunction); } else if (findCanAddMemoFromArrowFunction(node)) { - return addMemoAnnotation(node); - } else if (arkts.isEtsScript(node) && ImportCollector.getInstance().importInfos.length > 0) { + addMemoAnnotation(node.scriptFunction); + } + if (arkts.isEtsScript(node) && ImportCollector.getInstance().importInfos.length > 0) { ImportCollector.getInstance().insertCurrentImports(this.program); - } else if (arkts.isTSTypeAliasDeclaration(node)) { - return structFactory.transformTSTypeAlias(node); } return node; } diff --git a/arkui-plugins/ui-plugins/component-transformer.ts b/arkui-plugins/ui-plugins/component-transformer.ts index 1bde82161..0cd5b18b3 100644 --- a/arkui-plugins/ui-plugins/component-transformer.ts +++ b/arkui-plugins/ui-plugins/component-transformer.ts @@ -69,7 +69,10 @@ export class ComponentTransformer extends AbstractVisitor { private entryNames: string[] = []; private structMembersMap: Map = new Map(); private isCustomComponentImported: boolean = false; + private isCustomComponentV2Imported: boolean = false; private isEntryPointImported: boolean = false; + private isPageLifeCycleImported: boolean = false; + private isLayoutCallbackImported: boolean = false; private shouldAddLinkIntrinsic: boolean = false; private hasLegacy: boolean = false; private legacyStructMap: Map = new Map(); @@ -87,7 +90,10 @@ export class ComponentTransformer extends AbstractVisitor { this.entryNames = []; this.structMembersMap = new Map(); this.isCustomComponentImported = false; + this.isCustomComponentV2Imported = false; this.isEntryPointImported = false; + this.isPageLifeCycleImported = false; + this.isLayoutCallbackImported = false; this.shouldAddLinkIntrinsic = false; this.hasLegacy = false; this.legacyStructMap = new Map(); @@ -108,6 +114,13 @@ export class ComponentTransformer extends AbstractVisitor { CustomComponentNames.COMPONENT_CLASS_NAME ); } + if (arkts.isETSImportDeclaration(node) && !this.isCustomComponentV2Imported) { + this.isCustomComponentV2Imported = !!findLocalImport( + node, + CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, + CustomComponentNames.COMPONENT_V2_CLASS_NAME + ); + } if (arkts.isETSImportDeclaration(node) && !this.isEntryPointImported) { this.isEntryPointImported = !!findLocalImport( node, @@ -115,6 +128,20 @@ export class ComponentTransformer extends AbstractVisitor { EntryWrapperNames.ENTRY_POINT_CLASS_NAME ); } + if (arkts.isETSImportDeclaration(node) && !this.isPageLifeCycleImported) { + this.isPageLifeCycleImported = !!findLocalImport( + node, + CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, + CustomComponentNames.PAGE_LIFE_CYCLE + ); + } + if (arkts.isETSImportDeclaration(node) && !this.isLayoutCallbackImported) { + this.isLayoutCallbackImported = !!findLocalImport( + node, + CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, + CustomComponentNames.LAYOUT_CALLBACK + ); + } } exit(node: arkts.AstNode) { @@ -126,9 +153,9 @@ export class ComponentTransformer extends AbstractVisitor { } } - createImportDeclaration(): void { - const source: arkts.StringLiteral = arkts.factory.create1StringLiteral(CUSTOM_COMPONENT_IMPORT_SOURCE_NAME); - const imported: arkts.Identifier = arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_CLASS_NAME); + createImportDeclaration(sourceName: string, importedName: string): void { + const source: arkts.StringLiteral = arkts.factory.create1StringLiteral(sourceName); + const imported: arkts.Identifier = arkts.factory.createIdentifier(importedName); // Insert this import at the top of the script's statements. if (!this.program) { throw Error('Failed to insert import: Transformer has no program'); @@ -152,7 +179,18 @@ export class ComponentTransformer extends AbstractVisitor { updateStatements.push(factory.createIntrinsicAnnotationDeclaration({ expr })); } if (this.componentInterfaceCollection.length > 0) { - if (!this.isCustomComponentImported) this.createImportDeclaration(); + if (!this.isCustomComponentImported) + this.createImportDeclaration( + CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, + CustomComponentNames.COMPONENT_CLASS_NAME + ); + if (!this.isCustomComponentV2Imported) + this.createImportDeclaration( + CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, + CustomComponentNames.COMPONENT_V2_CLASS_NAME + ); + if (!this.isLayoutCallbackImported) + this.createImportDeclaration(CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, CustomComponentNames.LAYOUT_CALLBACK); updateStatements.push(...this.componentInterfaceCollection); } @@ -160,6 +198,8 @@ export class ComponentTransformer extends AbstractVisitor { if (!this.isEntryPointImported) entryFactory.createAndInsertEntryPointImport(this.program); // normally, we should only have at most one @Entry component in a single file. // probably need to handle error message here. + if (!this.isPageLifeCycleImported) + this.createImportDeclaration(CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, CustomComponentNames.PAGE_LIFE_CYCLE); updateStatements.push(...this.entryNames.map(entryFactory.generateEntryWrapper)); } if (updateStatements.length > 0) { @@ -210,20 +250,16 @@ export class ComponentTransformer extends AbstractVisitor { if (!className || scopeInfo?.name !== className) { return node; } - - arkts.insertGlobalStructInfo(className); - + // arkts.insertGlobalStructInfo(className); if (arkts.isStructDeclaration(node)) { this.collectComponentMembers(node, className); } - const customComponentInterface = this.generateComponentInterface( className, node.modifiers | arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_EXPORT, Object.values(scopeInfo.annotations ?? {}).map((anno) => anno.clone()) ); this.componentInterfaceCollection.push(customComponentInterface); - const definition: arkts.ClassDefinition = node.definition!; const newDefinitionBody: arkts.AstNode[] = []; if (!!scopeInfo.annotations?.entry) { @@ -269,26 +305,24 @@ export class ComponentTransformer extends AbstractVisitor { staticMethodBody.push(buildCompatibleNode); } } + const scopeInfo = this.scopeInfos[this.scopeInfos.length - 1]; + const extendsName: string = scopeInfo.annotations.component + ? CustomComponentNames.COMPONENT_CLASS_NAME + : CustomComponentNames.COMPONENT_V2_CLASS_NAME; return arkts.factory .createClassDefinition( definition.ident, undefined, undefined, // superTypeParams doen't work - definition.implements, + [...definition.implements, ...factory.generateImplementsForStruct(scopeInfo.annotations)], undefined, arkts.factory.createTypeReference( arkts.factory.createTypeReferencePart( - arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_CLASS_NAME), + arkts.factory.createIdentifier(extendsName), arkts.factory.createTSTypeParameterInstantiation([ - arkts.factory.createTypeReference( - arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier(className)) - ), - arkts.factory.createTypeReference( - arkts.factory.createTypeReferencePart( - arkts.factory.createIdentifier( - `${CustomComponentNames.COMPONENT_INTERFACE_PREFIX}${className}` - ) - ) + factory.createTypeReferenceFromString(className), + factory.createTypeReferenceFromString( + `${CustomComponentNames.COMPONENT_INTERFACE_PREFIX}${className}` ), ]) ) @@ -407,9 +441,7 @@ export class ComponentTransformer extends AbstractVisitor { const context: InteropContext = { className: className, path: path, - arguments: args && args.length === 1 && args[0] instanceof arkts.ObjectExpression - ? args[0] - : undefined + arguments: args && args.length === 1 && args[0] instanceof arkts.ObjectExpression ? args[0] : undefined, }; return generateInstantiateInterop(context); } diff --git a/arkui-plugins/ui-plugins/entry-translators/factory.ts b/arkui-plugins/ui-plugins/entry-translators/factory.ts index 09d1c85b7..4fedb602a 100644 --- a/arkui-plugins/ui-plugins/entry-translators/factory.ts +++ b/arkui-plugins/ui-plugins/entry-translators/factory.ts @@ -17,6 +17,7 @@ import * as arkts from '@koalaui/libarkts'; import { EntryWrapperNames } from './utils'; import { annotation, createAndInsertImportDeclaration } from '../../common/arkts-utils'; import { ENTRY_POINT_IMPORT_SOURCE_NAME } from '../../common/predefines'; +import { addMemoAnnotation } from '../memo-translators/utils'; export class factory { /** @@ -219,7 +220,8 @@ export class factory { !!member.scriptFunction.id && member.scriptFunction.id.name === EntryWrapperNames.ENTRY_FUNC ) { - member.scriptFunction.setAnnotations([annotation('memo')]); + addMemoAnnotation(member.scriptFunction); + arkts.NodeCache.getInstance().collect(member); } }); } diff --git a/arkui-plugins/ui-plugins/index.ts b/arkui-plugins/ui-plugins/index.ts index a50014ecf..453ca60e0 100644 --- a/arkui-plugins/ui-plugins/index.ts +++ b/arkui-plugins/ui-plugins/index.ts @@ -51,11 +51,11 @@ function parsedTransform(this: PluginContext): arkts.EtsScript | undefined { ); arkts.Performance.getInstance().createEvent('ui-parsed'); const componentTransformer = new ComponentTransformer(); - const preprocessorTransformer = new PreprocessorTransformer(); + // const preprocessorTransformer = new PreprocessorTransformer(); const programVisitor = new ProgramVisitor({ pluginName: uiTransform.name, state: arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, - visitors: [componentTransformer, preprocessorTransformer], + visitors: [componentTransformer], skipPrefixNames: EXTERNAL_SOURCE_PREFIX_NAMES, pluginContext: this, }); @@ -114,9 +114,9 @@ function checkedTransform(this: PluginContext): arkts.EtsScript | undefined { cachePath, program.fileNameWithExtension ); - arkts.Performance.getInstance().createEvent('ui-recheck'); - arkts.recheckSubtree(script); - arkts.Performance.getInstance().stopEvent('ui-recheck', false); + // arkts.Performance.getInstance().createEvent('ui-recheck'); + // arkts.recheckSubtree(script); + // arkts.Performance.getInstance().stopEvent('ui-recheck', false); arkts.Performance.getInstance().clearAllEvents(false); arkts.Performance.getInstance().visualizeEvents(true); arkts.Performance.getInstance().clearHistory(); diff --git a/arkui-plugins/ui-plugins/memo-translators/function-collector.ts b/arkui-plugins/ui-plugins/memo-translators/function-collector.ts new file mode 100644 index 000000000..590f7b508 --- /dev/null +++ b/arkui-plugins/ui-plugins/memo-translators/function-collector.ts @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as arkts from '@koalaui/libarkts'; +import { AbstractVisitor } from '../../common/abstract-visitor'; +import { + checkIsMemoFromMemoableInfo, + collectMemoableInfoInFunctionReturnType, + collectMemoableInfoInParameter, + collectMemoableInfoInScriptFunction, + collectMemoableInfoInVariableDeclarator, + collectMemoableInfoMapInFunctionParams, + getDeclResolveAlias, + MemoableInfo, +} from './utils'; +import { forEachArgWithParam } from '../utils'; + +export class MemoFunctionCollector extends AbstractVisitor { + private returnMemoableInfo: MemoableInfo | undefined; + private paramMemoableInfoMap: Map | undefined; + public shouldCollectReturn: boolean = true; + private skips: arkts.AstNode['peer'][] = []; + + private collectReturnArgument(argument: arkts.ArrowFunctionExpression, info: MemoableInfo): void { + if ((!info.hasBuilder && !info.hasMemo) || !info.hasProperType) { + return; + } + arkts.NodeCache.getInstance().collect(argument); + } + + private collectGensymDeclarator(declarator: arkts.VariableDeclarator, info: MemoableInfo): void { + if ((!info.hasBuilder && !info.hasMemo) || !info.hasProperType) { + return; + } + arkts.NodeCache.getInstance().collect(declarator); + } + + registerReturnInfo(info: MemoableInfo): this { + this.returnMemoableInfo = info; + return this; + } + + registerParamInfoMap(infoMap: Map): this { + this.paramMemoableInfoMap = infoMap; + return this; + } + + skip(peers: arkts.AstNode['peer'][]): this { + this.skips = peers; + return this; + } + + reset(): void { + this.returnMemoableInfo = undefined; + this.paramMemoableInfoMap = undefined; + this.shouldCollectReturn = true; + this.skips = []; + } + + visitor(node: arkts.AstNode): arkts.AstNode { + // console.log('[MemoFunctionCollector] [VISITOR] node: ', node.dumpSrc()); + if (arkts.isVariableDeclarator(node)) { + // console.log('[MemoFunctionCollector] isVariableDeclarator'); + let memoableInfo: MemoableInfo; + if (this.paramMemoableInfoMap?.has(node.name.peer)) { + // console.log("[MemoFunctionCollector] [VariableDeclarator] node: ", node.dumpSrc()); + // console.log("[MemoFunctionCollector] [VariableDeclarator] node: ", node.dumpJson()); + memoableInfo = this.paramMemoableInfoMap.get(node.name.peer)!; + // console.log("[MemoFunctionCollector] [VariableDeclarator] memoableInfo: ", memoableInfo); + } else { + // console.log("[MemoFunctionCollector] [VariableDeclarator] node: ", node.dumpSrc()); + memoableInfo = collectMemoableInfoInVariableDeclarator(node); + // console.log("[MemoFunctionCollector] [VariableDeclarator] memoableInfo: ", memoableInfo); + } + this.collectGensymDeclarator(node, memoableInfo); + if (!node.initializer) { + return node; + } + if (arkts.isArrowFunctionExpression(node.initializer)) { + const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(node.initializer.scriptFunction); + const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams(node.initializer.scriptFunction); + if ( + !!node.initializer.scriptFunction.body && + arkts.isBlockStatement(node.initializer.scriptFunction.body) + ) { + const collector = new MemoFunctionCollector(); + node.initializer.scriptFunction.body.statements.forEach((st, index) => { + if (index < gensymCount) { + return; + } + collector + .registerReturnInfo(returnMemoableInfo) + .registerParamInfoMap(paramMemoableInfoMap) + .visitor(st); + collector.reset(); + }); + // collector + // .registerReturnInfo(returnMemoableInfo) + // .registerParamInfoMap(paramMemoableInfoMap) + // .visitor(node.initializer.scriptFunction); + // collector.reset(); + } + return node; + } + this.shouldCollectReturn = (!!memoableInfo.hasMemo || !!memoableInfo.hasBuilder); + this.visitor(node.initializer); + return node; + } else if (arkts.isCallExpression(node)) { + // console.log('[MemoFunctionCollector] isCallExpression'); + // console.log('[MemoFunctionCollector] [CallExpression] node: ', node.dumpSrc()); + const expr = findIdentifierFromCallee(node.expression); + // console.log('[MemoFunctionCollector] [CallExpression] expr: ', expr?.dumpSrc()); + const decl = (expr && getDeclResolveAlias(expr)) ?? node.expression; + if (!decl) { + this.shouldCollectReturn = false; + const newNode = this.visitEachChild(node); + this.shouldCollectReturn = true; + return newNode; + } + // console.log('[MemoFunctionCollector] [CallExpression] decl: ', decl?.dumpSrc()); + // console.log( + // '[MemoFunctionCollector] [CallExpression] decl.identifier: ', + // arkts.isEtsParameterExpression(decl) && decl.identifier.peer + // ); + // console.log("[MemoFunctionCollector] [CallExpression] OTHER NODE: ", node.dumpSrc()); + // console.log("[MemoFunctionCollector] [CallExpression] OTHER DECL: ", decl.dumpSrc()); + if (this.paramMemoableInfoMap?.has(decl.peer)) { + // console.log("[MemoFunctionCollector] [CallExpression] paramMemoableInfoMap ident info: ", this.paramMemoableInfoMap.get(decl.peer)) + const memoableInfo = this.paramMemoableInfoMap.get(decl.peer)!; + if (memoableInfo.hasMemo || memoableInfo.hasBuilder) { + arkts.NodeCache.getInstance().collect(node); + } + } else if (arkts.isEtsParameterExpression(decl) && this.paramMemoableInfoMap?.has(decl.identifier.peer)) { + // console.log("[MemoFunctionCollector] [CallExpression] paramMemoableInfoMap param info: ", this.paramMemoableInfoMap.get(decl.identifier.peer)) + const memoableInfo = this.paramMemoableInfoMap.get(decl.identifier.peer)!; + if (memoableInfo.hasMemo || memoableInfo.hasBuilder) { + arkts.NodeCache.getInstance().collect(node); + } + } else if (arkts.isMethodDefinition(decl)) { + const hasReceiver = decl.scriptFunction.hasReceiver; + const params = decl.scriptFunction.params; + const args = node.arguments; + const hasRestParameter = decl.scriptFunction.hasRestParameter; + const isTrailingCall = node.isTrailingCall; + // const argsToVisit: arkts.Expression[] = []; + forEachArgWithParam( + args, + params, + (arg, param) => { + if (!arg) { + return; + } + let info: MemoableInfo; + if (arkts.NodeCache.getInstance().has(param)) { + info = { hasMemo: true, hasProperType: true }; + } else { + info = collectMemoableInfoInParameter(param); + } + if ( + (info.hasMemo || info.hasBuilder) && + info.hasProperType && + arkts.isArrowFunctionExpression(arg) + ) { + arkts.NodeCache.getInstance().collect(arg); + const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(arg.scriptFunction); + const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams(arg.scriptFunction); + if (!!arg.scriptFunction.body && arkts.isBlockStatement(arg.scriptFunction.body)) { + const collector = new MemoFunctionCollector(); + arg.scriptFunction.body.statements.forEach((st, index) => { + if (index < gensymCount) { + return; + } + collector + .registerReturnInfo(returnMemoableInfo) + .registerParamInfoMap(paramMemoableInfoMap) + .visitor(st); + collector.reset(); + }); + // collector + // .registerReturnInfo(returnMemoableInfo) + // .registerParamInfoMap(paramMemoableInfoMap) + // .visitor(arg.scriptFunction); + // collector.reset(); + } + } + }, + { hasRestParameter, isTrailingCall } + ); + if (arkts.NodeCache.getInstance().has(decl)) { + arkts.NodeCache.getInstance().collect(node, { hasReceiver }); + } else { + const memoableInfo = collectMemoableInfoInScriptFunction(decl.scriptFunction); + if (memoableInfo.hasMemo || memoableInfo.hasBuilder) { + // console.log('[MemoFunctionCollector] [CallExpression] COLLECT metadata hasReceiver: ', hasReceiver); + arkts.NodeCache.getInstance().collect(node, { hasReceiver }); + } + } + // const currShouldCollectReturn = this.shouldCollectReturn; + // this.shouldCollectReturn = true; + // argsToVisit.forEach(arg => this.visitor(arg)); + // this.shouldCollectReturn = currShouldCollectReturn; + } else if (arkts.isIdentifier(decl) && !!decl.parent && arkts.isVariableDeclarator(decl.parent)) { + // console.log("[MemoFunctionCollector] [CallExpression] IDENTIFIER decl: ", decl.dumpSrc()) + // console.log("[MemoFunctionCollector] [CallExpression] IDENTIFIER decl.parent: ", decl.parent.dumpSrc()) + const shouldCollect = + arkts.NodeCache.getInstance().has(decl.parent) || + (!!decl.parent.initializer && arkts.NodeCache.getInstance().has(decl.parent.initializer)); + if (shouldCollect) { + arkts.NodeCache.getInstance().collect(node); + } + } else if (arkts.NodeCache.getInstance().has(decl)) { + arkts.NodeCache.getInstance().collect(node); + } + this.shouldCollectReturn = false; + this.visitEachChild(node); + this.shouldCollectReturn = true; + return node; + } + if (!!this.paramMemoableInfoMap && arkts.isIdentifier(node)) { + // console.log('[MemoFunctionCollector] isIdentifier'); + // console.log("[MemoFunctionCollector] [Identifier] node: ", node.dumpSrc()); + const decl = getDeclResolveAlias(node); + if (!decl) { + return node; + } + // console.log("[MemoFunctionCollector] [Identifier] node 11: ", node.dumpSrc()); + // console.log("[MemoFunctionCollector] [Identifier] decl: ", decl.dumpSrc()); + if (this.paramMemoableInfoMap?.has(decl.peer)) { + // console.log('[PARAM COLLECTOR] [GENSYM] GETTER: ', decl.peer); + // console.log("[MemoFunctionCollector] [GENSYM] paramMemoableInfoMap ident info: ", this.paramMemoableInfoMap.get(decl.peer)); + arkts.NodeCache.getInstance().collect(node); + } else if (arkts.isEtsParameterExpression(decl) && this.paramMemoableInfoMap?.has(decl.identifier.peer)) { + // console.log('[PARAM COLLECTOR] [EtsParameterExpression] GETTER: ', decl.identifier); + // console.log('[PARAM COLLECTOR] [EtsParameterExpression] paramMemoableInfoMap ident info: ', this.paramMemoableInfoMap?.get(decl.identifier.peer)); + arkts.NodeCache.getInstance().collect(node); + } + return node; + } + if (arkts.isReturnStatement(node) && this.shouldCollectReturn) { + // console.log('[MemoFunctionCollector] isReturnStatement'); + // console.log('[MemoFunctionCollector] [ReturnStatement] node: ', node.dumpSrc()); + if (!!this.returnMemoableInfo && !!node.argument && arkts.isArrowFunctionExpression(node.argument)) { + this.collectReturnArgument(node.argument, this.returnMemoableInfo); + } + arkts.NodeCache.getInstance().collect(node); + this.visitEachChild(node); + return node; + } + if ( + arkts.isArrowFunctionExpression(node) && + !arkts.NodeCache.getInstance().has(node) && + !arkts.NodeCache.getInstance().has(node.scriptFunction) + ) { + // console.log("[MemoFunctionCollector] [ArrowFunctionExpression] node: ", node.dumpSrc()); + // this.autoSetCollectReturnFlag(false); + this.shouldCollectReturn = false; + } + this.visitEachChild(node); + return node; + } +} + +function findIdentifierFromCallee(callee: arkts.AstNode | undefined): arkts.Identifier | undefined { + if (!callee) { + return undefined; + } + if (arkts.isIdentifier(callee)) { + return callee; + } + if (arkts.isMemberExpression(callee)) { + return findIdentifierFromCallee(callee.property); + } + return undefined; +} diff --git a/arkui-plugins/ui-plugins/memo-translators/memo-visitor.ts b/arkui-plugins/ui-plugins/memo-translators/memo-visitor.ts new file mode 100644 index 000000000..e981acb7c --- /dev/null +++ b/arkui-plugins/ui-plugins/memo-translators/memo-visitor.ts @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as arkts from '@koalaui/libarkts'; +import { AbstractVisitor } from '../../common/abstract-visitor'; +import { + addMemoAnnotation, + findCanAddMemoFromArrowFunction, + findCanAddMemoFromClassProperty, + findCanAddMemoFromMethod, + findCanAddMemoFromParameter, + findCanAddMemoFromProperty, + findCanAddMemoFromTypeAlias, +} from './utils'; + +export class MemoVisitor extends AbstractVisitor { + visitor(node: arkts.AstNode): arkts.AstNode { + const newNode = this.visitEachChild(node); + if (findCanAddMemoFromProperty(newNode)) { + addMemoAnnotation(newNode.value! as arkts.ArrowFunctionExpression); + } else if (findCanAddMemoFromClassProperty(newNode)) { + addMemoAnnotation(newNode); + } else if (findCanAddMemoFromTypeAlias(newNode)) { + addMemoAnnotation(newNode); + } else if (findCanAddMemoFromParameter(newNode)) { + addMemoAnnotation(newNode); + } else if (findCanAddMemoFromMethod(newNode)) { + addMemoAnnotation(newNode.scriptFunction); + } else if (findCanAddMemoFromArrowFunction(newNode)) { + addMemoAnnotation(newNode.scriptFunction); + } + return newNode; + } +} diff --git a/arkui-plugins/ui-plugins/memo-translators/utils.ts b/arkui-plugins/ui-plugins/memo-translators/utils.ts new file mode 100644 index 000000000..ba0ef1a7f --- /dev/null +++ b/arkui-plugins/ui-plugins/memo-translators/utils.ts @@ -0,0 +1,719 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as arkts from '@koalaui/libarkts'; +import { annotation } from '../../common/arkts-utils'; +import { ImportCollector } from '../../common/import-collector'; +import { DecoratorNames, MEMO_IMPORT_SOURCE_NAME } from '../../common/predefines'; +import { isDecoratorAnnotation } from '../property-translators/utils'; +import { MemoFunctionCollector } from './function-collector'; + +export enum MemoNames { + MEMO = 'memo', + MEMO_SKIP = 'memo_skip', +} + +export type MemoAstNode = + | arkts.ScriptFunction + | arkts.ETSParameterExpression + | arkts.ClassProperty + | arkts.TSTypeAliasDeclaration + | arkts.ETSFunctionType + | arkts.ArrowFunctionExpression + | arkts.ETSUnionType + | arkts.VariableDeclaration; + +interface MemoableAnnotationInfo { + hasMemo?: boolean; + hasMemoSkip?: boolean; + hasBuilder?: boolean; +} + +export type MemoableInfo = MemoableAnnotationInfo & { + hasProperType?: boolean; + // isReturnVoid?: boolean; + // hasReceiver?: boolean; +}; + +export function hasMemoAnnotation(node: T): boolean { + return node.annotations.some((it) => isMemoAnnotation(it, MemoNames.MEMO)); +} + +export function hasMemoableAnnotation(node: T): MemoableAnnotationInfo { + let hasBuilder: boolean = false; + let hasMemo: boolean = false; + let hasMemoSkip: boolean = false; + node.annotations.forEach((it) => { + hasBuilder ||= isDecoratorAnnotation(it, DecoratorNames.BUILDER); + hasMemo ||= isMemoAnnotation(it, MemoNames.MEMO); + hasMemoSkip ||= isMemoAnnotation(it, MemoNames.MEMO_SKIP); + }); + return { + ...(hasMemo ? { hasMemo } : {}), + ...(hasMemoSkip ? { hasMemoSkip } : {}), + ...(hasBuilder ? { hasBuilder } : {}), + }; +} + +export function collectMemoAnnotationImport(memoName: MemoNames = MemoNames.MEMO): void { + ImportCollector.getInstance().collectImport(memoName); +} + +export function collectMemoAnnotationSource(memoName: MemoNames = MemoNames.MEMO): void { + ImportCollector.getInstance().collectSource(memoName, MEMO_IMPORT_SOURCE_NAME); +} + +export function collectMemoableInfoInUnionType(node: arkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (arkts.NodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!arkts.isETSUnionType(node)) { + return currInfo; + } + node.types.forEach((t) => { + currInfo = { + ...currInfo, + ...collectMemoableInfoInTypeReference(t), + ...collectMemoableInfoInFunctionType(t), + ...collectMemoableInfoInUnionType(t), + }; + }); + currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; + return currInfo; +} + +export function collectMemoableInfoInTypeReference(node: arkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (arkts.NodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!arkts.isETSTypeReference(node) || !node.part || !arkts.isETSTypeReferencePart(node.part)) { + return currInfo; + } + const expr = node.part.name; + let decl: arkts.AstNode | undefined; + if (!expr || !(decl = arkts.getDecl(expr))) { + return currInfo; + } + return { + ...currInfo, + ...collectMemoableInfoInTypeAlias(decl), + }; +} + +export function collectMemoableInfoInFunctionType(node: arkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (arkts.NodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!arkts.isETSFunctionType(node)) { + return currInfo; + } + currInfo.hasProperType = true; + // currInfo.isReturnVoid = isReturnVoidInType(node.returnType); + currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; + return currInfo; +} + +export function collectMemoableInfoInTypeAlias(node: arkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (arkts.NodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!arkts.isTSTypeAliasDeclaration(node)) { + return currInfo; + } + currInfo = { + ...currInfo, + ...hasMemoableAnnotation(node), + }; + if (!!node.typeAnnotation) { + return { + ...currInfo, + ...collectMemoableInfoInType(node.typeAnnotation), + }; + } + return currInfo; +} + +export function collectMemoableInfoInParameter(node: arkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (arkts.NodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!arkts.isEtsParameterExpression(node)) { + return currInfo; + } + currInfo = { + ...currInfo, + ...hasMemoableAnnotation(node), + }; + if (!!node.type) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInType(node.type), + }; + } + if (!!node.initializer) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInArrowFunction(node.initializer), + }; + } + return currInfo; +} + +export function collectMemoableInfoInVariableDeclarator(node: arkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (arkts.NodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!arkts.isVariableDeclarator(node)) { + return currInfo; + } + if (!!node.name.typeAnnotation) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInType(node.name.typeAnnotation), + }; + } + if (!!node.initializer && arkts.isArrowFunctionExpression(node.initializer)) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInArrowFunction(node.initializer), + }; + } + if (!!node.parent && arkts.isVariableDeclaration(node.parent)) { + currInfo = { + ...currInfo, + ...hasMemoableAnnotation(node.parent), + }; + } + const decl = arkts.getDecl(node.name); + // console.log("[collectMemoableInfoInVariableDeclarator] node.name: ", node.name.dumpSrc()); + if (!decl) { + return currInfo; + } + // console.log("[collectMemoableInfoInVariableDeclarator] decl: ", decl.dumpSrc()); + if (arkts.isMethodDefinition(decl)) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInScriptFunction(decl.scriptFunction), + }; + } else if (arkts.isClassProperty(decl)) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInClassProperty(decl), + }; + } + return currInfo; +} + +export function collectMemoableInfoInProperty(node: arkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (arkts.NodeCache.getInstance().has(node)) { + const property = node as arkts.Property; + const hasProperType = !!property.value && arkts.isArrowFunctionExpression(property.value); + return { ...currInfo, hasMemo: true, hasProperType }; + } + if (!arkts.isProperty(node) || !node.key || !arkts.isIdentifier(node.key)) { + return currInfo; + } + const decl = arkts.getDecl(node.key); + if (!decl || !arkts.isMethodDefinition(decl)) { + return currInfo; + } + const hasReceiver = decl.scriptFunction.hasReceiver; + const isSetter = decl.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; + const isGetter = decl.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; + let newInfo: MemoableInfo = {} + if (isSetter && decl.scriptFunction.params.length > 0) { + if (hasReceiver && decl.scriptFunction.params.length === 2) { + newInfo = collectMemoableInfoInParameter(decl.scriptFunction.params.at(1)!); + } else { + newInfo = collectMemoableInfoInParameter(decl.scriptFunction.params.at(0)!); + } + } else if (isGetter) { + newInfo = collectMemoableInfoInFunctionReturnType(decl.scriptFunction); + } + currInfo = { ...currInfo, ...collectMemoableInfoInScriptFunction(decl.scriptFunction), ...newInfo }; + // currInfo.hasReceiver = hasReceiver; + currInfo.hasProperType = false; + if (!!node.value && arkts.isArrowFunctionExpression(node.value)) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInScriptFunction(node.value.scriptFunction), + }; + } + return currInfo; +} + +export function collectMemoableInfoInClassProperty(node: arkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (arkts.NodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!arkts.isClassProperty(node)) { + return currInfo; + } + currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; + if (!!node.typeAnnotation) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInType(node.typeAnnotation), + }; + } + if (!!node.value) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInArrowFunction(node.value), + }; + } + return currInfo; +} + +export function collectMemoableInfoInArrowFunction(node: arkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (arkts.NodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!arkts.isArrowFunctionExpression(node)) { + return currInfo; + } + currInfo.hasProperType = true; + currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; + if (!!node.scriptFunction) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInScriptFunction(node.scriptFunction), + }; + } + if (!!node.parent && arkts.isAssignmentExpression(node.parent) && !!node.parent.left) { + const expr = arkts.isMemberExpression(node.parent.left) ? node.parent.left.property : node.parent.left; + const decl = arkts.getDecl(expr); + if (!decl) { + return currInfo; + } + if (arkts.isClassProperty(decl)) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInClassProperty(decl), + }; + } + } + return currInfo; +} + +export function collectMemoableInfoInScriptFunction(node: arkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (arkts.NodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!arkts.isScriptFunction(node)) { + return currInfo; + } + currInfo.hasProperType = true; + currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; + return currInfo; +} + +export function collectMemoableInfoInType(node: arkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + // console.log("[collectMemoableInfoInType] function: ", collectMemoableInfoInFunctionType(node)); + // console.log("[collectMemoableInfoInType] union: ", collectMemoableInfoInUnionType(node)); + // console.log("[collectMemoableInfoInType] typereference: ", collectMemoableInfoInTypeReference(node)); + return { + ...currInfo, + ...collectMemoableInfoInFunctionType(node), + ...collectMemoableInfoInUnionType(node), + ...collectMemoableInfoInTypeReference(node), + }; +} + +export function collectMemoableInfo(node: T, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (arkts.NodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + return { + ...currInfo, + ...collectMemoableInfoInScriptFunction(node), + ...collectMemoableInfoInClassProperty(node), + ...collectMemoableInfoInParameter(node), + ...collectMemoableInfoInTypeAlias(node), + ...collectMemoableInfoInFunctionType(node), + ...collectMemoableInfoInArrowFunction(node), + ...collectMemoableInfoInUnionType(node), + }; +} + +export function collectMemoableInfoInFunctionReturnType(node: arkts.ScriptFunction): MemoableInfo { + if (!!node.returnTypeAnnotation) { + let memoableInfo: MemoableInfo; + if (arkts.NodeCache.getInstance().has(node.returnTypeAnnotation)) { + memoableInfo = { hasMemo: true, hasProperType: true }; + } else { + memoableInfo = collectMemoableInfoInType(node.returnTypeAnnotation); + } + if ((memoableInfo.hasMemo || memoableInfo.hasBuilder) && memoableInfo.hasProperType) { + arkts.NodeCache.getInstance().collect(node.returnTypeAnnotation); + } + return memoableInfo; + } + return {}; +} + +export function collectGensymDeclarator(declarator: arkts.VariableDeclarator, info: MemoableInfo): void { + if (!info.hasMemo && !info.hasBuilder) { + return; + } + arkts.NodeCache.getInstance().collect(declarator); + // let isReturnVoid = info.isReturnVoid; + const type = declarator.name.typeAnnotation; + if (!!type) { + // isReturnVoid = isReturnVoidInType(type) ?? isReturnVoid; + // arkts.NodeCache.getInstance().collect(declarator); + } + const initializer = declarator.initializer; + if (!initializer || !arkts.isConditionalExpression(initializer)) { + return; + } + const alternate = initializer.alternate; + if (!alternate) { + return; + } + let arrowFunc: arkts.ArrowFunctionExpression | undefined; + if (arkts.isTSAsExpression(alternate) && !!alternate.expr && arkts.isArrowFunctionExpression(alternate.expr)) { + arrowFunc = alternate.expr; + // if (!!alternate.typeAnnotation) { + // isReturnVoid = isReturnVoidInType(alternate.typeAnnotation) ?? isReturnVoid; + // arkts.NodeCache.getInstance().collect(alternate.typeAnnotation); + // } + } else if (arkts.isArrowFunctionExpression(alternate)) { + arrowFunc = alternate; + } + if (!!arrowFunc) { + // console.log("[collectGensymDeclarator] arrowFunc: ", arrowFunc.dumpSrc()); + // console.log("[collectGensymDeclarator] info: ", info); + + // arkts.NodeCache.getInstance().collect(arrowFunc, { isReturnVoid }); + const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(arrowFunc.scriptFunction); + const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams(arrowFunc.scriptFunction); + if (!!arrowFunc.scriptFunction.body && arkts.isBlockStatement(arrowFunc.scriptFunction.body)) { + const collector = new MemoFunctionCollector(); + arrowFunc.scriptFunction.body.statements.forEach((st, index) => { + if (index < gensymCount) { + return; + } + collector + .registerReturnInfo(returnMemoableInfo) + .registerParamInfoMap(paramMemoableInfoMap) + .visitor(st); + collector.reset(); + }); + } + + } +} + +export function collectMemoableInfoMapInFunctionParams( + node: arkts.ScriptFunction +): [Map, number] { + const hasReceiver = node.hasReceiver; + const paramMap = new Map(); + let gensymCount: number = 0; + node.params.slice(hasReceiver ? 1 : 0).forEach((p) => { + const param = p as arkts.ETSParameterExpression; + let memoableInfo: MemoableInfo; + if (arkts.NodeCache.getInstance().has(param)) { + const metadata = arkts.NodeCache.getInstance().get(param)!.metadata ?? {}; + const { hasMemoSkip } = metadata; + memoableInfo = { hasMemo: true, hasMemoSkip, hasProperType: true }; + } else { + memoableInfo = collectMemoableInfoInParameter(param); + } + // console.log('[PARAM COLLECTOR] [PARAM] node: ', param.dumpSrc()); + // console.log("[PARAM COLLECTOR] [PARAM] param.identifier: ", param.identifier.peer); + // console.log('[PARAM COLLECTOR] [PARAM] memoableInfo: ', memoableInfo); + if (param.identifier.name.startsWith('gensym%%') && !!node.body && arkts.isBlockStatement(node.body)) { + const declaration = node.body.statements.at(gensymCount); + if (!!declaration && arkts.isVariableDeclaration(declaration) && declaration.declarators.length > 0) { + // console.log('[PARAM COLLECTOR] [GENSYM] node: ', declaration.declarators[0].dumpSrc()); + // console.log('[PARAM COLLECTOR] [GENSYM] SETTER: ', declaration.declarators[0].name.peer); + const declarator = declaration.declarators[0]; + collectGensymDeclarator(declarator, memoableInfo); + // if (memoableInfo.hasMemo || memoableInfo.hasBuilder) { + // arkts.NodeCache.getInstance().collect(declarator); + // } + // const initializer = declarator.initializer; + // const type = declarator.name.typeAnnotation; + // const hasMemo = ; + // if ((memoableInfo.hasMemo || memoableInfo.hasBuilder)) { + // arkts.NodeCache.getInstance().collect(initializer.alternate); + // } + // if (hasMemo && !!type) { + // arkts.NodeCache.getInstance().collect(type); + // } + if (!memoableInfo.hasMemoSkip) { + paramMap.set(declarator.name.peer, memoableInfo); + } + gensymCount++; + } + } + if ((memoableInfo.hasMemo || memoableInfo.hasBuilder) && memoableInfo.hasProperType) { + arkts.NodeCache.getInstance().collect(param, { hasMemoSkip: memoableInfo.hasMemoSkip }); + } + if (!memoableInfo.hasMemoSkip) { + paramMap.set(param.identifier.peer, memoableInfo); + } + }); + return [paramMap, gensymCount]; +} + +export function findCanAddMemoFromTypeAnnotation( + typeAnnotation: arkts.AstNode | undefined +): typeAnnotation is arkts.ETSFunctionType { + if (!typeAnnotation) { + return false; + } + const memoableInfo = collectMemoableInfoInType(typeAnnotation); + if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { + arkts.NodeCache.getInstance().collect(typeAnnotation); + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo; +} + +// export function findCanAddMemoFromVariableDeclarator(declarator: arkts.AstNode): declarator is arkts.VariableDeclarator { +// const memoableInfo = collectMemoableInfoInVariableDeclarator(declarator); +// if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { +// console.log('[findCanAddMemoFromVariableDeclarator] MEMO declarator: ', declarator.dumpSrc()); +// arkts.NodeCache.getInstance().collect(declarator); +// } +// return false; // do nothing +// } + +export function findCanAddMemoFromProperty(property: arkts.AstNode): property is arkts.Property { + const memoableInfo = collectMemoableInfoInProperty(property); + if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { + // console.log('[findCanAddMemoFromClassProperty] MEMO property: ', property.dumpSrc()); + // console.log('[findCanAddMemoFromClassProperty] MEMO hasReceiver: ', memoableInfo.hasReceiver); + arkts.NodeCache.getInstance().collect(property); + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +} + +export function findCanAddMemoFromClassProperty(property: arkts.AstNode): property is arkts.ClassProperty { + const memoableInfo = collectMemoableInfoInClassProperty(property); + if (arkts.isClassProperty(property)) { + // console.log('[findCanAddMemoFromClassProperty] class property: ', property.dumpSrc()); + } + if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { + // console.log('[findCanAddMemoFromClassProperty] MEMO class property: ', property.dumpSrc()); + arkts.NodeCache.getInstance().collect(property); + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo; +} + +export function findCanAddMemoFromParameter(param: arkts.AstNode | undefined): param is arkts.ETSParameterExpression { + if (!param) { + return false; + } + const memoableInfo = collectMemoableInfoInParameter(param); + + // if (arkts.isEtsParameterExpression(param)) { + // console.log('[findCanAddMemoFromParameter] param: ', param.dumpSrc()); + // console.log('[findCanAddMemoFromParameter] memoableInfo: ', memoableInfo); + // } + + if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { + // console.log('[findCanAddMemoFromParameter] param: ', param.dumpSrc()); + arkts.NodeCache.getInstance().collect(param, { hasMemoSkip: memoableInfo.hasMemoSkip }); + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo; +} + +export function findCanAddMemoFromArrowFunction(node: arkts.AstNode): node is arkts.ArrowFunctionExpression { + if (!arkts.isArrowFunctionExpression(node)) { + return false; + } + const memoableInfo = collectMemoableInfoInArrowFunction(node); + const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(node.scriptFunction); + const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams(node.scriptFunction); + const isMemo = checkIsMemoFromMemoableInfo(memoableInfo); + const isMemoReturnType = checkIsMemoFromMemoableInfo(returnMemoableInfo); + if (isMemoReturnType) { + arkts.NodeCache.getInstance().collect(node.scriptFunction.returnTypeAnnotation!); + } + if (isMemo && !arkts.NodeCache.getInstance().has(node)) { + arkts.NodeCache.getInstance().collect(node); + if (!!node.scriptFunction.body && arkts.isBlockStatement(node.scriptFunction.body)) { + const collector = new MemoFunctionCollector(); + node.scriptFunction.body.statements.forEach((st, index) => { + if (index < gensymCount) { + return; + } + collector + .registerReturnInfo(returnMemoableInfo) + .registerParamInfoMap(paramMemoableInfoMap) + .visitor(st); + collector.reset(); + }); + // const collector = new MemoFunctionCollector(); + // collector + // .registerReturnInfo(returnMemoableInfo) + // .registerParamInfoMap(paramMemoableInfoMap) + // .visitor(node.scriptFunction); + // collector.reset(); + } + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo; +} + +export function findCanAddMemoFromTypeAlias(node: arkts.AstNode): node is arkts.TSTypeAliasDeclaration { + const memoableInfo = collectMemoableInfoInTypeAlias(node); + if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { + arkts.NodeCache.getInstance().collect(node); + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo; +} + +export function findCanAddMemoFromMethod(node: arkts.AstNode): node is arkts.MethodDefinition { + if (!arkts.isMethodDefinition(node)) { + return false; + } + const callName = node.name.name; + const hasReceiver = node.scriptFunction.hasReceiver; + const isSetter = node.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; + const isGetter = node.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; + let info: MemoableInfo = {} + if (isSetter && node.scriptFunction.params.length > 0) { + if (hasReceiver && node.scriptFunction.params.length === 2) { + info = collectMemoableInfoInParameter(node.scriptFunction.params.at(1)!); + } else { + info = collectMemoableInfoInParameter(node.scriptFunction.params.at(0)!); + } + } else if (isGetter) { + info = collectMemoableInfoInFunctionReturnType(node.scriptFunction); + } + const memoableInfo = collectMemoableInfoInScriptFunction(node.scriptFunction, info); + const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(node.scriptFunction); + const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams(node.scriptFunction); + const isMemo = checkIsMemoFromMemoableInfo(memoableInfo); + const isMemoReturnType = checkIsMemoFromMemoableInfo(returnMemoableInfo); + if (isMemoReturnType) { + arkts.NodeCache.getInstance().collect(node.scriptFunction.returnTypeAnnotation!); + } + if (isMemo && !arkts.NodeCache.getInstance().has(node)) { + // console.log("[findCanAddMemoFromMethod] node: ", node.dumpSrc()); + arkts.NodeCache.getInstance().collect(node, { callName, hasReceiver, isSetter, isGetter }); + if (!!node.scriptFunction.body && arkts.isBlockStatement(node.scriptFunction.body)) { + const collector = new MemoFunctionCollector(); + node.scriptFunction.body.statements.forEach((st, index) => { + if (index < gensymCount) { + return; + } + collector + .registerReturnInfo(returnMemoableInfo) + .registerParamInfoMap(paramMemoableInfoMap) + .visitor(st); + collector.reset(); + }); + // const collector = new MemoFunctionCollector(); + // collector + // .registerReturnInfo(returnMemoableInfo) + // .registerParamInfoMap(paramMemoableInfoMap) + // .visitor(node.scriptFunction); + // collector.reset(); + } + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo; +} + +export function isMemoAnnotation(node: arkts.AnnotationUsage, memoName: MemoNames): boolean { + if (!(node.expr !== undefined && arkts.isIdentifier(node.expr) && node.expr.name === memoName)) { + return false; + } + return true; +} + +export function addMemoAnnotation(node: T, memoName: MemoNames = MemoNames.MEMO): T { + collectMemoAnnotationSource(memoName); + if (arkts.isETSUnionType(node)) { + return arkts.factory.updateUnionType( + node, + node.types.map((type) => { + if (arkts.isETSFunctionType(type)) { + return addMemoAnnotation(type, memoName); + } + return type; + }) + ) as T; + } + const newAnnotations: arkts.AnnotationUsage[] = [ + ...node.annotations.filter((it) => !isMemoAnnotation(it, memoName)), + annotation(memoName), + ]; + collectMemoAnnotationImport(memoName); + if (arkts.isEtsParameterExpression(node)) { + node.annotations = newAnnotations; + arkts.NodeCache.getInstance().collect(node); + return node; + } + const newNode = node.setAnnotations(newAnnotations) as T; + arkts.NodeCache.getInstance().collect(newNode); + return newNode; +} + +export function isMemoableAnotation(annotation: arkts.AnnotationUsage): boolean { + return isDecoratorAnnotation(annotation, DecoratorNames.BUILDER) || isMemoAnnotation(annotation, MemoNames.MEMO); +} + +export function checkIsMemoFromMemoableInfo(info: MemoableInfo, ignoreType: boolean = false): boolean { + return (!!info.hasMemo || !!info.hasBuilder) && (ignoreType || !!info.hasProperType); +} + +export function getDeclResolveAlias(node: arkts.AstNode): arkts.AstNode | undefined { + const decl = arkts.getDecl(node); + if (!!decl && !!decl.parent && arkts.isIdentifier(decl) && arkts.isVariableDeclarator(decl.parent)) { + // console.log("[getDeclResolveAlias] decl: ", decl.dumpSrc()); + // console.log("[getDeclResolveAlias] decl.parent: ", decl.parent.dumpSrc()); + if (!!decl.parent.initializer && arkts.isIdentifier(decl.parent.initializer)) { + return getDeclResolveAlias(decl.parent.initializer); + } + if (!!decl.parent.initializer && arkts.isMemberExpression(decl.parent.initializer)) { + return getDeclResolveAlias(decl.parent.initializer.property); + } + } + return decl; +} + +export function parametersBlockHasReceiver(params: readonly arkts.Expression[]): boolean { + return params.length > 0 && arkts.isEtsParameterExpression(params[0]) && isThisParam(params[0]); +} + +export function parametrizedNodeHasReceiver(node: arkts.ScriptFunction | arkts.ETSFunctionType | undefined): boolean { + if (node === undefined) { + return false; + } + return parametersBlockHasReceiver(node.params); +} + +function isThisParam(node: arkts.Expression | undefined): boolean { + if (node === undefined || !arkts.isEtsParameterExpression(node)) { + return false; + } + return node.identifier?.isReceiver ?? false; +} \ No newline at end of file diff --git a/arkui-plugins/ui-plugins/property-translators/builderParam.ts b/arkui-plugins/ui-plugins/property-translators/builderParam.ts index 44266f3d1..5d7a97305 100644 --- a/arkui-plugins/ui-plugins/property-translators/builderParam.ts +++ b/arkui-plugins/ui-plugins/property-translators/builderParam.ts @@ -27,8 +27,8 @@ import { } from './utils'; import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; import { GetterSetter, InitializerConstructor } from './types'; -import { addMemoAnnotation, findCanAddMemoFromParamExpression, findCanAddMemoFromTypeAnnotation } from '../utils'; import { factory } from './factory'; +import { addMemoAnnotation, findCanAddMemoFromParameter, findCanAddMemoFromTypeAnnotation } from '../memo-translators/utils'; export class BuilderParamTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { translateMember(): arkts.AstNode[] { @@ -52,6 +52,7 @@ export class BuilderParamTranslator extends PropertyTranslator implements Initia arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, true ); + arkts.NodeCache.getInstance().collect(field); const thisGetValue: arkts.Expression = generateThisBacking(newName, false, true); const thisSetValue: arkts.Expression = generateThisBacking(newName, false, false); const getter: arkts.MethodDefinition = this.translateGetter( @@ -59,11 +60,13 @@ export class BuilderParamTranslator extends PropertyTranslator implements Initia this.property.typeAnnotation, thisGetValue ); + arkts.NodeCache.getInstance().collect(getter); const setter: arkts.MethodDefinition = this.translateSetter( originalName, this.property.typeAnnotation, thisSetValue ); + arkts.NodeCache.getInstance().collect(setter); return [field, getter, setter]; } @@ -145,12 +148,14 @@ export class BuilderParamInterfaceTranslator e }); method.setOverloads(newOverLoads); removeDecorator(method, DecoratorNames.BUILDER_PARAM); + arkts.NodeCache.getInstance().collect(method, { isGetter: true }); } else if (method.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET) { const param: arkts.Expression | undefined = method.scriptFunction.params.at(0); - if (findCanAddMemoFromParamExpression(param)) { + if (findCanAddMemoFromParameter(param)) { addMemoAnnotation(param); } removeDecorator(method, DecoratorNames.BUILDER_PARAM); + arkts.NodeCache.getInstance().collect(method, { isSetter: true }); } return method; } diff --git a/arkui-plugins/ui-plugins/property-translators/factory.ts b/arkui-plugins/ui-plugins/property-translators/factory.ts index f77047363..a13bee518 100644 --- a/arkui-plugins/ui-plugins/property-translators/factory.ts +++ b/arkui-plugins/ui-plugins/property-translators/factory.ts @@ -18,7 +18,8 @@ import { GenSymGenerator } from '../../common/gensym-generator'; import { DecoratorNames, DECORATOR_TYPE_MAP, StateManagementTypes } from '../../common/predefines'; import { factory as UIFactory } from '../ui-factory'; import { collectStateManagementTypeImport, getValueInAnnotation, hasDecorator, removeDecorator } from './utils'; -import { addMemoAnnotation, findCanAddMemoFromTypeAnnotation, CustomComponentNames } from '../utils'; +import { CustomComponentNames } from '../utils'; +import { addMemoAnnotation, findCanAddMemoFromTypeAnnotation } from '../memo-translators/utils'; export class factory { /** diff --git a/arkui-plugins/ui-plugins/property-translators/utils.ts b/arkui-plugins/ui-plugins/property-translators/utils.ts index 2a750395a..a60a0186d 100644 --- a/arkui-plugins/ui-plugins/property-translators/utils.ts +++ b/arkui-plugins/ui-plugins/property-translators/utils.ts @@ -23,9 +23,9 @@ import { DecoratorNames, DECORATOR_TYPE_MAP, StateManagementTypes, - GetSetTypes + GetSetTypes, } from '../../common/predefines'; -import { addMemoAnnotation, findCanAddMemoFromParamExpression, findCanAddMemoFromTypeAnnotation } from '../utils'; +import { addMemoAnnotation, findCanAddMemoFromParameter, findCanAddMemoFromTypeAnnotation } from '../memo-translators/utils'; export interface DecoratorInfo { annotation: arkts.AnnotationUsage; @@ -94,7 +94,12 @@ export function hasDecoratorName( } export function hasDecorator( - property: arkts.ClassProperty | arkts.ClassDefinition | arkts.MethodDefinition, + property: + | arkts.ClassProperty + | arkts.ClassDefinition + | arkts.MethodDefinition + | arkts.ETSParameterExpression + | arkts.ETSFunctionType, decoratorName: DecoratorNames ): boolean { if (arkts.isMethodDefinition(property)) { @@ -211,7 +216,7 @@ export function createSetter( arkts.factory.createIdentifier('value', type?.clone()), undefined ); - if (needMemo && findCanAddMemoFromParamExpression(param)) { + if (needMemo && findCanAddMemoFromParameter(param)) { addMemoAnnotation(param); } const scriptFunction = arkts.factory.createScriptFunction( diff --git a/arkui-plugins/ui-plugins/struct-translators/factory.ts b/arkui-plugins/ui-plugins/struct-translators/factory.ts index fc0a1ca57..5b33dfe9b 100644 --- a/arkui-plugins/ui-plugins/struct-translators/factory.ts +++ b/arkui-plugins/ui-plugins/struct-translators/factory.ts @@ -16,18 +16,17 @@ import * as arkts from '@koalaui/libarkts'; import { BuilderLambdaNames, - addMemoAnnotation, CustomComponentNames, getCustomComponentOptionsName, getGettersFromClassDecl, getTypeNameFromTypeParameter, getTypeParamsFromClassDecl, isCustomComponentInterface, - MemoNames, + isKnownMethodDefinition } from '../utils'; import { factory as uiFactory } from '../ui-factory'; import { factory as propertyFactory } from '../property-translators/factory'; -import { collect, filterDefined, annotation } from '../../common/arkts-utils'; +import { collect, filterDefined } from '../../common/arkts-utils'; import { classifyObservedTrack, classifyProperty, @@ -36,10 +35,9 @@ import { InterfacePropertyTranslator, PropertyTranslator, } from '../property-translators'; -import { CustomComponentScopeInfo, isEtsGlobalClass, isKnownMethodDefinition } from './utils'; +import { CustomComponentScopeInfo, isEtsGlobalClass } from './utils'; import { collectStateManagementTypeImport, hasDecorator, PropertyCache } from '../property-translators/utils'; import { ProjectConfig } from '../../common/plugin-context'; -import { DeclarationCollector } from '../../common/declaration-collector'; import { ImportCollector } from '../../common/import-collector'; import { ARKUI_COMPONENT_COMMON_SOURCE_NAME, @@ -48,9 +46,11 @@ import { StateManagementTypes, } from '../../common/predefines'; import { ObservedTrackTranslator } from '../property-translators/observedTrack'; +import { addMemoAnnotation } from '../memo-translators/utils'; +import { MemoFunctionCollector } from '../memo-translators/function-collector'; export class factory { - /* + /** * update class `constructor` to private. */ static setStructConstructorToPrivate(member: arkts.MethodDefinition): arkts.MethodDefinition { @@ -60,48 +60,7 @@ export class factory { return member; } - /* - * create _build method. - */ - static transformBuildMethodWithOriginBuild( - method: arkts.MethodDefinition, - typeName: string, - optionsName: string, - isDecl?: boolean - ): arkts.MethodDefinition { - const updateKey: arkts.Identifier = arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_BUILD); - - const scriptFunction: arkts.ScriptFunction = method.scriptFunction; - const updateScriptFunction = arkts.factory.createScriptFunction( - scriptFunction.body, - arkts.FunctionSignature.createFunctionSignature( - scriptFunction.typeParams, - [ - uiFactory.createStyleParameter(typeName), - uiFactory.createContentParameter(), - uiFactory.createInitializersOptionsParameter(optionsName), - ], - arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), - false - ), - scriptFunction.flags, - scriptFunction.modifiers - ); - addMemoAnnotation(updateScriptFunction); - - const modifiers: arkts.Es2pandaModifierFlags = isDecl - ? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_ABSTRACT - : arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC; - return arkts.factory.createMethodDefinition( - arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, - updateKey, - updateScriptFunction, - modifiers, - false - ); - } - - /* + /** * generate _r() or _rawfile(). */ static generateTransformedResource( @@ -120,7 +79,7 @@ export class factory { ); } - /* + /** * create __initializeStruct method. */ static createInitializeStruct(optionsTypeName: string, scope: CustomComponentScopeInfo): arkts.MethodDefinition { @@ -157,7 +116,7 @@ export class factory { ); } - /* + /** * create __updateStruct method. */ static createUpdateStruct(optionsTypeName: string, scope: CustomComponentScopeInfo): arkts.MethodDefinition { @@ -195,7 +154,7 @@ export class factory { ); } - /* + /** * create __toRecord method when the component is decorated with @Reusable. */ static createToRecord(optionsTypeName: string, scope: CustomComponentScopeInfo): arkts.MethodDefinition { @@ -230,7 +189,7 @@ export class factory { ); } - /* + /** * generate `const paramsCasted = (params as )`. */ static generateParamsCasted(optionsTypeName: string): arkts.VariableDeclaration { @@ -251,7 +210,7 @@ export class factory { ); } - /* + /** * generate Record type. */ static generateTypeRecord(): arkts.ETSTypeReference { @@ -266,7 +225,7 @@ export class factory { ); } - /* + /** * create type reference with type name, e.g. number. */ static generateTypeReferenceWithTypeName(typeName: string): arkts.ETSTypeReference { @@ -275,7 +234,7 @@ export class factory { ); } - /* + /** * create type reference with type name, e.g. number. */ static updateCustomComponentClass( @@ -296,7 +255,7 @@ export class factory { ); } - /* + /** * add headers for animation in UICommonMethod */ static modifyExternalComponentCommon(node: arkts.TSInterfaceDeclaration): arkts.TSInterfaceDeclaration { @@ -318,7 +277,7 @@ export class factory { ); } - /* + /** * generate animationStart(...) and animationStop(...) */ static createAnimationMethod(key: string): arkts.MethodDefinition { @@ -376,24 +335,20 @@ export class factory { /** * transform non-property members in custom-component class. */ - static transformNonPropertyMembersInClass( - member: arkts.AstNode, - classTypeName: string | undefined, - classOptionsName: string | undefined, - className: string, - isDecl?: boolean - ): arkts.AstNode { + static transformNonPropertyMembersInClass(member: arkts.AstNode, isDecl?: boolean): arkts.AstNode { if (arkts.isMethodDefinition(member)) { propertyFactory.addMemoToBuilderClassMethod(member); if (isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI) && !isDecl) { return this.setStructConstructorToPrivate(member); - } else if (isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_BUILD_ORI)) { - return this.transformBuildMethodWithOriginBuild( - member, - classTypeName ?? className, - classOptionsName ?? getCustomComponentOptionsName(className), - isDecl - ); + } + if (isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_BUILD_ORI)) { + addMemoAnnotation(member.scriptFunction); + if (!!member.scriptFunction.body && arkts.isBlockStatement(member.scriptFunction.body)) { + const collector = new MemoFunctionCollector(); + collector.shouldCollectReturn = false; + collector.visitor(member.scriptFunction); + collector.reset(); + } } return member; } @@ -407,11 +362,9 @@ export class factory { if (!node.definition) { return node; } - let classTypeName: string | undefined; let classOptionsName: string | undefined; if (scope.isDecl) { - const [classType, classOptions] = getTypeParamsFromClassDecl(node); - classTypeName = getTypeNameFromTypeParameter(classType); + const [_, classOptions] = getTypeParamsFromClassDecl(node); classOptionsName = getTypeNameFromTypeParameter(classOptions); } const definition: arkts.ClassDefinition = node.definition; @@ -430,15 +383,7 @@ export class factory { ); const updateMembers: arkts.AstNode[] = definition.body .filter((member) => !arkts.isClassProperty(member)) - .map((member: arkts.AstNode) => - factory.transformNonPropertyMembersInClass( - member, - classTypeName, - classOptionsName, - className, - scope.isDecl - ) - ); + .map((member: arkts.AstNode) => factory.transformNonPropertyMembersInClass(member, scope.isDecl)); const updateClassDef: arkts.ClassDefinition = this.updateCustomComponentClass(definition, [ ...translatedMembers, @@ -481,7 +426,25 @@ export class factory { if (isCustomComponentInterface(node)) { return factory.tranformCustomComponentInterfaceMembers(node); } - return node; + return factory.tranformInterfaceBuildMember(node); + } + + static tranformInterfaceBuildMember(node: arkts.TSInterfaceDeclaration): arkts.TSInterfaceDeclaration { + const newBody: arkts.AstNode[] = node.body!.body.map((it) => { + if (arkts.isMethodDefinition(it)) { + propertyFactory.addMemoToBuilderClassMethod(it); + } + return it; + }); + return arkts.factory.updateInterfaceDeclaration( + node, + node.extends, + node.id, + node.typeParams, + arkts.factory.updateInterfaceBody(node.body!, newBody), + node.isStatic, + node.isFromExternal + ); } /** @@ -529,13 +492,6 @@ export class factory { return arkts.factory.updateClassDeclaration(node, newClassDef); } - static transformTSTypeAlias(node: arkts.TSTypeAliasDeclaration): arkts.TSTypeAliasDeclaration { - if (arkts.isETSFunctionType(node.typeAnnotation) && hasDecorator(node.typeAnnotation, DecoratorNames.BUILDER)) { - node.typeAnnotation.setAnnotations([annotation(MemoNames.MEMO)]); - } - return node; - } - static updateObservedTrackClassDef(node: arkts.ClassDefinition): arkts.ClassDefinition { const isObserved: boolean = hasDecorator(node, DecoratorNames.OBSERVED); const classHasTrack: boolean = node.body.some( diff --git a/arkui-plugins/ui-plugins/struct-translators/struct-transformer.ts b/arkui-plugins/ui-plugins/struct-translators/struct-transformer.ts index 05b470a09..86148273a 100644 --- a/arkui-plugins/ui-plugins/struct-translators/struct-transformer.ts +++ b/arkui-plugins/ui-plugins/struct-translators/struct-transformer.ts @@ -17,18 +17,19 @@ import * as arkts from '@koalaui/libarkts'; import { AbstractVisitor } from '../../common/abstract-visitor'; import { ProjectConfig } from '../../common/plugin-context'; import { - addMemoAnnotation, collectCustomComponentScopeInfo, CustomComponentNames, isCustomComponentClass, } from '../utils'; -import { CustomComponentScopeInfo, findCanAddMemoFromArrowFunction, isResourceNode, ScopeInfoCollection } from './utils'; +import { CustomComponentScopeInfo, isResourceNode, ScopeInfoCollection } from './utils'; import { factory } from './factory'; import { isEntryWrapperClass } from '../entry-translators/utils'; import { factory as entryFactory } from '../entry-translators/factory'; import { ImportCollector } from '../../common/import-collector'; import { DeclarationCollector } from '../../common/declaration-collector'; import { PropertyCache } from '../property-translators/utils'; +import { addMemoAnnotation, findCanAddMemoFromArrowFunction, findCanAddMemoFromMethod, findCanAddMemoFromParameter, findCanAddMemoFromTypeAlias } from '../memo-translators/utils'; +import { generateArkUICompatible, isArkUICompatible } from '../interop/interop'; export class StructTransformer extends AbstractVisitor { private scope: ScopeInfoCollection; @@ -76,6 +77,10 @@ export class StructTransformer extends AbstractVisitor { visitor(beforeChildren: arkts.AstNode): arkts.AstNode { this.enter(beforeChildren); const node = this.visitEachChild(beforeChildren); + // if (arkts.isCallExpression(node)) { + // console.log(`[UPCALL 2] ptr: ${node.peer} lambda: `, node.dumpSrc()); + // // arkts.NodeCache.getInstance().collect(node); + // } if ( arkts.isClassDeclaration(node) && this.scope.customComponents.length > 0 && @@ -92,10 +97,18 @@ export class StructTransformer extends AbstractVisitor { return factory.transformNormalClass(node); } else if (arkts.isCallExpression(node) && isResourceNode(node)) { return factory.transformResource(node, this.projectConfig); + } else if (isArkUICompatible(node)) { + return generateArkUICompatible(node as arkts.CallExpression); } else if (arkts.isTSInterfaceDeclaration(node)) { return factory.tranformInterfaceMembers(node, this.externalSourceName); - } else if (findCanAddMemoFromArrowFunction(node)) { + } else if (findCanAddMemoFromTypeAlias(node)) { + return addMemoAnnotation(node); + } else if (findCanAddMemoFromParameter(node)) { return addMemoAnnotation(node); + } else if (findCanAddMemoFromMethod(node)) { + return addMemoAnnotation(node.scriptFunction); + } else if (findCanAddMemoFromArrowFunction(node)) { + return addMemoAnnotation(node.scriptFunction); } else if (arkts.isEtsScript(node) && ImportCollector.getInstance().importInfos.length > 0) { ImportCollector.getInstance().insertCurrentImports(this.program); } diff --git a/arkui-plugins/ui-plugins/struct-translators/utils.ts b/arkui-plugins/ui-plugins/struct-translators/utils.ts index 7c68e9429..579b6f6f6 100644 --- a/arkui-plugins/ui-plugins/struct-translators/utils.ts +++ b/arkui-plugins/ui-plugins/struct-translators/utils.ts @@ -14,11 +14,10 @@ */ import * as arkts from '@koalaui/libarkts'; -import { CustomComponentInfo, isMemoAnnotation, MemoNames } from '../utils'; -import { isDecoratorAnnotation } from '../property-translators/utils'; +import { CustomComponentInfo } from '../utils'; import { matchPrefix } from '../../common/arkts-utils'; -import { ARKUI_IMPORT_PREFIX_NAMES, DecoratorNames, Dollars } from '../../common/predefines'; import { DeclarationCollector } from '../../common/declaration-collector'; +import { ARKUI_IMPORT_PREFIX_NAMES, Dollars } from '../../common/predefines'; export type ScopeInfoCollection = { customComponents: CustomComponentScopeInfo[]; @@ -30,22 +29,6 @@ export type CustomComponentScopeInfo = CustomComponentInfo & { hasReusableRebind?: boolean; }; -/** - * Determine whether it is method with specified name. - * - * @param method method definition node - * @param name specified method name - */ -export function isKnownMethodDefinition(method: arkts.MethodDefinition, name: string): boolean { - if (!method || !arkts.isMethodDefinition(method)) { - return false; - } - - // For now, we only considered matched method name. - const isNameMatched: boolean = method.name?.name === name; - return isNameMatched; -} - /** * Determine whether it is ETSGLOBAL class. * @@ -85,35 +68,3 @@ export function isResourceNode(node: arkts.CallExpression, ignoreDecl: boolean = } return true; } - -export function isMemoCall(node: arkts.AstNode): node is arkts.CallExpression { - if (!arkts.isCallExpression(node)) { - return false; - } - const expr: arkts.AstNode = node.expression; - const decl: arkts.AstNode | undefined = arkts.getDecl(expr); - - if (!decl) { - return false; - } - - if (arkts.isMethodDefinition(decl)) { - return decl.scriptFunction.annotations.some( - (anno) => isDecoratorAnnotation(anno, DecoratorNames.BUILDER) || isMemoAnnotation(anno, MemoNames.MEMO) - ); - } - return false; -} - -export function findCanAddMemoFromArrowFunction(node: arkts.AstNode): node is arkts.ArrowFunctionExpression { - if (!arkts.isArrowFunctionExpression(node)) { - return false; - } - const hasMemo: boolean = node.annotations.some((anno) => isMemoAnnotation(anno, MemoNames.MEMO)); - if (!hasMemo && !!node.scriptFunction.body && arkts.isBlockStatement(node.scriptFunction.body)) { - return node.scriptFunction.body.statements.some( - (st) => arkts.isExpressionStatement(st) && isMemoCall(st.expression) - ); - } - return false; -} diff --git a/arkui-plugins/ui-plugins/ui-factory.ts b/arkui-plugins/ui-plugins/ui-factory.ts index c5f735bf9..6714616be 100644 --- a/arkui-plugins/ui-plugins/ui-factory.ts +++ b/arkui-plugins/ui-plugins/ui-factory.ts @@ -15,16 +15,16 @@ import * as arkts from '@koalaui/libarkts'; import { - addMemoAnnotation, BuilderLambdaNames, + CustomComponentAnontations, CustomComponentNames, - findCanAddMemoFromParamExpression, hasNullOrUndefinedType, hasPropertyInAnnotation, } from './utils'; import { PartialExcept, PartialNested, PartialNestedExcept } from '../common/safe-types'; import { DecoratorNames } from '../common/predefines'; import { needDefiniteOrOptionalModifier } from './property-translators/utils'; +import { addMemoAnnotation } from './memo-translators/utils'; export interface ScriptFunctionConfiguration { key: arkts.Identifier | undefined; @@ -98,18 +98,6 @@ export class factory { ); } - /** - * create `@memo() style: ((instance: ) => void) | undefined` as parameter - */ - static createStyleParameter(typeName: string): arkts.ETSParameterExpression { - const styleParam: arkts.Identifier = factory.createStyleIdentifier(typeName); - const param: arkts.ETSParameterExpression = arkts.factory.createParameterDeclaration(styleParam, undefined); - if (findCanAddMemoFromParamExpression(param)) { - addMemoAnnotation(param); - } - return param; - } - /** * create `initializers: | undefined` as identifier */ @@ -149,9 +137,7 @@ export class factory { static createContentParameter(): arkts.ETSParameterExpression { const contentParam: arkts.Identifier = factory.createContentIdentifier(); const param: arkts.ETSParameterExpression = arkts.factory.createParameterDeclaration(contentParam, undefined); - if (findCanAddMemoFromParamExpression(param)) { - addMemoAnnotation(param); - } + addMemoAnnotation(param); return param; } @@ -316,7 +302,7 @@ export class factory { return newAnnotationDecl; } - /* + /** * add alias: to @Provide annotation when no alias in @Provide({...}). */ static processNoAliasProvideVariable(property: arkts.ClassProperty): void { @@ -344,7 +330,7 @@ export class factory { property.setAnnotations(newAnnos); } - /* + /** * create class property : `alias: `. */ static createAliasClassProperty(value: arkts.Identifier): arkts.ClassProperty { @@ -357,7 +343,7 @@ export class factory { ); } - /* + /** * add optional or definite modifier for class property needs initializing without assignment. */ static PreprocessClassPropertyModifier(st: arkts.AstNode): arkts.AstNode { @@ -370,4 +356,35 @@ export class factory { } return st; } + + /** + * create class implements : `implements `. + */ + static createClassImplements( + interfaceName: string, + typeParameters?: arkts.TSTypeParameterInstantiation + ): arkts.TSClassImplements { + return arkts.factory.createTSClassImplements( + arkts.factory.createTypeReference( + arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier(interfaceName)) + ), + typeParameters + ); + } + + /** + * Generate class implements for struct with struct annotations. + * + * @param method method definition node + */ + static generateImplementsForStruct(annotations: CustomComponentAnontations): arkts.TSClassImplements[] { + const implementsInfo: arkts.TSClassImplements[] = []; + if (annotations.entry) { + implementsInfo.push(factory.createClassImplements(CustomComponentNames.PAGE_LIFE_CYCLE)); + } + if (annotations.customLayout) { + implementsInfo.push(factory.createClassImplements(CustomComponentNames.LAYOUT_CALLBACK)); + } + return implementsInfo; + } } diff --git a/arkui-plugins/ui-plugins/utils.ts b/arkui-plugins/ui-plugins/utils.ts index d23949753..a02748e3e 100644 --- a/arkui-plugins/ui-plugins/utils.ts +++ b/arkui-plugins/ui-plugins/utils.ts @@ -14,22 +14,23 @@ */ import * as arkts from '@koalaui/libarkts'; -import { annotation, matchPrefix } from '../common/arkts-utils'; -import { ARKUI_IMPORT_PREFIX_NAMES, MEMO_IMPORT_SOURCE_NAME, StructDecoratorNames } from '../common/predefines'; +import { matchPrefix } from '../common/arkts-utils'; +import { ARKUI_IMPORT_PREFIX_NAMES, StructDecoratorNames } from '../common/predefines'; import { DeclarationCollector } from '../common/declaration-collector'; -import { ImportCollector } from '../common/import-collector'; export enum CustomComponentNames { COMPONENT_BUILD_ORI = 'build', COMPONENT_CONSTRUCTOR_ORI = 'constructor', COMPONENT_CLASS_NAME = 'CustomComponent', + COMPONENT_V2_CLASS_NAME = 'CustomComponentV2', COMPONENT_INTERFACE_PREFIX = '__Options_', COMPONENT_INITIALIZE_STRUCT = '__initializeStruct', COMPONENT_UPDATE_STRUCT = '__updateStruct', - COMPONENT_BUILD = '_build', COMPONENT_INITIALIZERS_NAME = 'initializers', BUILDCOMPATIBLENODE = '_buildCompatibleNode', OPTIONS = 'options', + PAGE_LIFE_CYCLE = 'PageLifeCycle', + LAYOUT_CALLBACK = 'LayoutCallback', } export enum BuilderLambdaNames { @@ -44,10 +45,6 @@ export enum BuilderLambdaNames { ANIMATION_STOP = 'animationStop', } -export enum MemoNames { - MEMO = 'memo', -} - // IMPORT export function findImportSourceByName(importName: string): string { const source = DeclarationCollector.getInstance().findExternalSourceFromName(importName); @@ -140,8 +137,20 @@ export type CustomComponentInfo = { export type CustomComponentAnontations = { component?: arkts.AnnotationUsage; + componentV2?: arkts.AnnotationUsage; entry?: arkts.AnnotationUsage; reusable?: arkts.AnnotationUsage; + reusableV2?: arkts.AnnotationUsage; + customLayout?: arkts.AnnotationUsage; +}; + +type StructAnnoationInfo = { + isComponent: boolean; + isComponentV2: boolean; + isEntry: boolean; + isReusable: boolean; + isReusableV2: boolean; + isCustomLayout: boolean; }; export function isCustomComponentAnnotation( @@ -177,22 +186,28 @@ export function collectCustomComponentScopeInfo( const isDecl: boolean = arkts.hasModifierFlag(node, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE); const isCustomComponentClassDecl = !isStruct && isDecl; const shouldIgnoreDecl = isStruct || isDecl; - if (isCustomComponentClassDecl && definition.ident.name !== CustomComponentNames.COMPONENT_CLASS_NAME) { + if ( + isCustomComponentClassDecl && + definition.ident.name !== CustomComponentNames.COMPONENT_CLASS_NAME && + definition.ident.name !== CustomComponentNames.COMPONENT_V2_CLASS_NAME + ) { return undefined; } let annotations: CustomComponentAnontations = {}; if (!isCustomComponentClassDecl) { let isCustomComponent: boolean = false; for (const anno of definition.annotations) { - const isComponent = isCustomComponentAnnotation(anno, StructDecoratorNames.COMPONENT, shouldIgnoreDecl); - const isEntry = isCustomComponentAnnotation(anno, StructDecoratorNames.ENTRY, shouldIgnoreDecl); - const isReusable = isCustomComponentAnnotation(anno, StructDecoratorNames.RESUABLE, shouldIgnoreDecl); - isCustomComponent ||= isComponent; + const { isComponent, isComponentV2, isEntry, isReusable, isReusableV2, isCustomLayout } = + getAnnotationInfoForStruct(anno, shouldIgnoreDecl); + isCustomComponent ||= isComponent || isComponentV2; annotations = { ...annotations, ...(isComponent && !annotations?.component && { component: anno }), + ...(isComponentV2 && !annotations?.componentV2 && { componentV2: anno }), ...(isEntry && !annotations?.entry && { entry: anno }), ...(isReusable && !annotations?.reusable && { reusable: anno }), + ...(isReusableV2 && !annotations?.reusableV2 && { reusableV2: anno }), + ...(isCustomLayout && !annotations?.customLayout && { customLayout: anno }), }; } if (!isCustomComponent) { @@ -206,6 +221,19 @@ export function collectCustomComponentScopeInfo( }; } +export function getAnnotationInfoForStruct( + anno: arkts.AnnotationUsage, + shouldIgnoreDecl: boolean +): StructAnnoationInfo { + const isComponent = isCustomComponentAnnotation(anno, StructDecoratorNames.COMPONENT, shouldIgnoreDecl); + const isComponentV2 = isCustomComponentAnnotation(anno, StructDecoratorNames.COMPONENT_V2, shouldIgnoreDecl); + const isEntry = isCustomComponentAnnotation(anno, StructDecoratorNames.ENTRY, shouldIgnoreDecl); + const isReusable = isCustomComponentAnnotation(anno, StructDecoratorNames.RESUABLE, shouldIgnoreDecl); + const isReusableV2 = isCustomComponentAnnotation(anno, StructDecoratorNames.RESUABLE_V2, shouldIgnoreDecl); + const isCustomLayout = isCustomComponentAnnotation(anno, StructDecoratorNames.CUSTOM_LAYOUT, shouldIgnoreDecl); + return { isComponent, isComponentV2, isEntry, isReusable, isReusableV2, isCustomLayout }; +} + export function isComponentStruct(node: arkts.StructDeclaration, scopeInfo: CustomComponentInfo): boolean { return scopeInfo.name === node.definition.ident?.name; } @@ -221,7 +249,9 @@ export function isCustomComponentClass(node: arkts.ClassDeclaration, scopeInfo: } const name: string = node.definition.ident.name; if (scopeInfo.isDecl) { - return name === CustomComponentNames.COMPONENT_CLASS_NAME; + return ( + name === CustomComponentNames.COMPONENT_CLASS_NAME || name === CustomComponentNames.COMPONENT_V2_CLASS_NAME + ); } return name === scopeInfo.name; } @@ -238,80 +268,46 @@ export function getCustomComponentOptionsName(className: string): string { return `${CustomComponentNames.COMPONENT_INTERFACE_PREFIX}${className}`; } -// MEMO -export type MemoAstNode = - | arkts.ScriptFunction - | arkts.ETSParameterExpression - | arkts.ClassProperty - | arkts.TSTypeAliasDeclaration - | arkts.ETSFunctionType - | arkts.ArrowFunctionExpression - | arkts.ETSUnionType; - -export function hasMemoAnnotation(node: T): boolean { - return node.annotations.some((it) => isMemoAnnotation(it, MemoNames.MEMO)); -} - -export function collectMemoAnnotationImport(memoName: MemoNames = MemoNames.MEMO): void { - ImportCollector.getInstance().collectImport(memoName); -} - -export function collectMemoAnnotationSource(memoName: MemoNames = MemoNames.MEMO): void { - ImportCollector.getInstance().collectSource(memoName, MEMO_IMPORT_SOURCE_NAME); -} - -export function findCanAddMemoFromTypeAnnotation( - typeAnnotation: arkts.AstNode | undefined -): typeAnnotation is arkts.ETSFunctionType { - if (!typeAnnotation) { - return false; +// PARAMETER & ARGUMENTS +export function forEachArgWithParam( + args: readonly arkts.Expression[], + params: readonly arkts.Expression[], + cb: (arg: arkts.Expression | undefined, param: arkts.Expression, index?: number) => void, + options?: { isTrailingCall?: boolean; hasReceiver?: boolean; hasRestParameter?: boolean } +): void { + const argLen: number = args.length; + const paramLen: number = params.length; + if (argLen === 0 || paramLen === 0) { + return; } - if (arkts.isETSFunctionType(typeAnnotation)) { - return true; - } else if (arkts.isETSUnionType(typeAnnotation)) { - return typeAnnotation.types.some((type) => arkts.isETSFunctionType(type)); + const hasRestParam: boolean = !!options?.hasRestParameter; + const isTrailingCall: boolean = !!options?.isTrailingCall; + const maxLen = hasRestParam ? argLen : paramLen; + let index: number = 0; + while (index < maxLen - 1) { + const param = params.at(index) ?? params.at(paramLen - 1)!; + const argument = isTrailingCall && index >= argLen - 1 ? undefined : args.at(index); + cb(argument, param, index); + index++; } - return false; + const lastParam = params.at(paramLen - 1)!; + const lastIndex = isTrailingCall ? argLen - 1 : maxLen - 1; + const lastArg = args.at(lastIndex); + cb(lastArg, lastParam, maxLen - 1); } -export function findCanAddMemoFromParamExpression( - param: arkts.AstNode | undefined -): param is arkts.ETSParameterExpression { - if (!param) { - return false; - } - if (!arkts.isEtsParameterExpression(param)) { - return false; - } - const type = param.type; - return findCanAddMemoFromTypeAnnotation(type); -} - -export function isMemoAnnotation(node: arkts.AnnotationUsage, memoName: MemoNames): boolean { - if (!(node.expr !== undefined && arkts.isIdentifier(node.expr) && node.expr.name === memoName)) { +/** + * Determine whether it is method with specified name. + * + * @param method method definition node + * @param name specified method name + */ +export function isKnownMethodDefinition(method: arkts.MethodDefinition, name: string): boolean { + if (!method || !arkts.isMethodDefinition(method)) { return false; } - return true; -} -export function addMemoAnnotation(node: T, memoName: MemoNames = MemoNames.MEMO): T { - collectMemoAnnotationSource(memoName); - if (arkts.isETSUnionType(node)) { - const functionType = node.types.find((type) => arkts.isETSFunctionType(type)); - if (!functionType) { - return node; - } - addMemoAnnotation(functionType, memoName); - return node; - } - const newAnnotations: arkts.AnnotationUsage[] = [ - ...node.annotations.filter((it) => !isMemoAnnotation(it, memoName)), - annotation(memoName), - ]; - collectMemoAnnotationImport(memoName); - if (arkts.isEtsParameterExpression(node)) { - node.annotations = newAnnotations; - return node; - } - return node.setAnnotations(newAnnotations) as T; + // For now, we only considered matched method name. + const isNameMatched: boolean = method.name?.name === name; + return isNameMatched; } diff --git a/koala-wrapper/native/include/common.h b/koala-wrapper/native/include/common.h index 399d28d22..ad087c22a 100644 --- a/koala-wrapper/native/include/common.h +++ b/koala-wrapper/native/include/common.h @@ -26,6 +26,11 @@ using std::string, std::cout, std::endl, std::vector; +// struct AstNodeContext { +// KInt astNodeType = 0; +// es2panda_AstNode *ptr = nullptr; +// }; + es2panda_Impl *GetImpl(); string getString(KStringPtr ptr); diff --git a/koala-wrapper/native/src/bridges.cc b/koala-wrapper/native/src/bridges.cc index 5333ea8b5..d0858a000 100644 --- a/koala-wrapper/native/src/bridges.cc +++ b/koala-wrapper/native/src/bridges.cc @@ -19,23 +19,82 @@ #include #include -std::set globalStructInfo; -std::mutex g_structMutex; - -void impl_InsertGlobalStructInfo(KNativePointer contextPtr, KStringPtr& instancePtr) -{ - std::lock_guard lock(g_structMutex); - globalStructInfo.insert(getStringCopy(instancePtr)); - return; -} -KOALA_INTEROP_V2(InsertGlobalStructInfo, KNativePointer, KStringPtr); - -KBoolean impl_HasGlobalStructInfo(KNativePointer contextPtr, KStringPtr& instancePtr) -{ - std::lock_guard lock(g_structMutex); - return globalStructInfo.count(getStringCopy(instancePtr)); -} -KOALA_INTEROP_2(HasGlobalStructInfo, KBoolean, KNativePointer, KStringPtr); +// thread_local std::unordered_map cachedAstNodeContext; +// std::set globalStructInfo; +// std::mutex g_structMutex; + +// KInt impl_AstNodeContextAstNodeType(KNativePointer context, KNativePointer receiver) +// { +// const auto astNodeContext = reinterpret_cast(receiver); +// return astNodeContext->astNodeType; +// } +// KOALA_INTEROP_2(AstNodeContextAstNodeType, KInt, KNativePointer, KNativePointer); + +// KNativePointer impl_AstNodeContextAstNodePointer(KNativePointer context, KNativePointer receiver) +// { +// const auto astNodeContext = reinterpret_cast(receiver); +// return astNodeContext->ptr; +// } +// KOALA_INTEROP_2(AstNodeContextAstNodePointer, KNativePointer, KNativePointer, KNativePointer); + +// void impl_InsertAstNodeContext(KNativePointer context, KNativePointer receiver) +// { +// const auto _context = reinterpret_cast(context); +// const auto _receiver = reinterpret_cast(receiver); +// const auto type = GetImpl()->AstNodeTypeConst(_context, _receiver); +// AstNodeContext* astNodeContext = new AstNodeContext(); +// astNodeContext->astNodeType = type; +// astNodeContext->ptr = _receiver; +// cachedAstNodeContext[receiver] = astNodeContext; +// return; +// } +// KOALA_INTEROP_V2(InsertAstNodeContext, KNativePointer, KNativePointer); + +// AstNodeContext* impl_GetAstNodeContextFromCache(KNativePointer context, KNativePointer receiver) +// { +// const auto _receiver = reinterpret_cast(receiver); +// if (cachedAstNodeContext.count(_receiver) == 0U) { +// return nullptr; +// } +// return nullptr; +// } +// KOALA_INTEROP_2(GetAstNodeContextFromCache, AstNodeContext, KNativePointer, KStringPtr); + +// KBoolean impl_HasAstNodeContextInCache(KNativePointer context, KStringPtr& keyPtr) +// { +// return cachedAstNodeContext.find(keyPtr) != cachedAstNodeContext.end(); +// } +// KOALA_INTEROP_2(HasAstNodeContextInCache, KBoolean, KNativePointer, KStringPtr); + +// void impl_ClearAstNodeContextCache(KNativePointer context) +// { +// for (auto& pair : cachedAstNodeContext) { +// delete pair.second; +// } +// cachedAstNodeContext.clear(); +// } +// KOALA_INTEROP_V1(ClearAstNodeContextCache, KNativePointer); + +// KBoolean impl_IsAstNodeContextCacheEmpty(KNativePointer context) +// { +// return cachedAstNodeContext.empty(); +// } +// KOALA_INTEROP_1(ClearAstNodeContextCache, KBoolean, KNativePointer); + +// void impl_InsertGlobalStructInfo(KNativePointer contextPtr, KStringPtr& instancePtr) +// { +// std::lock_guard lock(g_structMutex); +// globalStructInfo.insert(getStringCopy(instancePtr)); +// return; +// } +// KOALA_INTEROP_V2(InsertGlobalStructInfo, KNativePointer, KStringPtr); + +// KBoolean impl_HasGlobalStructInfo(KNativePointer contextPtr, KStringPtr& instancePtr) +// { +// std::lock_guard lock(g_structMutex); +// return globalStructInfo.count(getStringCopy(instancePtr)); +// } +// KOALA_INTEROP_2(HasGlobalStructInfo, KBoolean, KNativePointer, KStringPtr); KBoolean impl_ClassDefinitionIsFromStructConst(KNativePointer contextPtr, KNativePointer instancePtr) { @@ -316,6 +375,23 @@ static KNativePointer impl_ExternalSourcePrograms(KNativePointer instance) } KOALA_INTEROP_1(ExternalSourcePrograms, KNativePointer, KNativePointer); +KNativePointer impl_CreateContextGenerateAbcForExternalSourceFiles(KNativePointer configPtr, KInt fileNamesCount, KStringArray fileNames) +{ + auto config = reinterpret_cast(configPtr); + const std::size_t HEADER_LEN = 4; + const char **argv = new const char *[static_cast(fileNamesCount)]; + std::size_t position = HEADER_LEN; + std::size_t strLen; + for (std::size_t i = 0; i < static_cast(fileNamesCount); ++i) { + strLen = unpackUInt(fileNames + position); + position += HEADER_LEN; + argv[i] = strdup(std::string(reinterpret_cast(fileNames + position), strLen).c_str()); + position += strLen; + } + return GetImpl()->CreateContextGenerateAbcForExternalSourceFiles(config, fileNamesCount, argv); +} +KOALA_INTEROP_3(CreateContextGenerateAbcForExternalSourceFiles, KNativePointer, KNativePointer, KInt, KStringArray) + KBoolean impl_IsClassProperty(KNativePointer nodePtr) { auto node = reinterpret_cast(nodePtr); @@ -627,6 +703,7 @@ KNativePointer impl_CreateSuggestionInfo(KNativePointer context, KNativePointer } KOALA_INTEROP_5(CreateSuggestionInfo, KNativePointer, KNativePointer, KNativePointer, KStringArray, KInt, KStringPtr); + void impl_LogDiagnostic(KNativePointer context, KNativePointer kind, KStringArray argvPtr, KInt argc, KNativePointer pos) { @@ -646,6 +723,7 @@ void impl_LogDiagnostic(KNativePointer context, KNativePointer kind, KStringArra GetImpl()->LogDiagnostic(_context_, _kind_, argv, argc, _pos_); } KOALA_INTEROP_V5(LogDiagnostic, KNativePointer, KNativePointer, KStringArray, KInt, KNativePointer); + void impl_LogDiagnosticWithSuggestion(KNativePointer context, KNativePointer diagnosticInfo, KNativePointer suggestionInfo, KNativePointer range) { @@ -656,3 +734,12 @@ void impl_LogDiagnosticWithSuggestion(KNativePointer context, KNativePointer dia GetImpl()->LogDiagnosticWithSuggestion(_context, _diagnosticInfo, _suggestionInfo, _range); } KOALA_INTEROP_V4(LogDiagnosticWithSuggestion, KNativePointer, KNativePointer, KNativePointer, KNativePointer); + +KBoolean impl_CallExpressionIsTrailingCallConst(KNativePointer context, KNativePointer receiver) +{ + const auto _context = reinterpret_cast(context); + const auto _receiver = reinterpret_cast(receiver); + return GetImpl()->CallExpressionIsTrailingCallConst(_context, _receiver); +} +KOALA_INTEROP_2(CallExpressionIsTrailingCallConst, KBoolean, KNativePointer, KNativePointer); + diff --git a/koala-wrapper/src/Es2pandaNativeModule.ts b/koala-wrapper/src/Es2pandaNativeModule.ts index 6c5b48e7d..c32a9524c 100644 --- a/koala-wrapper/src/Es2pandaNativeModule.ts +++ b/koala-wrapper/src/Es2pandaNativeModule.ts @@ -105,6 +105,10 @@ export class Es2pandaNativeModule { _CreateContextFromString(config: KPtr, source: String, filename: String): KPtr { throw new Error('Not implemented'); } + _CreateContextGenerateAbcForExternalSourceFiles(config: KPtr, fileCount: KInt, filenames: + string[]): KPtr { + throw new Error('Not implemented'); + } _CreateContextFromFile(config: KPtr, filename: String): KPtr { throw new Error('Not implemented'); } @@ -897,13 +901,13 @@ export class Es2pandaNativeModule { throw new Error('CreateCacheContextFromFile was not overloaded by native module initialization'); } - _InsertGlobalStructInfo(context: KNativePointer, str: String): void { - throw new Error('InsertGlobalStructInfo was not overloaded by native module initialization'); - } + // _InsertGlobalStructInfo(context: KNativePointer, str: String): void { + // throw new Error('InsertGlobalStructInfo was not overloaded by native module initialization'); + // } - _HasGlobalStructInfo(context: KNativePointer, str: String): KBoolean { - throw new Error('HasGlobalStructInfo was not overloaded by native module initialization'); - } + // _HasGlobalStructInfo(context: KNativePointer, str: String): KBoolean { + // throw new Error('HasGlobalStructInfo was not overloaded by native module initialization'); + // } _CreateDiagnosticKind(context: KNativePointer, message: string, type: PluginDiagnosticType): KNativePointer { throw new Error('Not implemented'); } @@ -929,6 +933,10 @@ export class Es2pandaNativeModule { _SetUpSoPath(soPath: string): void { throw new Error('Not implemented'); } + + _CallExpressionIsTrailingCallConst(context: KNativePointer, node: KNativePointer): boolean { + throw new Error('CallExpressionIsTrailingCallConst was not overloaded by native module initialization'); + } } export function initEs2panda(): Es2pandaNativeModule { diff --git a/koala-wrapper/src/arkts-api/class-by-peer.ts b/koala-wrapper/src/arkts-api/class-by-peer.ts index f8d79996c..a12afcba8 100644 --- a/koala-wrapper/src/arkts-api/class-by-peer.ts +++ b/koala-wrapper/src/arkts-api/class-by-peer.ts @@ -26,7 +26,7 @@ export function clearNodeCache(): void { cache.clear(); } -function getOrPut(peer: KNativePointer, create: (peer: KNativePointer) => AstNode): AstNode { +export function getOrPut(peer: KNativePointer, create: (peer: KNativePointer) => AstNode): AstNode { if (cache.has(peer)) { return cache.get(peer)!; } diff --git a/koala-wrapper/src/arkts-api/factory/nodeFactory.ts b/koala-wrapper/src/arkts-api/factory/nodeFactory.ts index 0f9c3e283..f9ad24118 100644 --- a/koala-wrapper/src/arkts-api/factory/nodeFactory.ts +++ b/koala-wrapper/src/arkts-api/factory/nodeFactory.ts @@ -77,6 +77,7 @@ import { ArrayExpression, AnnotationDeclaration, TryStatement, + TSClassImplements, } from '../../generated'; import { Es2pandaModifierFlags } from '../../generated/Es2pandaEnums'; import { classPropertySetOptional, hasModifierFlag } from '../utilities/public'; @@ -134,6 +135,7 @@ import { updateTemplateLiteral } from '../node-utilities/TemplateLiteral'; import { updateArrayExpression } from '../node-utilities/ArrayExpression'; import { updateAnnotationDeclaration } from '../node-utilities/AnnotationDeclaration'; import { updateTryStatement } from '../node-utilities/TryStatement'; +import { updateTSClassImplements } from '../node-utilities/TSClassImplements'; export const factory = { get createIdentifier(): (...args: Parameters) => Identifier { @@ -565,6 +567,12 @@ export const factory = { get updateTryStatement(): (...args: Parameters) => TryStatement { return updateTryStatement; }, + get createTSClassImplements(): (...args: Parameters) => TSClassImplements { + return TSClassImplements.createTSClassImplements; + }, + get UpdateTSClassImplements(): (...args: Parameters) => TSClassImplements { + return updateTSClassImplements; + }, /** @deprecated */ createTypeParameter1_(name: Identifier, constraint?: TypeNode, defaultType?: TypeNode) { return TSTypeParameter.createTSTypeParameter(Identifier.create1Identifier(name.name), constraint, defaultType); diff --git a/koala-wrapper/src/arkts-api/index.ts b/koala-wrapper/src/arkts-api/index.ts index f0a9a91d1..1a0e34eb7 100644 --- a/koala-wrapper/src/arkts-api/index.ts +++ b/koala-wrapper/src/arkts-api/index.ts @@ -68,6 +68,7 @@ export * from "./types" export * from "./utilities/private" export * from "./utilities/public" export * from "./utilities/performance" +export * from "./utilities/nodeCache" export * from "./factory/nodeFactory" export * from "./factory/nodeTests" export * from "./visitor" diff --git a/koala-wrapper/src/arkts-api/node-utilities/ArrowFunctionExpression.ts b/koala-wrapper/src/arkts-api/node-utilities/ArrowFunctionExpression.ts index 9ad9e11af..bb155cd3b 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/ArrowFunctionExpression.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/ArrowFunctionExpression.ts @@ -16,6 +16,7 @@ import { ScriptFunction } from '../../generated'; import { isSameNativeObject } from '../peers/ArktsObject'; import { ArrowFunctionExpression } from '../types'; +import { NodeCache } from '../utilities/nodeCache'; import { attachModifiers, updateThenAttach } from '../utilities/private'; export function updateArrowFunctionExpression( @@ -31,5 +32,11 @@ export function updateArrowFunctionExpression( attachModifiers, (node: ArrowFunctionExpression, original: ArrowFunctionExpression) => node.setAnnotations(original.annotations) ); - return update(original, func); + const newNode = update(original, func); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [ArrowFunctionExpression] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [ArrowFunctionExpression] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/node-utilities/CallExpression.ts b/koala-wrapper/src/arkts-api/node-utilities/CallExpression.ts index d1489441a..03dd58971 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/CallExpression.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/CallExpression.ts @@ -17,6 +17,7 @@ import { TypeNode } from '../../generated'; import { isSameNativeObject } from '../peers/ArktsObject'; import { AstNode } from '../peers/AstNode'; import { CallExpression } from '../types'; +import { NodeCache } from '../utilities/nodeCache'; import { attachModifiers, updateThenAttach } from '../utilities/private'; export function updateCallExpression( @@ -41,5 +42,11 @@ export function updateCallExpression( (node: CallExpression, original: CallExpression) => !!original.trailingBlock ? node.setTralingBlock(original.trailingBlock) : node ); - return update(original, expression, typeArguments, args, isOptional, trailingComma); + const newNode = update(original, expression, typeArguments, args, isOptional, trailingComma); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [CallExpression] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [CallExpression] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/node-utilities/ClassProperty.ts b/koala-wrapper/src/arkts-api/node-utilities/ClassProperty.ts index 06450c0cb..e600a4cca 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/ClassProperty.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/ClassProperty.ts @@ -18,6 +18,7 @@ import { isSameNativeObject } from '../peers/ArktsObject'; import { updateThenAttach } from '../utilities/private'; import { classPropertySetOptional, hasModifierFlag } from '../utilities/public'; import { Es2pandaModifierFlags } from '../../generated/Es2pandaEnums'; +import { NodeCache } from '../utilities/nodeCache'; export function updateClassProperty( original: ClassProperty, @@ -47,5 +48,11 @@ export function updateClassProperty( return node; } ); - return update(original, key, value, typeAnnotation, modifiers, isComputed); + const newNode = update(original, key, value, typeAnnotation, modifiers, isComputed); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [ClassProperty] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [ClassProperty] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/node-utilities/ETSFunctionType.ts b/koala-wrapper/src/arkts-api/node-utilities/ETSFunctionType.ts index a0e522040..bdd05a3dc 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/ETSFunctionType.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/ETSFunctionType.ts @@ -17,6 +17,7 @@ import { ETSFunctionType, FunctionSignature } from '../../generated'; import { isSameNativeObject } from '../peers/ArktsObject'; import { attachModifiers, updateThenAttach } from '../utilities/private'; import { Es2pandaScriptFunctionFlags } from '../../generated/Es2pandaEnums'; +import { NodeCache } from '../utilities/nodeCache'; export function updateETSFunctionType( original: ETSFunctionType, @@ -38,5 +39,11 @@ export function updateETSFunctionType( attachModifiers, (node: ETSFunctionType, original: ETSFunctionType) => node.setAnnotations(original.annotations) ); - return update(original, signature, funcFlags); + const newNode = update(original, signature, funcFlags); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [ETSFunctionType] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [ETSFunctionType] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/node-utilities/ETSParameterExpression.ts b/koala-wrapper/src/arkts-api/node-utilities/ETSParameterExpression.ts index b9e8f0514..cd483c17a 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/ETSParameterExpression.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/ETSParameterExpression.ts @@ -17,6 +17,7 @@ import { Identifier } from '../../generated'; import { isSameNativeObject } from '../peers/ArktsObject'; import { AstNode } from '../peers/AstNode'; import { ETSParameterExpression } from '../types'; +import { NodeCache } from '../utilities/nodeCache'; import { attachModifiers, updateThenAttach } from '../utilities/private'; export function updateETSParameterExpression( @@ -36,8 +37,15 @@ export function updateETSParameterExpression( attachModifiers, (node: ETSParameterExpression, original: ETSParameterExpression) => { node.annotations = original.annotations; + node.type = original.type; return node; - } + }, ); - return update(original, identifier, initializer); + const newNode = update(original, identifier, initializer); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [ETSParameterExpression] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [ETSParameterExpression] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/node-utilities/ETSUnionType.ts b/koala-wrapper/src/arkts-api/node-utilities/ETSUnionType.ts index c8dc20720..c4bc75dae 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/ETSUnionType.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/ETSUnionType.ts @@ -15,6 +15,7 @@ import { ETSUnionType, TypeNode } from '../../generated'; import { isSameNativeObject } from '../peers/ArktsObject'; +import { NodeCache } from '../utilities/nodeCache'; import { attachModifiers, updateThenAttach } from '../utilities/private'; export function updateETSUnionType(original: ETSUnionType, types: readonly TypeNode[]): ETSUnionType { @@ -23,5 +24,11 @@ export function updateETSUnionType(original: ETSUnionType, types: readonly TypeN } const update = updateThenAttach(ETSUnionType.updateETSUnionType, attachModifiers); - return update(original, types); + const newNode = update(original, types); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [ETSUnionType] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [ETSUnionType] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/node-utilities/Identifier.ts b/koala-wrapper/src/arkts-api/node-utilities/Identifier.ts index f31d25fc6..586d1a0e4 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/Identifier.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/Identifier.ts @@ -15,6 +15,7 @@ import { Identifier, TypeNode } from '../../generated'; import { isSameNativeObject } from '../peers/ArktsObject'; +import { NodeCache } from '../utilities/nodeCache'; import { attachModifiers, updateThenAttach } from '../utilities/private'; export function updateIdentifier(original: Identifier, name: string, typeAnnotation?: TypeNode): Identifier { @@ -23,5 +24,11 @@ export function updateIdentifier(original: Identifier, name: string, typeAnnotat } const update = updateThenAttach(Identifier.update2Identifier, attachModifiers); - return update(original, name, typeAnnotation); + const newNode = update(original, name, typeAnnotation); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [Identifier] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [Identifier] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/node-utilities/MethodDefinition.ts b/koala-wrapper/src/arkts-api/node-utilities/MethodDefinition.ts index 42006a300..d1363f87c 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/MethodDefinition.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/MethodDefinition.ts @@ -20,6 +20,7 @@ import { MethodDefinition } from '../types'; import { updateThenAttach } from '../utilities/private'; import { Es2pandaMethodDefinitionKind } from '../../generated/Es2pandaEnums'; import { ScriptFunction } from '../../generated'; +import { NodeCache } from '../utilities/nodeCache'; export function updateMethodDefinition( original: MethodDefinition, @@ -42,5 +43,11 @@ export function updateMethodDefinition( const update = updateThenAttach(MethodDefinition.update, (node: MethodDefinition, original: MethodDefinition) => node.setOverloads(original.overloads) ); - return update(original, kind, key, value, modifiers, isComputed); + const newNode = update(original, kind, key, value, modifiers, isComputed); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [MethodDefinition] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [MethodDefinition] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/node-utilities/Property.ts b/koala-wrapper/src/arkts-api/node-utilities/Property.ts index 4b0a33c5b..12a3357ca 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/Property.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/Property.ts @@ -15,6 +15,7 @@ import { Expression, Property } from '../../generated'; import { isSameNativeObject } from '../peers/ArktsObject'; +import { NodeCache } from '../utilities/nodeCache'; import { attachModifiers, updateThenAttach } from '../utilities/private'; export function updateProperty(original: Property, key?: Expression, value?: Expression): Property { @@ -23,5 +24,12 @@ export function updateProperty(original: Property, key?: Expression, value?: Exp } const update = updateThenAttach(Property.updateProperty, attachModifiers); - return update(original, key, value); + const newNode = update(original, key, value); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [Property] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [Property] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; + } diff --git a/koala-wrapper/src/arkts-api/node-utilities/ReturnStatement.ts b/koala-wrapper/src/arkts-api/node-utilities/ReturnStatement.ts index 9fc05a371..fc942c24f 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/ReturnStatement.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/ReturnStatement.ts @@ -15,6 +15,7 @@ import { Expression, ReturnStatement } from '../../generated'; import { isSameNativeObject } from '../peers/ArktsObject'; +import { NodeCache } from '../utilities/nodeCache'; import { attachModifiers, updateThenAttach } from '../utilities/private'; export function updateReturnStatement(original: ReturnStatement, argument?: Expression): ReturnStatement { @@ -23,5 +24,11 @@ export function updateReturnStatement(original: ReturnStatement, argument?: Expr } const update = updateThenAttach(ReturnStatement.update1ReturnStatement, attachModifiers); - return update(original, argument); + const newNode = update(original, argument); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [ReturnStatement] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [ReturnStatement] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/node-utilities/ScriptFunction.ts b/koala-wrapper/src/arkts-api/node-utilities/ScriptFunction.ts index 745a5398c..6af60d304 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/ScriptFunction.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/ScriptFunction.ts @@ -16,6 +16,7 @@ import { FunctionSignature, ScriptFunction } from '../../generated'; import { isSameNativeObject } from '../peers/ArktsObject'; import { AstNode } from '../peers/AstNode'; +import { NodeCache } from '../utilities/nodeCache'; import { updateThenAttach } from '../utilities/private'; export function updateScriptFunction( @@ -42,5 +43,11 @@ export function updateScriptFunction( (node: ScriptFunction, original: ScriptFunction) => (!!original.id ? node.setIdent(original.id) : node), (node: ScriptFunction, original: ScriptFunction) => node.setAnnotations(original.annotations) ); - return update(original, databody, datasignature, datafuncFlags, dataflags); + const newNode = update(original, databody, datasignature, datafuncFlags, dataflags); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [ScriptFunction] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [ScriptFunction] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/node-utilities/TSClassImplements.ts b/koala-wrapper/src/arkts-api/node-utilities/TSClassImplements.ts new file mode 100644 index 000000000..1e51e9c44 --- /dev/null +++ b/koala-wrapper/src/arkts-api/node-utilities/TSClassImplements.ts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Expression, TSClassImplements, TSTypeParameterInstantiation } from '../../generated'; +import { isSameNativeObject } from '../peers/ArktsObject'; +import { attachModifiers, updateThenAttach } from '../utilities/private'; + +export function updateTSClassImplements( + original: TSClassImplements, + expression?: Expression, + typeParameters?: TSTypeParameterInstantiation +): TSClassImplements { + if (isSameNativeObject(expression, original.expr) && isSameNativeObject(typeParameters, original.typeParameters)) { + return original; + } + + const update = updateThenAttach(TSClassImplements.updateTSClassImplements, attachModifiers); + return update(original, expression, typeParameters); +} diff --git a/koala-wrapper/src/arkts-api/node-utilities/TSTypeAliasDeclaration.ts b/koala-wrapper/src/arkts-api/node-utilities/TSTypeAliasDeclaration.ts index 3531ee3a9..6889495eb 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/TSTypeAliasDeclaration.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/TSTypeAliasDeclaration.ts @@ -15,6 +15,7 @@ import { Identifier, TSTypeAliasDeclaration, TSTypeParameterDeclaration, TypeNode } from '../../generated'; import { isSameNativeObject } from '../peers/ArktsObject'; +import { NodeCache } from '../utilities/nodeCache'; import { attachModifiers, updateThenAttach } from '../utilities/private'; export function updateTSTypeAliasDeclaration( @@ -36,5 +37,11 @@ export function updateTSTypeAliasDeclaration( attachModifiers, (node: TSTypeAliasDeclaration, original: TSTypeAliasDeclaration) => node.setAnnotations(original.annotations) ); - return update(original, id, typeParams, typeAnnotation); + const newNode = update(original, id, typeParams, typeAnnotation); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [TSTypeAliasDeclaration] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [TSTypeAliasDeclaration] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/node-utilities/VariableDeclarator.ts b/koala-wrapper/src/arkts-api/node-utilities/VariableDeclarator.ts index 52c002fd3..4652143da 100644 --- a/koala-wrapper/src/arkts-api/node-utilities/VariableDeclarator.ts +++ b/koala-wrapper/src/arkts-api/node-utilities/VariableDeclarator.ts @@ -19,6 +19,7 @@ import { VariableDeclarator } from '../types'; import { attachModifiers, updateThenAttach } from '../utilities/private'; import { Es2pandaVariableDeclaratorFlag } from '../../generated/Es2pandaEnums'; import { AstNode } from '../peers/AstNode'; +import { NodeCache } from '../utilities/nodeCache'; export function updateVariableDeclarator( original: VariableDeclarator, @@ -35,5 +36,11 @@ export function updateVariableDeclarator( } const update = updateThenAttach(VariableDeclarator.update, attachModifiers); - return update(original, flag, name, initializer); + const newNode = update(original, flag, name, initializer); + if (NodeCache.getInstance().has(original)) { + // console.log(`[NODE CACHE UPDATE] [VariableDeclarator] [ORIGINAL] ptr ${original.peer} node: `, original.dumpSrc()); + // console.log(`[NODE CACHE UPDATE] [VariableDeclarator] [NEW NODE] ptr ${newNode.peer} node: `, newNode.dumpSrc()); + NodeCache.getInstance().refresh(original, newNode); + } + return newNode; } diff --git a/koala-wrapper/src/arkts-api/peers/Context.ts b/koala-wrapper/src/arkts-api/peers/Context.ts index b4bf7a203..60c923698 100644 --- a/koala-wrapper/src/arkts-api/peers/Context.ts +++ b/koala-wrapper/src/arkts-api/peers/Context.ts @@ -16,8 +16,8 @@ import { ArktsObject } from './ArktsObject'; import { global } from '../static/global'; import { throwError, filterSource } from '../../utils'; -import { passString } from '../utilities/private'; -import { KBoolean, KNativePointer } from '@koalaui/interop'; +import { passString, passStringArray } from '../utilities/private'; +import { KBoolean, KInt, KNativePointer } from '@koalaui/interop'; import { AstNode } from './AstNode'; import { Program } from './Program'; export class Context extends ArktsObject { @@ -45,6 +45,16 @@ export class Context extends ArktsObject { ); } + static createContextGenerateAbcForExternalSourceFiles(filenames: string[]): Context { + if (!global.configIsInitialized()) { + throwError(`Config not initialized`); + } + return new Context( + global.es2panda._CreateContextGenerateAbcForExternalSourceFiles(global.config, filenames.length, passStringArray(filenames)) + ); + } + + static destroyAndRecreate(ast: AstNode): Context { console.log('[TS WRAPPER] DESTROY AND RECREATE'); const source = filterSource(ast.dumpSrc()); diff --git a/koala-wrapper/src/arkts-api/types.ts b/koala-wrapper/src/arkts-api/types.ts index be1c31651..7666ec711 100644 --- a/koala-wrapper/src/arkts-api/types.ts +++ b/koala-wrapper/src/arkts-api/types.ts @@ -241,6 +241,14 @@ export class CallExpression extends Expression { return this; } + get hasTrailingComma(): boolean { + return global.generatedEs2panda._CallExpressionHasTrailingCommaConst(global.context, this.peer); + } + + get isTrailingCall(): boolean { + return global.es2panda._CallExpressionIsTrailingCallConst(global.context, this.peer); + } + readonly expression: AstNode; // Expression readonly typeArguments: readonly TypeNode[] | undefined; readonly arguments: readonly Expression[]; diff --git a/koala-wrapper/src/arkts-api/utilities/nodeCache.ts b/koala-wrapper/src/arkts-api/utilities/nodeCache.ts new file mode 100644 index 000000000..d6caa68a9 --- /dev/null +++ b/koala-wrapper/src/arkts-api/utilities/nodeCache.ts @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { KNativePointer } from "@koalaui/interop"; +import { Es2pandaAstNodeType } from "../../Es2pandaEnums"; +import { AstNode, UnsupportedNode } from "../peers/AstNode"; +import { global } from "../static/global"; +import { getOrPut, nodeByType } from "../class-by-peer"; + +export interface AstNodeCacheValue { + peer: KNativePointer; + type: Es2pandaAstNodeType; + metadata?: AstNodeCacheValueMetadata; +} + +export interface AstNodeCacheValueMetadata { + callName?: string; + hasReceiver?: boolean; + isSetter?: boolean; + isGetter?: boolean; + // isReturnVoid?: boolean; + hasMemoSkip?: boolean; + hasMemoIntrinsic?: boolean; + hasMemoEntry?: boolean; +} + +export class NodeCache { + private _isCollected: boolean = false; + private cacheMap: Map; + private static instance: NodeCache; + + private constructor() { + this.cacheMap = new Map(); + } + + static getInstance(): NodeCache { + if (!this.instance) { + this.instance = new NodeCache(); + } + return this.instance; + } + + collect(node: AstNode, metadata?: AstNodeCacheValueMetadata): void { + const peer = node.peer; + const type = global.generatedEs2panda._AstNodeTypeConst(global.context, node.peer); + let currMetadata: AstNodeCacheValueMetadata | undefined = metadata ?? {}; + if (this.cacheMap.has(peer)) { + const oldMetadata = this.cacheMap.get(peer)!.metadata ?? {}; + currMetadata = { ...oldMetadata, ...currMetadata }; + } + currMetadata = Object.keys(currMetadata).length === 0 ? undefined : currMetadata; + // console.log("[NodeCache] collect: current metadata: ", currMetadata); + this.cacheMap.set(peer, { peer, type, metadata: currMetadata }); + this._isCollected = true; + } + + refresh(original: AstNode, node: AstNode): void { + let metadata: AstNodeCacheValueMetadata | undefined; + if (this.has(original)) { + metadata = this.get(original)?.metadata; + this.cacheMap.delete(original.peer); + } + this.collect(node, metadata); + } + + isCollected(): boolean { + return this._isCollected; + } + + has(node: AstNode): boolean { + return this.cacheMap.has(node.peer); + } + + get(node: AstNode): AstNodeCacheValue | undefined { + return this.cacheMap.get(node.peer); + } + + clear(): void { + this.cacheMap.clear(); + this._isCollected = false; + } + + visualize(): void { + Array.from(this.cacheMap.values()).forEach(({ peer, type, metadata }) => { + const node = nodeByType.get(type) ?? UnsupportedNode; + const newNode = getOrPut(peer, (peer) => new node(peer)) as AstNode; + console.log(`[NODE CACHE] ptr ${peer}, type: ${type}, metadata: ${JSON.stringify(metadata)}, node: `, newNode.dumpSrc()); + }); + } +} diff --git a/koala-wrapper/src/arkts-api/utilities/public.ts b/koala-wrapper/src/arkts-api/utilities/public.ts index d7e2dfc33..c2f8deb0a 100644 --- a/koala-wrapper/src/arkts-api/utilities/public.ts +++ b/koala-wrapper/src/arkts-api/utilities/public.ts @@ -15,7 +15,7 @@ import { global } from '../static/global'; import { isNumber, throwError, getEnumName } from '../../utils'; -import { KNativePointer, KInt, nullptr, withStringResult } from '@koalaui/interop'; +import { KNativePointer, KInt, nullptr, withStringResult, KStringArrayPtr } from '@koalaui/interop' import { passNode, passString, passStringArray, unpackNodeArray, unpackNonNullableNode } from './private'; import { isFunctionDeclaration, isMemberExpression, isMethodDefinition, isNumberLiteral } from '../factory/nodeTests'; import { @@ -42,6 +42,7 @@ import { Program } from '../peers/Program'; import { clearNodeCache } from '../class-by-peer'; import { SourcePosition } from '../peers/SourcePosition'; import { MemberExpression } from '../to-be-generated/MemberExpression'; +import { KNativePointerArray } from 'src/generated/Es2pandaNativeModule'; export function proceedToState(state: Es2pandaContextState, context: KNativePointer, forceDtsEmit = false): void { console.log('[TS WRAPPER] PROCEED TO STATE: ', getEnumName(Es2pandaContextState, state)); @@ -310,10 +311,19 @@ export function CreateCacheContextFromFile( return global.es2panda._CreateCacheContextFromFile(configPtr, passString(filename), globalContext, isExternal); } -export function insertGlobalStructInfo(structName: string): void { - global.es2panda._InsertGlobalStructInfo(global.context, passString(structName)); -} +// export function insertGlobalStructInfo(structName: string): void { +// global.es2panda._InsertGlobalStructInfo(global.context, passString(structName)); +// } + +// export function hasGlobalStructInfo(structName: string): boolean { +// return global.es2panda._HasGlobalStructInfo(global.context, passString(structName)); +// } -export function hasGlobalStructInfo(structName: string): boolean { - return global.es2panda._HasGlobalStructInfo(global.context, passString(structName)); +export function createContextGenerateAbcForExternalSourceFiles( + fileCount: KInt, + filenames: string[] +): KNativePointer { + console.log("1232131") + console.log("filenames", filenames); + return global.es2panda._CreateContextGenerateAbcForExternalSourceFiles(global.config, fileCount, passStringArray(filenames)); } diff --git a/koala-wrapper/src/arkts-api/visitor.ts b/koala-wrapper/src/arkts-api/visitor.ts index b2bca2336..0d47aa30a 100644 --- a/koala-wrapper/src/arkts-api/visitor.ts +++ b/koala-wrapper/src/arkts-api/visitor.ts @@ -48,6 +48,9 @@ import { isArrayExpression, isTryStatement, isBinaryExpression, + isTSTypeAliasDeclaration, + isETSParameterExpression, + isETSFunctionType, } from '../generated'; import { isEtsScript, @@ -63,6 +66,7 @@ import { isVariableDeclarator, isArrowFunctionExpression, isAssignmentExpression, + isEtsParameterExpression, } from './factory/nodeTests'; import { classDefinitionFlags } from './utilities/public'; import { Es2pandaAstNodeType } from '../Es2pandaEnums'; @@ -268,6 +272,15 @@ function visitDeclaration(node: AstNode, visitor: Visitor): AstNode { nodesVisitor(node.declarators, visitor) ); } + if (isTSTypeAliasDeclaration(node)) { + updated = true; + return factory.updateTSTypeAliasDeclaration( + node, + node.id, + nodeVisitor(node.typeParams, visitor), + nodeVisitor(node.typeAnnotation, visitor) + ); + } // TODO return node; } @@ -427,5 +440,11 @@ function visitWithoutUpdate(node: T, visitor: Visitor): T { if (isImportDeclaration(node)) { nodesVisitor(node.specifiers, visitor); } + if (isETSFunctionType(node)) { + nodesVisitor(node.params, visitor); + } + if (isEtsParameterExpression(node)) { + nodeVisitor(node.type, visitor); + } return node; } diff --git a/koala-wrapper/src/generated/index.ts b/koala-wrapper/src/generated/index.ts index 0416a0691..81fc18f94 100644 --- a/koala-wrapper/src/generated/index.ts +++ b/koala-wrapper/src/generated/index.ts @@ -189,3 +189,4 @@ export * from "./peers/ETSDynamicFunctionType" export * from "./peers/InterfaceDecl" export * from "./peers/FunctionDecl" export * from "./peers/Context" +export * from "./peers/TSClassImplements"; -- Gitee