From 28942e87c6e04eaae242bdc050d5dadb1a50e767 Mon Sep 17 00:00:00 2001 From: xieziang Date: Tue, 24 Jun 2025 15:12:00 +0800 Subject: [PATCH 1/5] add koala mirror Signed-off-by: xieziang Change-Id: I4497679c53b266c975cc38df3095d0d8847631b3 --- arkui-plugins/custom-import-plugin.js | 18 ++++++ arkui-plugins/package.json | 3 +- arkui-plugins/path.ts | 4 ++ koala-wrapper/BUILD.gn | 4 ++ koala-wrapper/build_ts_wrapper.py | 81 ++++++++++++++++++++++++--- koala-wrapper/mirror/package.json | 56 ++++++++++++++++++ 6 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 koala-wrapper/mirror/package.json diff --git a/arkui-plugins/custom-import-plugin.js b/arkui-plugins/custom-import-plugin.js index 84b8a6bd5..13bda857c 100644 --- a/arkui-plugins/custom-import-plugin.js +++ b/arkui-plugins/custom-import-plugin.js @@ -40,6 +40,24 @@ module.exports = function (babel) { t.variableDeclarator(t.identifier('arkts'), requireCall) ]); + pathNode.replaceWithMultiple([newImport, arkts]); + } + if (sourceValue === '@koalaui/libarkts-mirror' && pathNode.node.specifiers.length === 1 && t.isImportNamespaceSpecifier(pathNode.node.specifiers[0])) { + const currentFileDir = path.dirname(pathNode.hub.file.opts.filename); + const configDir = process.cwd(); + const relativePath = path.relative(currentFileDir, configDir); + const importPath = relativePath ? path.join(relativePath, 'path') : './path'; + + const newImport = t.importDeclaration( + [t.importSpecifier(t.identifier('getArktsMirrorPath'), t.identifier('getArktsMirrorPath'))], + t.stringLiteral(importPath) + ); + + const requireCall = t.callExpression(t.identifier('require'), [t.callExpression(t.identifier('getArktsMirrorPath'), [])]); + const arkts = t.variableDeclaration('const', [ + t.variableDeclarator(t.identifier('mirrorArkts'), requireCall) + ]); + pathNode.replaceWithMultiple([newImport, arkts]); } } diff --git a/arkui-plugins/package.json b/arkui-plugins/package.json index 929c12377..47ca21127 100644 --- a/arkui-plugins/package.json +++ b/arkui-plugins/package.json @@ -31,6 +31,7 @@ "typescript": "^5.0.0" }, "dependencies": { - "@koalaui/libarkts": "../koala-wrapper" + "@koalaui/libarkts": "../koala-wrapper", + "@koalaui/libarkts-mirror": "../koala-wrapper/mirror" } } diff --git a/arkui-plugins/path.ts b/arkui-plugins/path.ts index a8646fcbf..d3f82da3f 100644 --- a/arkui-plugins/path.ts +++ b/arkui-plugins/path.ts @@ -59,3 +59,7 @@ export function getCommonPath() { export function getCompatPath() { return path.join(findRootDir(), 'koala-wrapper/koalaui/compat', './dist/src/index.js'); } + +export function getArktsMirrorPath() { + return path.join(findRootDir(), 'koala-wrapper/mirror', './build/arkts-api/index.js'); +} \ No newline at end of file diff --git a/koala-wrapper/BUILD.gn b/koala-wrapper/BUILD.gn index 58807818d..6f56b0623 100644 --- a/koala-wrapper/BUILD.gn +++ b/koala-wrapper/BUILD.gn @@ -15,10 +15,12 @@ import("//build/ohos.gni") import("//build/config/components/ets_frontend/ets2abc_config.gni") npm_path = "//prebuilts/build-tools/common/nodejs/current/bin/npm" +mirror_ui2abc_dir = "//foundation/arkui/ace_engine/frameworks/bridge/arkts_frontend/koala_mirror/ui2abc" action("gen_sdk_ts_wrapper") { script = "build_ts_wrapper.py" deps = [ "./native:es2panda" ] + external_deps = [ "ace_engine:es2panda" ] args = [ "--source_path", rebase_path(get_path_info(".", "abspath")), @@ -30,6 +32,8 @@ action("gen_sdk_ts_wrapper") { "$current_os", "--root_out_dir", rebase_path(root_out_dir), + "--mirror_ui2abc_dir", + rebase_path(mirror_ui2abc_dir) ] outputs = [ "$target_gen_dir" ] diff --git a/koala-wrapper/build_ts_wrapper.py b/koala-wrapper/build_ts_wrapper.py index 178f8772f..fb98cf6b8 100755 --- a/koala-wrapper/build_ts_wrapper.py +++ b/koala-wrapper/build_ts_wrapper.py @@ -18,17 +18,28 @@ import os import shutil import subprocess import sys -import tarfile - - -def copy_files(source_path, dest_path, is_file=False): +from pathlib import Path +import re + +def process_mirror_output(root_path): + root = Path(root_path) + js_files = [file for file in root.rglob('*.js') if file.is_file()] + for file in js_files: + content = file.read_text(encoding='utf-8') + new_content = re.sub(r'require\("@koalaui/(\w+?)"\)', r'require("#koalaui/\1")', content) + new_content = re.sub(r'require\("@common/(\w+?)"\)', r'require("#common/\1")', new_content) + new_content = re.sub(r'require\("@platform/(\w+?)"\)', r'require("#platform/\1")', new_content) + if new_content != content: + file.write_text(new_content, encoding='utf-8') + +def copy_files(source_path, dest_path, is_file=False, ignore_func=None): try: if is_file: os.makedirs(os.path.dirname(dest_path), exist_ok=True) shutil.copy(source_path, dest_path) else: shutil.copytree(source_path, dest_path, dirs_exist_ok=True, - symlinks=True) + symlinks=True, ignore=ignore_func) except Exception as err: raise Exception("Copy files failed. Error: " + str(err)) from err @@ -49,6 +60,28 @@ def build(options): def copy_output(options): + run_cmd(['rm', '-rf', os.path.join(options.source_path, 'mirror/build')]) + # only keep .d.ts files + def copy_to_source_ignore_func(dirname, filenames): + return [f for f in filenames + if os.path.isfile(os.path.join(dirname, f)) and not f.endswith('.d.ts')] + + copy_files(os.path.join(options.mirror_ui2abc_dir, 'libarkts/build'), + os.path.join(options.source_path, 'mirror/build'), + ignore_func=copy_to_source_ignore_func) + + copy_files(os.path.join(options.mirror_ui2abc_dir, '../incremental/common/build'), + os.path.join(options.source_path, 'mirror/build/koalaui/common/dist'), + ignore_func=copy_to_source_ignore_func) + + copy_files(os.path.join(options.mirror_ui2abc_dir, '../incremental/compat/build'), + os.path.join(options.source_path, 'mirror/build/koalaui/compat/dist'), + ignore_func=copy_to_source_ignore_func) + + copy_files(os.path.join(options.mirror_ui2abc_dir, '../interop/build/'), + os.path.join(options.source_path, 'mirror/build/koalaui/interop/dist'), + ignore_func=copy_to_source_ignore_func) + run_cmd(['rm', '-rf', options.output_path]) copy_files(os.path.join(options.source_path, 'build/lib'), os.path.join(options.output_path, 'build/lib')) @@ -59,17 +92,46 @@ def copy_output(options): copy_files(os.path.join(options.source_path, 'package.json'), os.path.join(options.output_path, 'package.json'), True) + # only keep JS files + def copy_to_out_ignore_func(dirname, filenames): + return [f for f in filenames + if os.path.isfile(os.path.join(dirname, f)) and not f.endswith('.js')] + + copy_files(os.path.join(options.mirror_ui2abc_dir, 'libarkts/build'), + os.path.join(options.output_path, 'mirror/build'), + ignore_func=copy_to_out_ignore_func) + + copy_files(os.path.join(options.mirror_ui2abc_dir, '../incremental/common/build'), + os.path.join(options.output_path, 'mirror/build/koalaui/common/dist'), + ignore_func=copy_to_out_ignore_func) + + copy_files(os.path.join(options.mirror_ui2abc_dir, '../incremental/compat/build'), + os.path.join(options.output_path, 'mirror/build/koalaui/compat/dist'), + ignore_func=copy_to_out_ignore_func) + + copy_files(os.path.join(options.mirror_ui2abc_dir, '../interop/build/'), + os.path.join(options.output_path, 'mirror/build/koalaui/interop/dist'), + ignore_func=copy_to_out_ignore_func) + + copy_files(os.path.join(options.source_path, 'mirror/package.json'), + os.path.join(options.output_path, 'mirror/package.json'), True) + + # replace package imports with alias imports + process_mirror_output(os.path.join(options.output_path, 'mirror/build')) + if options.current_os == "mingw" : copy_files(os.path.join(options.root_out_dir, 'libes2panda.dll'), os.path.join(options.output_path, 'build/native/es2panda.node'), True) - copy_files(os.path.join(options.root_out_dir, 'libes2panda.dll'), - os.path.join(options.source_path, 'build/native/es2panda.node'), True) + + copy_files(os.path.join(options.root_out_dir, 'libes2panda_lib.dll'), + os.path.join(options.output_path, 'mirror/build/native/build/es2panda.node'), True) if options.current_os == "linux" or options.current_os == "mac" : copy_files(os.path.join(options.root_out_dir, 'libes2panda.node'), os.path.join(options.output_path, 'build/native/es2panda.node'), True) - copy_files(os.path.join(options.root_out_dir, 'libes2panda.node'), - os.path.join(options.source_path, 'build/native/es2panda.node'), True) + + copy_files(os.path.join(options.root_out_dir, 'libes2panda_lib.node'), + os.path.join(options.output_path, 'mirror/build/native/build/es2panda.node'), True) def parse_args(): @@ -79,6 +141,7 @@ def parse_args(): parser.add_argument('--output_path', help='path to output') parser.add_argument('--root_out_dir', help='path to root out') parser.add_argument('--current_os', help='current_os') + parser.add_argument('--mirror_ui2abc_dir', help='mirror_ui2abc_dir') options = parser.parse_args() return options diff --git a/koala-wrapper/mirror/package.json b/koala-wrapper/mirror/package.json new file mode 100644 index 000000000..2942a72a8 --- /dev/null +++ b/koala-wrapper/mirror/package.json @@ -0,0 +1,56 @@ +{ + "name": "@koalaui/libarkts-mirror", + "version": "1.0.0", + "private": true, + "main": "./build/index.js", + "types": "./build/arkts-api/index.d.ts", + "exports": { + ".": "./build/arkts-api/index.js", + "./build/lib/es2panda": "./build/index.js" + }, + "files": [ + "./build/*" + ], + "config": { + "gen_version": "3.0.19" + }, + "devDependencies": { + "@babel/cli": "7.20.7", + "@babel/core": "7.20.12", + "@babel/plugin-proposal-class-properties": "7.18.6", + "@babel/preset-env": "7.20.2", + "@babel/preset-typescript": "7.18.6", + "@babel/runtime": "7.20.13", + "@tsconfig/recommended": "1.0.8", + "node-addon-api": "^8.3.0", + "typescript": "^5.0.0", + "@types/node": "^18.0.0" + }, + "imports": { + "#koalaui/interop": { + "default": "./build/koalaui/interop/dist/lib/src/interop/index.js" + }, + "#koalaui/common": { + "default": "./build/koalaui/common/dist/lib/src/index.js" + }, + "#koalaui/compat": { + "default": "./build/koalaui/compat/dist/src/index.js" + }, + "#common/wrappers": { + "browser": "./build/koalaui/interop/dist/lib/src/wasm/wrappers/index.js", + "node": "./build/koalaui/interop/dist/lib/src/napi/wrappers/index.js" + }, + "#common/wrappers/*": { + "browser": "./build/koalaui/interop/dist/lib/src/wasm/wrappers/*.js", + "node": "./build/koalaui/interop/dist/lib/src/napi/wrappers/*.js", + "default": "./build/koalaui/interop/dist/lib/src/napi/wrappers/*.js" + }, + "#platform": { + "ark": "./build/koalaui/compat/dist/src/ohos/index.js", + "ios": "./build/koalaui/compat/dist/src/typescript/index.js", + "browser": "./build/koalaui/compat/dist/src/typescript/index.js", + "node": "./build/koalaui/compat/dist/src/typescript/index.js", + "default": "./build/koalaui/compat/dist/src/typescript/index.js" + } + } +} -- Gitee From 6d7261894f17bf7e2519526e32b6a9d8f8430c43 Mon Sep 17 00:00:00 2001 From: xieziang Date: Tue, 1 Jul 2025 09:41:55 +0800 Subject: [PATCH 2/5] update interop-plugin Signed-off-by: xieziang Change-Id: If7dbfaea54d3162f1475f39d8529be0281281dff --- arkui-plugins/.gitignore | 6 + arkui-plugins/custom-import-plugin.js | 4 +- .../mirror-replace/common/abstract-visitor.ts | 53 +++ .../mirror-replace/common/arkts-utils.ts | 31 ++ .../common/compat-koala-wrapper.ts | 25 ++ arkui-plugins/mirror-replace/common/debug.ts | 77 +++++ .../mirror-replace/common/plugin-context.ts | 103 ++++++ .../mirror-replace/common/predefines.ts | 251 +++++++++++++++ .../mirror-replace/common/program-visitor.ts | 302 ++++++++++++++++++ .../interop-plugins/arkuiImportList.ts | 176 ++++++++++ .../interop-plugins/decl_transformer.ts | 137 ++++++++ .../interop-plugins/emit_transformer.ts | 61 ++++ .../mirror-replace/interop-plugins/index.ts | 100 ++++++ .../mirror-replace/interop-plugins/types.ts | 56 ++++ arkui-plugins/path.ts | 2 +- .../localtest/build_decl_config_template.json | 2 +- .../demo/entry/src/main/ets/pages/new.ets | 74 +++++ .../mirror/mirror_decl_config_template.json | 29 ++ arkui-plugins/test/mirror_decl_config.js | 51 +++ arkui-plugins/test/package.json | 5 +- koala-wrapper/BUILD.gn | 2 +- koala-wrapper/build_ts_wrapper.py | 72 +---- koala-wrapper/mirror/package.json | 50 +-- koala-wrapper/src/Es2pandaEnums.ts | 2 +- 24 files changed, 1552 insertions(+), 119 deletions(-) create mode 100644 arkui-plugins/mirror-replace/common/abstract-visitor.ts create mode 100644 arkui-plugins/mirror-replace/common/arkts-utils.ts create mode 100644 arkui-plugins/mirror-replace/common/compat-koala-wrapper.ts create mode 100644 arkui-plugins/mirror-replace/common/debug.ts create mode 100644 arkui-plugins/mirror-replace/common/plugin-context.ts create mode 100644 arkui-plugins/mirror-replace/common/predefines.ts create mode 100644 arkui-plugins/mirror-replace/common/program-visitor.ts create mode 100644 arkui-plugins/mirror-replace/interop-plugins/arkuiImportList.ts create mode 100644 arkui-plugins/mirror-replace/interop-plugins/decl_transformer.ts create mode 100644 arkui-plugins/mirror-replace/interop-plugins/emit_transformer.ts create mode 100644 arkui-plugins/mirror-replace/interop-plugins/index.ts create mode 100644 arkui-plugins/mirror-replace/interop-plugins/types.ts create mode 100755 arkui-plugins/test/mirror/demo/entry/src/main/ets/pages/new.ets create mode 100644 arkui-plugins/test/mirror/mirror_decl_config_template.json create mode 100644 arkui-plugins/test/mirror_decl_config.js diff --git a/arkui-plugins/.gitignore b/arkui-plugins/.gitignore index 98f9b3e90..7a115dac7 100644 --- a/arkui-plugins/.gitignore +++ b/arkui-plugins/.gitignore @@ -8,6 +8,7 @@ build/ lib/ *.tgz +*.log package-lock.json /**/*/package-lock.json @@ -17,3 +18,8 @@ coverage/ **/*/report test/demo/localtest/build_config.json +test/demo/localtest/build_decl_config.json +test/demo/hello_world + +test/mirror/mirror_decl_config.json +test/mirror/demo/hello_world diff --git a/arkui-plugins/custom-import-plugin.js b/arkui-plugins/custom-import-plugin.js index 13bda857c..510df2141 100644 --- a/arkui-plugins/custom-import-plugin.js +++ b/arkui-plugins/custom-import-plugin.js @@ -37,7 +37,7 @@ module.exports = function (babel) { const requireCall = t.callExpression(t.identifier('require'), [t.callExpression(t.identifier('getArktsPath'), [])]); const arkts = t.variableDeclaration('const', [ - t.variableDeclarator(t.identifier('arkts'), requireCall) + t.variableDeclarator(t.identifier(pathNode.node.specifiers[0].local.name), requireCall) ]); pathNode.replaceWithMultiple([newImport, arkts]); @@ -55,7 +55,7 @@ module.exports = function (babel) { const requireCall = t.callExpression(t.identifier('require'), [t.callExpression(t.identifier('getArktsMirrorPath'), [])]); const arkts = t.variableDeclaration('const', [ - t.variableDeclarator(t.identifier('mirrorArkts'), requireCall) + t.variableDeclarator(t.identifier(pathNode.node.specifiers[0].local.name), requireCall) ]); pathNode.replaceWithMultiple([newImport, arkts]); diff --git a/arkui-plugins/mirror-replace/common/abstract-visitor.ts b/arkui-plugins/mirror-replace/common/abstract-visitor.ts new file mode 100644 index 000000000..ba4cc27e2 --- /dev/null +++ b/arkui-plugins/mirror-replace/common/abstract-visitor.ts @@ -0,0 +1,53 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +export interface VisitorOptions { + isExternal?: boolean; + externalSourceName?: string; + program?: mirrorArkts.Program; +} + +export abstract class AbstractVisitor implements VisitorOptions { + public isExternal: boolean; + public externalSourceName?: string; + public program?: mirrorArkts.Program; + + constructor(options?: VisitorOptions) { + this.isExternal = options?.isExternal ?? false; + this.externalSourceName = options?.externalSourceName; + this.program = options?.program; + } + + indentation = 0; + + withIndentation(exec: () => T) { + this.indentation++; + const result = exec(); + this.indentation--; + return result; + } + + abstract visitor(node: mirrorArkts.AstNode): mirrorArkts.AstNode; + + reset(): void { + this.indentation = 0; + } + + visitEachChild(node: mirrorArkts.AstNode): mirrorArkts.AstNode { + return this.withIndentation(() => mirrorArkts.visitEachChild(node, (it) => this.visitor(it))); + } +} diff --git a/arkui-plugins/mirror-replace/common/arkts-utils.ts b/arkui-plugins/mirror-replace/common/arkts-utils.ts new file mode 100644 index 000000000..aaa6f23c7 --- /dev/null +++ b/arkui-plugins/mirror-replace/common/arkts-utils.ts @@ -0,0 +1,31 @@ +/* + * 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. + */ + +export function matchPrefix(prefixCollection: (string | RegExp)[], name: string): boolean { + for (const prefix of prefixCollection) { + let regex: RegExp; + + if (typeof prefix === 'string') { + regex = new RegExp('^' + prefix); + } else { + regex = new RegExp('^' + prefix.source); + } + + if (regex.test(name)) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/common/compat-koala-wrapper.ts b/arkui-plugins/mirror-replace/common/compat-koala-wrapper.ts new file mode 100644 index 000000000..1d84ac252 --- /dev/null +++ b/arkui-plugins/mirror-replace/common/compat-koala-wrapper.ts @@ -0,0 +1,25 @@ +/* + * 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 * as mirrorArkts from '@koalaui/libarkts-mirror'; +import { PluginContext } from './plugin-context'; + +export function copyGlobalFromKoalaWrapper(this: PluginContext): void { + mirrorArkts.arktsGlobal.config = arkts.arktsGlobal.config; + const contextPtr = arkts.arktsGlobal.compilerContext?.peer ?? this.getContextPtr() + mirrorArkts.arktsGlobal.compilerContext = new mirrorArkts.Context(contextPtr) + console.log('[MIRROR]: copied config and context from koala-wrapper') +} \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/common/debug.ts b/arkui-plugins/mirror-replace/common/debug.ts new file mode 100644 index 000000000..828e3e71d --- /dev/null +++ b/arkui-plugins/mirror-replace/common/debug.ts @@ -0,0 +1,77 @@ +/* + * 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 fs from 'fs'; +import * as path from 'path'; +import * as mirrorArkts from '@koalaui/libarkts-mirror'; + +const isDebugLog: boolean = true; +const isDebugDump: boolean = false; +const isPerformance: boolean = false; +const enableMemoryTracker: boolean = false; +// mirrorArkts.Performance.getInstance().skip(!isPerformance); +// mirrorArkts.Performance.getInstance().enableMemoryTracker(enableMemoryTracker); +export function getEnumName(enumType: any, value: number): string | undefined { + return enumType[value]; +} + +function mkDir(filePath: string): void { + const parent = path.join(filePath, '..'); + if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { + mkDir(parent); + } + fs.mkdirSync(filePath); +} + +export function debugDump( + content: string, + fileName: string, + isInit: boolean, + cachePath: string | undefined, + programFileName: string +): void { + if (!isDebugDump) return; + const currentDirectory = process.cwd(); + const modifiedFileName = programFileName.replaceAll('.', '_'); + const outputDir: string = cachePath + ? path.resolve(currentDirectory, cachePath, modifiedFileName) + : path.resolve(currentDirectory, 'dist', 'cache', modifiedFileName); + const filePath: string = path.resolve(outputDir, fileName); + if (!fs.existsSync(outputDir)) { + mkDir(outputDir); + } + try { + if (!isInit && fs.existsSync(filePath)) { + const existingContent = fs.readFileSync(filePath, 'utf8'); + const newContent = + existingContent && !existingContent.endsWith('\n') + ? existingContent + '\n' + content + : existingContent + content; + fs.writeFileSync(filePath, newContent, 'utf8'); + } else { + fs.writeFileSync(filePath, content, 'utf8'); + } + } catch (error) { + console.error('文件操作失败:', error); + } +} + +export function debugLog(message?: any, ...optionalParams: any[]): void { + if (!isDebugLog) return; + console.log(message, ...optionalParams); +} + +export function getDumpFileName(state: number, prefix: string, index: number | undefined, suffix: string): string { + return `${state}_${prefix}_${index ?? ''}_${suffix}.sts`; +} diff --git a/arkui-plugins/mirror-replace/common/plugin-context.ts b/arkui-plugins/mirror-replace/common/plugin-context.ts new file mode 100644 index 000000000..3621ff891 --- /dev/null +++ b/arkui-plugins/mirror-replace/common/plugin-context.ts @@ -0,0 +1,103 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +// This is the same plugin-context in the build-system. +export class PluginContext { + private projectConfig: ProjectConfig | undefined; + private contextPtr: number | undefined; + + constructor() { + this.projectConfig = undefined; + this.contextPtr = undefined; + } + + public setProjectConfig(projectConfig: ProjectConfig): void { + this.projectConfig = projectConfig; + } + + public getProjectConfig(): ProjectConfig | undefined { + return this.projectConfig; + } + + public setContextPtr(ptr: number): void { + this.contextPtr = ptr; + } + + public getContextPtr(): number | undefined { + return this.contextPtr; + } +} + +export interface DependentModuleConfig { + packageName: string; + moduleName: string; + moduleType: string; + modulePath: string; + sourceRoots: string[]; + entryFile: string; + language: string, + declFilesPath?: string, + dependencies?: string[] +} + +export interface ProjectConfig { + bundleName: string; + moduleName: string; + cachePath: string; + dependentModuleList: DependentModuleConfig[]; + appResource: string; + rawFileResource: string; + buildLoaderJson: string; + hspResourcesMap: boolean; + compileHar: boolean; + byteCodeHar: boolean; + uiTransformOptimization: boolean; + resetBundleName: boolean; + allowEmptyBundleName: boolean; + moduleType: string; + moduleRootPath: string; + aceModuleJsonPath: string; + ignoreError: boolean; +} + +export type PluginHandlerFunction = () => void; + +export type PluginHandlerObject = { + order: 'pre' | 'post' | undefined; + handler: PluginHandlerFunction; +}; + +export type PluginHandler = PluginHandlerFunction | PluginHandlerObject; + +export interface Plugins { + name: string; + afterNew?: PluginHandler; + parsed?: PluginHandler; + scopeInited?: PluginHandler; + checked?: PluginHandler; + lowered?: PluginHandler; + asmGenerated?: PluginHandler; + binGenerated?: PluginHandler; + clean?: PluginHandler; +} + +export type PluginState = keyof Omit; + +export type PluginExecutor = { + name: string; + handler: PluginHandlerFunction; +}; diff --git a/arkui-plugins/mirror-replace/common/predefines.ts b/arkui-plugins/mirror-replace/common/predefines.ts new file mode 100644 index 000000000..6e51e18f7 --- /dev/null +++ b/arkui-plugins/mirror-replace/common/predefines.ts @@ -0,0 +1,251 @@ +/* + * 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. + */ + +export const EXTERNAL_SOURCE_PREFIX_NAMES: (string | RegExp)[] = [ + 'std', + 'escompat', + 'security', + 'application', + 'permissions', + 'bundleManager', + 'commonEvent', + /@arkts\..*/, + /@ohos\.(?!arkui).*/, + /@system\..*/, + /arkui\.(?!Ark|[Uu]serView$)[A-Z]/, // temporary solution + /ability\..*/, +]; + +export const ARKUI_IMPORT_PREFIX_NAMES: (string | RegExp)[] = [/arkui\..*/, /@ohos\..*/, /@kit\..*/]; + +export const MEMO_IMPORT_SOURCE_NAME: string = 'arkui.stateManagement.runtime'; +export const CUSTOM_COMPONENT_IMPORT_SOURCE_NAME: string = 'arkui.component.customComponent'; +export const ENTRY_POINT_IMPORT_SOURCE_NAME: string = 'arkui.UserView'; +export const ARKUI_COMPONENT_COMMON_SOURCE_NAME: string = 'arkui.component.common'; + +export enum ModuleType { + HAR = 'har', + ENTRY = 'entry', + FEATURE = 'feature', + SHARED = 'shared', +} + +export enum DefaultConfiguration { + HAR_DEFAULT_MODULE_NAME = '__harDefaultModuleName__', + HAR_DEFAULT_BUNDLE_NAME = '__harDefaultBundleName__', + DYNAMIC_MODULE_NAME = '__MODULE_NAME__', + DYNAMIC_BUNDLE_NAME = '__BUNDLE_NAME__', +} + +export enum LogType { + ERROR = 'ERROR', + WARN = 'WARN', +} + +export enum Dollars { + DOLLAR_RESOURCE = '$r', + DOLLAR_RAWFILE = '$rawfile', + DOLLAR_DOLLAR = '$$', + TRANSFORM_DOLLAR_RESOURCE = '_r', + TRANSFORM_DOLLAR_RAWFILE = '_rawfile', +} + +export enum BindableDecl { + BINDABLE = 'Bindable', +} + +export enum StructDecoratorNames { + ENTRY = 'Entry', + COMPONENT = 'Component', + COMPONENT_V2 = 'ComponentV2', + RESUABLE = 'Reusable', + RESUABLE_V2 = 'ReusableV2', + CUSTOM_LAYOUT = 'CustomLayout', + CUSTOMDIALOG = 'CustomDialog', +} + +export enum DecoratorNames { + STATE = 'State', + STORAGE_LINK = 'StorageLink', + STORAGE_PROP = 'StorageProp', + LINK = 'Link', + PROP = 'Prop', + PROVIDE = 'Provide', + CONSUME = 'Consume', + OBJECT_LINK = 'ObjectLink', + OBSERVED = 'Observed', + WATCH = 'Watch', + BUILDER_PARAM = 'BuilderParam', + BUILDER = 'Builder', + CUSTOM_DIALOG = 'CustomDialog', + LOCAL_STORAGE_PROP = 'LocalStorageProp', + LOCAL_STORAGE_LINK = 'LocalStorageLink', + REUSABLE = 'Reusable', + TRACK = 'Track', + JSONSTRINGIFYIGNORE = 'JSONStringifyIgnore', + JSONRENAME = 'JSONRename', + ANIMATABLE_EXTEND = 'AnimatableExtend' +} + +export enum DecoratorIntrinsicNames { + LINK = '__Link_intrinsic', +} + +export enum StateManagementTypes { + STATE_MANAGEMENT_FACTORY = 'STATE_MGMT_FACTORY', + STATE_DECORATED = 'IStateDecoratedVariable', + LINK_DECORATED = 'ILinkDecoratedVariable', + LINK_SOURCE_TYPE = 'LinkSourceType', + STORAGE_LINK_DECORATED = 'IStorageLinkDecoratedVariable', + STORAGE_PROP_DECORATED = 'IStoragePropDecoratedVariable', + PROP_DECORATED = 'IPropDecoratedVariable', + MUTABLE_STATE = 'MutableState', + SYNCED_PROPERTY = 'SyncedProperty', + PROVIDE_DECORATED = 'IProvideDecoratedVariable', + CONSUME_DECORATED = 'IConsumeDecoratedVariable', + OBJECT_LINK_DECORATED = 'IObjectLinkDecoratedVariable', + MUTABLE_STATE_META = 'IMutableStateMeta', + OBSERVED_OBJECT = 'IObservedObject', + WATCH_ID_TYPE = 'WatchIdType', + RENDER_ID_TYPE = 'RenderIdType', + OBSERVE = 'OBSERVE', + META = '__meta', + SUBSCRIBED_WATCHES = 'ISubscribedWatches', + STORAGE_LINK_STATE = 'StorageLinkState', + OBSERVABLE_PROXY = 'observableProxy', + PROP_STATE = 'propState', + UPDATE = 'update', + MAKE_STATE = 'makeState', + MAKE_LINK = 'makeLink', + MAKE_PROP = 'makeProp', + MAKE_STORAGE_PROP = 'makeStorageProp', + MAKE_STORAGE_LINK = 'makeStorageLink', + MAKE_PROVIDE = 'makeProvide', + MAKE_CONSUME = 'makeConsume', + MAKE_OBJECT_LINK = 'makeObjectLink', + MAKE_SUBSCRIBED_WATCHES = 'makeSubscribedWatches', + MAKE_MUTABLESTATE_META = 'makeMutableStateMeta', +} + +export enum AnimationNames { + ANIMATABLE_ARITHMETIC = 'AnimatableArithmetic', + CREATE_OR_SET_ANIMATABLEPROPERTY = '__createOrSetAnimatableProperty', + ANIMATION = 'animation', + ANIMATION_START = 'animationStart', + ANIMATION_STOP = 'animationStop', +} + +export const RESOURCE_TYPE: Record = { + color: 10001, + float: 10002, + string: 10003, + plural: 10004, + boolean: 10005, + intarray: 10006, + integer: 10007, + pattern: 10008, + strarray: 10009, + media: 20000, + rawfile: 30000, + symbol: 40000, +}; + +export const DECORATOR_TYPE_MAP = new Map([ + [DecoratorNames.STATE, StateManagementTypes.STATE_DECORATED], + [DecoratorNames.LINK, StateManagementTypes.LINK_SOURCE_TYPE], + [DecoratorNames.PROP, StateManagementTypes.PROP_DECORATED], + [DecoratorNames.STORAGE_LINK, StateManagementTypes.STORAGE_LINK_DECORATED], + [DecoratorNames.STORAGE_PROP, StateManagementTypes.STORAGE_PROP_DECORATED], + [DecoratorNames.LOCAL_STORAGE_PROP, StateManagementTypes.SYNCED_PROPERTY], + [DecoratorNames.LOCAL_STORAGE_LINK, StateManagementTypes.MUTABLE_STATE], + [DecoratorNames.OBJECT_LINK, StateManagementTypes.OBJECT_LINK_DECORATED], + [DecoratorNames.PROVIDE, StateManagementTypes.PROVIDE_DECORATED], + [DecoratorNames.CONSUME, StateManagementTypes.CONSUME_DECORATED], +]); + +export const INTERMEDIATE_IMPORT_SOURCE: Map = new Map([ + [Dollars.DOLLAR_RESOURCE, [Dollars.TRANSFORM_DOLLAR_RESOURCE]], + [Dollars.DOLLAR_RAWFILE, [Dollars.TRANSFORM_DOLLAR_RAWFILE]], + [Dollars.DOLLAR_DOLLAR, [BindableDecl.BINDABLE]], + [DecoratorNames.STATE, [StateManagementTypes.STATE_DECORATED, StateManagementTypes.STATE_MANAGEMENT_FACTORY]], + [DecoratorNames.LINK, [StateManagementTypes.LINK_DECORATED, StateManagementTypes.LINK_SOURCE_TYPE, StateManagementTypes.STATE_MANAGEMENT_FACTORY]], + [DecoratorNames.PROP, [StateManagementTypes.PROP_DECORATED, StateManagementTypes.STATE_MANAGEMENT_FACTORY]], + [DecoratorNames.PROVIDE, [StateManagementTypes.PROVIDE_DECORATED, StateManagementTypes.STATE_MANAGEMENT_FACTORY]], + [DecoratorNames.CONSUME, [StateManagementTypes.CONSUME_DECORATED, StateManagementTypes.STATE_MANAGEMENT_FACTORY]], + [DecoratorNames.STORAGE_PROP, [StateManagementTypes.STORAGE_PROP_DECORATED, StateManagementTypes.STATE_MANAGEMENT_FACTORY]], + [DecoratorNames.STORAGE_LINK, [StateManagementTypes.STORAGE_LINK_DECORATED, StateManagementTypes.STATE_MANAGEMENT_FACTORY]], + [DecoratorNames.OBJECT_LINK, [StateManagementTypes.OBJECT_LINK_DECORATED, StateManagementTypes.STATE_MANAGEMENT_FACTORY]], + [ + DecoratorNames.LOCAL_STORAGE_LINK, + [ + StateManagementTypes.STORAGE_LINK_STATE, + StateManagementTypes.MUTABLE_STATE, + StateManagementTypes.OBSERVABLE_PROXY, + ], + ], + [ + DecoratorNames.LOCAL_STORAGE_PROP, + [ + StateManagementTypes.STORAGE_LINK_STATE, + StateManagementTypes.SYNCED_PROPERTY, + StateManagementTypes.OBSERVABLE_PROXY, + StateManagementTypes.PROP_STATE, + ], + ], + [ + DecoratorNames.OBSERVED, + [ + StateManagementTypes.MUTABLE_STATE_META, + StateManagementTypes.OBSERVED_OBJECT, + StateManagementTypes.WATCH_ID_TYPE, + StateManagementTypes.RENDER_ID_TYPE, + StateManagementTypes.OBSERVE, + StateManagementTypes.SUBSCRIBED_WATCHES, + StateManagementTypes.STATE_MANAGEMENT_FACTORY + ], + ], + [ + DecoratorNames.TRACK, + [ + StateManagementTypes.MUTABLE_STATE_META, + StateManagementTypes.OBSERVED_OBJECT, + StateManagementTypes.WATCH_ID_TYPE, + StateManagementTypes.RENDER_ID_TYPE, + StateManagementTypes.OBSERVE, + StateManagementTypes.SUBSCRIBED_WATCHES, + StateManagementTypes.STATE_MANAGEMENT_FACTORY + ], + ], + [DecoratorNames.ANIMATABLE_EXTEND, [AnimationNames.ANIMATABLE_ARITHMETIC]] +]); + +/** + * @deprecated + */ +export const IMPORT_SOURCE_MAP_V2: Map = new Map([ + [Dollars.TRANSFORM_DOLLAR_RESOURCE, 'arkui.component.resources'], + [Dollars.TRANSFORM_DOLLAR_RAWFILE, 'arkui.component.resources'], + [StateManagementTypes.MUTABLE_STATE, 'arkui.stateManagement.runtime'], + [StateManagementTypes.SYNCED_PROPERTY, 'arkui.stateManagement.runtime'], + [StateManagementTypes.STORAGE_LINK_STATE, 'arkui.stateManagement.runtime'], + [StateManagementTypes.OBSERVABLE_PROXY, 'arkui.stateManagement.runtime'], + [StateManagementTypes.PROP_STATE, 'arkui.stateManagement.runtime'], + [AnimationNames.ANIMATABLE_ARITHMETIC, 'arkui.component.common'] +]); + +export enum GetSetTypes { + GET = 'get', + SET = 'set', +} \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/common/program-visitor.ts b/arkui-plugins/mirror-replace/common/program-visitor.ts new file mode 100644 index 000000000..6838f5f24 --- /dev/null +++ b/arkui-plugins/mirror-replace/common/program-visitor.ts @@ -0,0 +1,302 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { AbstractVisitor, VisitorOptions } from './abstract-visitor'; +import { matchPrefix } from './arkts-utils'; +import { debugDump, getDumpFileName } from './debug'; +import { PluginContext } from './plugin-context'; +import { InteroperAbilityNames } from '../../ui-plugins/interop/predefines'; +// import { LegacyTransformer } from '../ui-plugins/interop/legacy-transformer'; +// import { ComponentTransformer } from '../ui-plugins/component-transformer'; + +export interface ProgramVisitorOptions extends VisitorOptions { + pluginName: string; + state: mirrorArkts.Es2pandaContextState; + visitors: AbstractVisitor[]; + skipPrefixNames: (string | RegExp)[]; + hooks?: ProgramHooks; + pluginContext?: PluginContext; +} + +export interface ProgramHookConfig { + visitors: AbstractVisitor[]; + resetAfter?: mirrorArkts.Es2pandaContextState; +} + +export type ProgramHookLifeCycle = Partial>; + +export interface ProgramHooks { + external?: ProgramHookLifeCycle; + source?: ProgramHookLifeCycle; +} + +function flattenVisitorsInHooks( + programHooks?: ProgramHooks, + resetAfterValue?: mirrorArkts.Es2pandaContextState +): AbstractVisitor[] { + if (!programHooks) return []; + const flatMapInHook = (config: ProgramHookConfig): AbstractVisitor[] => { + if (!resetAfterValue) return []; + if (!config.resetAfter || resetAfterValue !== config.resetAfter) return []; + return config.visitors; + }; + return [ + ...Object.values(programHooks.external || {}).flatMap(flatMapInHook), + ...Object.values(programHooks.source || {}).flatMap(flatMapInHook), + ]; +} + +export interface StructMap { + [key: string]: string; +} + +export class ProgramVisitor extends AbstractVisitor { + private readonly pluginName: string; + private readonly state: mirrorArkts.Es2pandaContextState; + private readonly visitors: AbstractVisitor[]; + private readonly skipPrefixNames: (string | RegExp)[]; + private readonly hooks?: ProgramHooks; + private filenames: Map; + private pluginContext?: PluginContext; + private legacyModuleList: string[] = []; + private legacyStructMap: Map; + + constructor(options: ProgramVisitorOptions) { + super(options); + this.pluginName = options.pluginName; + this.state = options.state; + this.visitors = options.visitors; + this.skipPrefixNames = options.skipPrefixNames ?? []; + this.hooks = options.hooks; + this.filenames = new Map(); + this.pluginContext = options.pluginContext; + this.legacyModuleList = []; + this.legacyStructMap = new Map(); + } + + reset(): void { + super.reset(); + this.filenames = new Map(); + this.legacyStructMap = new Map(); + this.legacyModuleList = []; + } + + private getLegacyModule(): void { + const moduleList = this.pluginContext?.getProjectConfig()?.dependentModuleList; + if (moduleList === undefined) { + return; + } + for (const module of moduleList) { + const language = module.language; + const moduleName = module.moduleName; + if (language !== InteroperAbilityNames.ARKTS_1_1) { + continue; + } + if (!this.legacyStructMap.has(moduleName)) { + this.legacyStructMap.set(moduleName, {}); + this.legacyModuleList.push(moduleName); + } + } + } + + private dumpExternalSource( + script: mirrorArkts.AstNode, + name: string, + cachePath: string | undefined, + prefixName: string, + extensionName: string + ): void { + debugDump( + script.dumpSrc(), + getDumpFileName(this.state, prefixName, undefined, name), + true, + cachePath, + extensionName + ); + } + + // private visitLegacyInExternalSource(currProgram: mirrorArkts.Program, name: string): void { + // if (this.state === mirrorArkts.Es2pandaContextState.ES2PANDA_STATE_PARSED) { + // const structList = this.visitorLegacy(currProgram.ast, currProgram, name); + // const moduleName = name.split('/')[0]; + // const structMap = this.legacyStructMap.get(moduleName)!; + // for (const struct of structList) { + // structMap[struct] = name; + // } + // } + // } + + private visitNonLegacyInExternalSource( + program: mirrorArkts.Program, + currProgram: mirrorArkts.Program, + name: string, + cachePath?: string + ): void { + const extensionName: string = program.fileNameWithExtension; + this.dumpExternalSource(currProgram.ast, name, cachePath, 'ORI', extensionName); + const script = this.visitor(currProgram.ast, currProgram, name); + if (script) { + this.dumpExternalSource(script, name, cachePath, this.pluginName, extensionName); + } + } + + private visitNextProgramInQueue( + queue: mirrorArkts.Program[], + visited: Set, + externalSource: mirrorArkts.ExternalSource + ): void { + const nextProgramArr: mirrorArkts.Program[] = externalSource.programs ?? []; + for (const nextProgram of nextProgramArr) { + this.filenames.set(nextProgram.peer, externalSource.getName()); + if (!visited.has(nextProgram.peer)) { + queue.push(nextProgram); + } + } + } + + private visitExternalSources( + program: mirrorArkts.Program, + programQueue: mirrorArkts.Program[] + ): void { + const visited = new Set(); + const queue: mirrorArkts.Program[] = programQueue; + this.getLegacyModule(); + while (queue.length > 0) { + const currProgram = queue.shift()!; + if (visited.has(currProgram.peer) || currProgram.isASTLowered) { + continue; + } + if (currProgram.peer !== program.peer) { + const name: string = this.filenames.get(currProgram.peer)!; + const cachePath: string | undefined = this.pluginContext?.getProjectConfig()?.cachePath; + if (this.legacyModuleList && matchPrefix(this.legacyModuleList, name)) { + // this.visitLegacyInExternalSource(currProgram, name); + } else { + this.visitNonLegacyInExternalSource(program, currProgram, name, cachePath); + } + } + visited.add(currProgram.peer); + for (const externalSource of mirrorArkts.programGetExternalSources(currProgram)) { + if (matchPrefix(this.skipPrefixNames, externalSource.getName())) { + continue; + } + this.visitNextProgramInQueue(queue, visited, externalSource); + } + } + } + + programVisitor(program: mirrorArkts.Program): mirrorArkts.Program { + this.visitExternalSources(program, [program]); + let programScript = program.ast; + programScript = this.visitor(programScript, program, this.externalSourceName); + const visitorsToReset = flattenVisitorsInHooks(this.hooks, this.state); + visitorsToReset.forEach((visitor) => visitor.reset()); + + return program; + } + + private preVisitor( + hook: ProgramHookLifeCycle | undefined, + node: mirrorArkts.AstNode, + program?: mirrorArkts.Program, + externalSourceName?: string + ): void { + let script: mirrorArkts.ETSModule = node as mirrorArkts.ETSModule; + const preVisitors = hook?.pre?.visitors ?? []; + for (const transformer of preVisitors) { + this.visitTransformer(transformer, script, externalSourceName, program); + if (!this.hooks?.external?.pre?.resetAfter) { + transformer.reset(); + } + } + } + + private postVisitor( + hook: ProgramHookLifeCycle | undefined, + node: mirrorArkts.AstNode, + program?: mirrorArkts.Program, + externalSourceName?: string + ): void { + let script: mirrorArkts.ETSModule = node as mirrorArkts.ETSModule; + const postVisitors = hook?.post?.visitors ?? []; + for (const transformer of postVisitors) { + this.visitTransformer(transformer, script, externalSourceName, program); + if (!this.hooks?.external?.pre?.resetAfter) { + transformer.reset(); + } + } + } + + visitor(node: mirrorArkts.AstNode, program?: mirrorArkts.Program, externalSourceName?: string): mirrorArkts.ETSModule { + let hook: ProgramHookLifeCycle | undefined; + + let script: mirrorArkts.ETSModule = node as mirrorArkts.ETSModule; + let count: number = 0; + const isExternal: boolean = !!externalSourceName; + + // pre-run visitors + hook = isExternal ? this.hooks?.external : this.hooks?.source; + this.preVisitor(hook, node, program, externalSourceName); + + for (const transformer of this.visitors) { + // if (this.legacyStructMap.size > 0 && transformer instanceof ComponentTransformer) { + // transformer.registerMap(this.legacyStructMap); + // } + this.visitTransformer(transformer, script, externalSourceName, program); + transformer.reset(); + mirrorArkts.setAllParents(script); + if (!transformer.isExternal) { + debugDump( + script.dumpSrc(), + getDumpFileName(this.state, this.pluginName, count, transformer.constructor.name), + true, + this.pluginContext?.getProjectConfig()?.cachePath, + program!.fileNameWithExtension + ); + count += 1; + } + } + + // post-run visitors + hook = isExternal ? this.hooks?.external : this.hooks?.source; + this.postVisitor(hook, node, program, externalSourceName); + return script; + } + + // private visitorLegacy(node: mirrorArkts.AstNode, program?: mirrorArkts.Program, externalSourceName?: string): string[] { + // const transformer = new LegacyTransformer(); + // transformer.isExternal = !!externalSourceName; + // transformer.externalSourceName = externalSourceName; + // transformer.program = program; + // transformer.visitor(node); + // const structList = transformer.getList(); + // return structList; + // } + + private visitTransformer( + transformer: AbstractVisitor, + script: mirrorArkts.ETSModule, + externalSourceName?: string, + program?: mirrorArkts.Program + ): mirrorArkts.ETSModule { + transformer.isExternal = !!externalSourceName; + transformer.externalSourceName = externalSourceName; + transformer.program = program; + const newScript = transformer.visitor(script) as mirrorArkts.ETSModule; + program?.ast.setStatements(newScript.statements) + return newScript; + } +} diff --git a/arkui-plugins/mirror-replace/interop-plugins/arkuiImportList.ts b/arkui-plugins/mirror-replace/interop-plugins/arkuiImportList.ts new file mode 100644 index 000000000..ddc19b2bd --- /dev/null +++ b/arkui-plugins/mirror-replace/interop-plugins/arkuiImportList.ts @@ -0,0 +1,176 @@ +/* + * 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. + */ + +export const ARKUI_DECLARE_LIST: Set = new Set([ + 'AbilityComponent', + 'AlphabetIndexer', + 'AnalogClock', + 'Animator', + 'Badge', + 'Blank', + 'Button', + 'Calendar', + 'CalendarPicker', + 'Camera', + 'Canvas', + 'Checkbox', + 'CheckboxGroup', + 'Circle', + 'ColorPicker', + 'ColorPickerDialog', + 'Column', + 'ColumnSplit', + 'ContentSlot', + 'Counter', + 'DataPanel', + 'DatePicker', + 'Divider', + 'EffectComponent', + 'Ellipse', + 'EmbeddedComponent', + 'Flex', + 'FolderStack', + 'FormComponent', + 'FormLink', + 'Gauge', + 'GeometryView', + 'Grid', + 'GridItem', + 'GridContainer', + 'Hyperlink', + 'Image', + 'ImageAnimator', + 'Line', + 'LinearIndicator', + 'List', + 'ListItem', + 'ListItemGroup', + 'LoadingProgress', + 'Marquee', + 'MediaCachedImage', + 'Menu', + 'MenuItem', + 'MenuItemGroup', + 'MovingPhotoView', + 'NavDestination', + 'NavRouter', + 'Navigation', + 'Navigator', + 'NodeContainer', + 'Option', + 'PageTransitionEnter', + 'PageTransitionExit', + 'Panel', + 'Particle', + 'Path', + 'PatternLock', + 'Piece', + 'PlatformView', + 'PluginComponent', + 'Polygon', + 'Polyline', + 'Progress', + 'QRCode', + 'Radio', + 'Rating', + 'Rect', + 'Refresh', + 'RelativeContainer', + 'RemoteWindow', + 'RootScene', + 'Row', + 'RowSplit', + 'RichText', + 'Screen', + 'Scroll', + 'ScrollBar', + 'Search', + 'Section', + 'Select', + 'Shape', + 'Sheet', + 'SideBarContainer', + 'Slider', + 'Span', + 'Stack', + 'Stepper', + 'StepperItem', + 'Swiper', + 'SymbolGlyph', + 'SymbolSpan', + 'TabContent', + 'Tabs', + 'Text', + 'TextPicker', + 'TextClock', + 'TextArea', + 'TextInput', + 'TextTimer', + 'TimePicker', + 'Toggle', + 'Video', + 'Web', + 'WindowScene', + 'WithTheme', + 'XComponent', + 'GridRow', + 'GridCol', + 'WaterFlow', + 'FlowItem', + 'ImageSpan', + 'LocationButton', + 'PasteButton', + 'SaveButton', + 'UIExtensionComponent', + 'IsolatedComponent', + 'RichEditor', + 'Component3D', + 'ContainerSpan', + 'Require', + 'BuilderParam', + 'Local', + 'Param', + 'Once', + 'Event', + 'State', + 'Track', + 'Trace', + 'Prop', + 'Link', + 'ObjectLink', + 'Provide', + 'Provider', + 'Consume', + 'Consumer', + 'StorageProp', + 'StorageLink', + 'Watch', + 'LocalStorageLink', + 'LocalStorageProp', + 'Component', + 'ComponentV2', + 'Entry', + 'Observed', + 'ObservedV2', + 'Preview', + 'CustomDialog', + 'Reusable', + 'Computed', + 'Builder', + 'LocalBuilder', + 'Styles', + 'Extend', + 'AnimatableExtend' +]); \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/interop-plugins/decl_transformer.ts b/arkui-plugins/mirror-replace/interop-plugins/decl_transformer.ts new file mode 100644 index 000000000..1ec5a9b05 --- /dev/null +++ b/arkui-plugins/mirror-replace/interop-plugins/decl_transformer.ts @@ -0,0 +1,137 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { AbstractVisitor } from '../common/abstract-visitor'; +import { ARKUI_DECLARE_LIST } from './arkuiImportList'; +import { debugLog } from '../common/debug'; + +export class DeclTransformer extends AbstractVisitor { + constructor(private options?: interop.DeclTransformerOptions) { + super(); + } + + processComponent(node: mirrorArkts.ETSStructDeclaration): mirrorArkts.ClassDeclaration { + const className = node.definition?.ident?.name; + if (!className) { + throw 'Non Empty className expected for Component'; + } + + let newDec: mirrorArkts.ClassDeclaration = mirrorArkts.factory.createClassDeclaration(node.definition); + + const newDefinition = mirrorArkts.factory.updateClassDefinition( + newDec.definition!, + newDec.definition?.ident, + undefined, + undefined, + newDec.definition?.implements!, + undefined, + undefined, + node.definition?.body, + newDec.definition?.modifiers!, + mirrorArkts.classDefinitionFlags(newDec.definition!) | mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE + ); + + mirrorArkts.factory.updateClassDeclaration(newDec, newDefinition); + newDec.modifierFlags = node.modifierFlags; + return newDec; + } + + visitor(beforeChildren: mirrorArkts.AstNode): mirrorArkts.AstNode { + let astNode: mirrorArkts.AstNode = beforeChildren; + if (astNode instanceof mirrorArkts.ETSModule) { + astNode = this.transformImportDecl(astNode); + } + const node = this.visitEachChild(astNode); + if (mirrorArkts.isETSStructDeclaration(node)) { + debugLog(`[MIRROR]: DeclTransformer:before:flag:${node.definition!.isFromStruct}`); + node.definition!.setFromStructModifier(); + let newnode = this.processComponent(node); + debugLog(`[MIRROR]: DeclTransformer:after:flag:${newnode.definition!.isFromStruct}`); + return newnode; + } + else if (mirrorArkts.isETSImportDeclaration(astNode)) { + return this.updateImportDeclaration(astNode); + } + else if (mirrorArkts.isMethodDefinition(astNode)) { + if (astNode.id?.name === 'build') { + return this.transformMethodDefinition(astNode); + } + return astNode; + } + return node; + } + + transformImportDecl(astNode: mirrorArkts.AstNode): mirrorArkts.AstNode { + if (!(astNode instanceof mirrorArkts.ETSModule)) { + return astNode; + } + let statements = astNode.statements.filter(node => this.isImportDeclarationNeedFilter(node)); + return mirrorArkts.factory.updateETSModule(astNode, statements, astNode.ident, astNode.getNamespaceFlag(), astNode.program); + } + + transformMethodDefinition(node: mirrorArkts.MethodDefinition): mirrorArkts.AstNode { + const func: mirrorArkts.ScriptFunction = node.function!; + const updateFunc = mirrorArkts.factory.updateScriptFunction( + func, + !!func.body && mirrorArkts.isBlockStatement(func.body) + ? mirrorArkts.factory.updateBlockStatement( + func.body, + func.body.statements.filter(() => false) + ) + : undefined, + func.typeParams, func.params, func.returnTypeAnnotation, false, + func?.flags, + func?.modifierFlags, + func.id, + func.annotations + ); + + return mirrorArkts.factory.updateMethodDefinition( + node, + node.kind, + mirrorArkts.factory.updateIdentifier( + node.id!, + node.id?.name! + ), + mirrorArkts.factory.createFunctionExpression(updateFunc.id, updateFunc), + node.modifierFlags, + false, + node.overloads + ); + } + + isImportDeclarationNeedFilter(astNode: mirrorArkts.AstNode): boolean { + if (!mirrorArkts.isETSImportDeclaration(astNode)) { + return true; + } + return astNode?.source?.str !== '@global.arkui'; + } + + updateImportDeclaration(astNode: mirrorArkts.AstNode): mirrorArkts.AstNode { + if (!mirrorArkts.isETSImportDeclaration(astNode) || astNode?.source?.str !== '@ohos.arkui.component') { + return astNode; + } + astNode.specifiers.forEach((element) => { + if (mirrorArkts.isImportSpecifier(element)) { + if (ARKUI_DECLARE_LIST.has(element.imported?.name as string)) { + element.setRemovable(true); + } + } + }); + return astNode; + } +} diff --git a/arkui-plugins/mirror-replace/interop-plugins/emit_transformer.ts b/arkui-plugins/mirror-replace/interop-plugins/emit_transformer.ts new file mode 100644 index 000000000..cd9d18ac9 --- /dev/null +++ b/arkui-plugins/mirror-replace/interop-plugins/emit_transformer.ts @@ -0,0 +1,61 @@ +/* + * 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 * as mirrorArkts from '@koalaui/libarkts-mirror'; + +import { AbstractVisitor } from '../common/abstract-visitor'; + +import { debugLog } from '../common/debug'; + +export class EmitTransformer extends AbstractVisitor { + constructor(private options?: interop.EmitTransformerOptions) { + super(); + } + + processComponent(node: mirrorArkts.ClassDeclaration): mirrorArkts.ClassDeclaration { + const className = node.definition?.ident?.name; + if (!className) { + throw 'Non Empty className expected for Component'; + } + + const newDefinition = mirrorArkts.factory.updateClassDefinition( + node.definition, + node.definition?.ident, + undefined, + undefined, + node.definition?.implements, + undefined, + undefined, + node.definition?.body, + node.definition?.modifiers, + mirrorArkts.classDefinitionFlags(node.definition) | mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE + ); + + let newDec: mirrorArkts.ClassDeclaration = mirrorArkts.factory.updateClassDeclaration(node, newDefinition); + + debugLog(`[MIRROR]: DeclTransformer:checked:struct_ast:${newDefinition.dumpJson()}`); + newDec.modifierFlags = node.modifierFlags; + return newDec; + } + + visitor(beforeChildren: mirrorArkts.AstNode): mirrorArkts.AstNode { + const node = this.visitEachChild(beforeChildren); + if (mirrorArkts.isClassDeclaration(node) && node.definition!.isFromStruct) { + return this.processComponent(node); + } + return node; + } +} diff --git a/arkui-plugins/mirror-replace/interop-plugins/index.ts b/arkui-plugins/mirror-replace/interop-plugins/index.ts new file mode 100644 index 000000000..a54d9eec1 --- /dev/null +++ b/arkui-plugins/mirror-replace/interop-plugins/index.ts @@ -0,0 +1,100 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { DeclTransformer } from './decl_transformer'; +import { EmitTransformer } from './emit_transformer'; + +import { ProgramVisitor } from '../common/program-visitor'; +import { EXTERNAL_SOURCE_PREFIX_NAMES } from '../common/predefines'; +import { debugLog } from '../common/debug'; +import { PluginContext, Plugins } from '../common/plugin-context'; +import { copyGlobalFromKoalaWrapper } from '../common/compat-koala-wrapper' + +export function interopTransform(): Plugins { + return { + name: 'interop-plugin', + parsed: parsedTransform, + checked: checkedTransform, + }; +} + +function parsedTransform(this: PluginContext): mirrorArkts.ETSModule | undefined { + copyGlobalFromKoalaWrapper(this); + + let script: mirrorArkts.ETSModule | undefined; + debugLog('[MIRROR]: interopTransform:parsed'); + const contextPtr = mirrorArkts.arktsGlobal.context; + if (!!contextPtr) { + let program = mirrorArkts.arkts.getOrUpdateGlobalContext(contextPtr).program; + script = program.ast as mirrorArkts.ETSModule; + if (script) { + debugLog(`[MIRROR]: interopTransform: script before parsing: ${script?.dumpSrc()}`) + const declTransformer = new DeclTransformer({ + arkui: '@koalaui.arkts-arkui.StructParse' as interop.TransfromerName + }); + + const programVisitor = new ProgramVisitor({ + pluginName: interopTransform().name, + state: mirrorArkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, + visitors: [declTransformer], + skipPrefixNames: EXTERNAL_SOURCE_PREFIX_NAMES, + pluginContext: this as unknown as PluginContext + }); + program = programVisitor.programVisitor(program); + script = program.ast as mirrorArkts.ETSModule; + debugLog(`[MIRROR]: interopTransform: script after parsing: ${script?.dumpSrc()}`) + debugLog('[MIRROR]: interopTransform: parsed exit'); + return script; + } + } + debugLog('[MIRROR]: interopTransform: parsed exit with no transform'); + return script; +} + +function checkedTransform(this: PluginContext): mirrorArkts.ETSModule | undefined { + copyGlobalFromKoalaWrapper(this); + + let script: mirrorArkts.ETSModule | undefined; + debugLog('[MIRROR]: interopTransform:checked'); + const contextPtr = mirrorArkts.arktsGlobal.context; + if (!!contextPtr) { + let program = mirrorArkts.arkts.getOrUpdateGlobalContext(contextPtr).program; + script = program.ast as mirrorArkts.ETSModule; + if (script) { + debugLog(`[MIRROR]: interopTransform: script before checking: ${script?.dumpSrc()}`) + const emitTransformer = new EmitTransformer({ + arkui: '@koalaui.arkts-arkui.EmitBase' as interop.TransfromerName + }); + + const programVisitor = new ProgramVisitor({ + pluginName: interopTransform().name, + state: mirrorArkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, + visitors: [emitTransformer], + skipPrefixNames: EXTERNAL_SOURCE_PREFIX_NAMES, + pluginContext: this as unknown as PluginContext + }); + program = programVisitor.programVisitor(program); + script = program.ast as mirrorArkts.ETSModule + mirrorArkts.recheckSubtree(script); + debugLog(`[MIRROR]: interopTransform: script after checking: ${script?.dumpSrc()}`) + debugLog('[MIRROR]: interopTransform:checked exit'); + return script; + } + } + debugLog('[MIRROR]: interopTransform:checked exit with no transform'); + return script; +} diff --git a/arkui-plugins/mirror-replace/interop-plugins/types.ts b/arkui-plugins/mirror-replace/interop-plugins/types.ts new file mode 100644 index 000000000..4bb425a4b --- /dev/null +++ b/arkui-plugins/mirror-replace/interop-plugins/types.ts @@ -0,0 +1,56 @@ +/* + * 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. + */ + +namespace interop { + export type NativePointer = number; + + export interface PluginContext { + setArkTSAst(ast: Node): void; + getArkTSAst(): Node | undefined; + setArkTSProgram(program: Node): void; + getArkTSProgram(): Node | undefined; + setProjectConfig(projectConfig: Node): void; + getProjectConfig(): Node | undefined; + } + + export interface ArktsObject { + readonly peer: NativePointer; + } + + export interface Node extends ArktsObject { + get originalPeer(): NativePointer; + set originalPeer(peer: NativePointer); + dumpJson(): string; + dumpSrc(): string; + } + + export interface ETSModule extends Node { } + + export interface Plugin { + name: string; + parsed?(context: PluginContext): ETSModule | undefined; + checked?(context: PluginContext): ETSModule | undefined; + } + + export type TransfromerName = string & { __TransfromerNameBrand: any }; + + export interface EmitTransformerOptions { + arkui: TransfromerName; + } + + export interface DeclTransformerOptions { + arkui: TransfromerName; + } +} diff --git a/arkui-plugins/path.ts b/arkui-plugins/path.ts index d3f82da3f..336b17831 100644 --- a/arkui-plugins/path.ts +++ b/arkui-plugins/path.ts @@ -61,5 +61,5 @@ export function getCompatPath() { } export function getArktsMirrorPath() { - return path.join(findRootDir(), 'koala-wrapper/mirror', './build/arkts-api/index.js'); + return path.join(findRootDir(), 'koala-wrapper/mirror', './build/libarkts.js'); } \ No newline at end of file diff --git a/arkui-plugins/test/demo/localtest/build_decl_config_template.json b/arkui-plugins/test/demo/localtest/build_decl_config_template.json index fc58a0e00..b546d4773 100644 --- a/arkui-plugins/test/demo/localtest/build_decl_config_template.json +++ b/arkui-plugins/test/demo/localtest/build_decl_config_template.json @@ -4,7 +4,7 @@ }, "compileFiles": [ - "./demo/localtest/entry/new.ets" + "./demo/localtest/entry/src/main/ets/pages/new.ets" ], "packageName" : "entry", diff --git a/arkui-plugins/test/mirror/demo/entry/src/main/ets/pages/new.ets b/arkui-plugins/test/mirror/demo/entry/src/main/ets/pages/new.ets new file mode 100755 index 000000000..b3627f723 --- /dev/null +++ b/arkui-plugins/test/mirror/demo/entry/src/main/ets/pages/new.ets @@ -0,0 +1,74 @@ +/* + * 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 { Text, Column, Component, Entry, Button, ClickEvent } from "@ohos.arkui.component" +import { State, Link, Prop } from "@ohos.arkui.stateManagement" +import hilog from '@ohos.hilog' + +@Entry +@Component +export struct MyStateSample { + @State stateVar: string = "state var"; + message: string = `click to change state variable, add **`; + changeValue() { + this.stateVar+="**" + } + build() { + Column() { + Button("clean variable").onClick((e: ClickEvent) => { this.stateVar = "state var" }) + Text("Hello World").fontSize(20) + Button(this.message).backgroundColor("#FFFF00FF") + .onClick((e: ClickEvent) => { + hilog.info(0x0000, 'testTag', 'On Click'); + this.changeValue() + }) + Text(this.stateVar).fontSize(20) + Child({linkVar: this.stateVar, propVar: this.stateVar}) + }.margin(10) + } +} + +@Component +export struct Child { + @Link linkVar: string = ""; // TODO: remove this + @Prop propVar: string = "Prop"; + + changeValue1() { + this.linkVar+="!!" + } + + changeValue2() { + this.propVar+="~~" + } + + build() { + Column() { + Button(`click to change Link variable, add symbol !!`) + .backgroundColor("#4169E1") + .onClick((e: ClickEvent) => { + hilog.info(0x0000, 'testTag', 'On Click'); + this.changeValue1() + }) + Button(`click to change Prop variable, add symbol ~~`) + .backgroundColor("#3CB371") + .onClick((e: ClickEvent) => { + hilog.info(0x0000, 'testTag', 'On Click'); + this.changeValue2() + }) + Text(`Link variable in child: ${this.linkVar}`).fontSize(30) + Text(`Prop variable in child: ${this.propVar}`).fontSize(30) + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/mirror/mirror_decl_config_template.json b/arkui-plugins/test/mirror/mirror_decl_config_template.json new file mode 100644 index 000000000..5beb6168c --- /dev/null +++ b/arkui-plugins/test/mirror/mirror_decl_config_template.json @@ -0,0 +1,29 @@ +{ + "plugins": { + "interop_plugin": "workspace/out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ui-plugins/lib/mirror-replace/interop-plugins/index" + }, + + "compileFiles": [ + "./mirror/demo/entry/src/main/ets/pages/new.ets" + ], + + "packageName" : "entry", + + "buildType": "build", + "buildMode": "Debug", + "moduleRootPath": "./mirror/demo/entry/", + "sourceRoots": ["./"], + + "loaderOutPath": "./dist", + "cachePath": "./dist/cache", + + "buildSdkPath": "workspace/out/sdk/ohos-sdk/linux/ets/ets1.2/", + + "dependentModuleList": [], + + "isIDE": "false", + "enableDeclgenEts2Ts": true, + + "declgenV1OutPath": "workspace/developtools/ace_ets2bundle/arkui-plugins/test/mirror/demo/hello_world/declgenV1OutPath", + "declgenBridgeCodePath": "workspace/developtools/ace_ets2bundle/arkui-plugins/test/mirror/demo/hello_world/declgenBridgeCodePath" +} diff --git a/arkui-plugins/test/mirror_decl_config.js b/arkui-plugins/test/mirror_decl_config.js new file mode 100644 index 000000000..954951f94 --- /dev/null +++ b/arkui-plugins/test/mirror_decl_config.js @@ -0,0 +1,51 @@ +/* + * 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. + */ + +const fs = require('fs'); +const path = require('path'); + +const currentDirectory = process.cwd(); +let workSpace = currentDirectory; +for (let i = 0; i < 4; i++) { + workSpace = path.dirname(workSpace); +} + +const jsonFilePath = path.join(__dirname, 'mirror/mirror_decl_config_template.json'); +const outJsonFilePath = path.join(__dirname, 'mirror/mirror_decl_config.json'); + +try { + const data = fs.readFileSync(jsonFilePath, 'utf8'); + const jsonData = JSON.parse(data); + + if (jsonData.buildSdkPath) { + jsonData.buildSdkPath = jsonData.buildSdkPath.replace(/workspace/g, workSpace); + } + + if (jsonData.plugins.interop_plugin) { + jsonData.plugins.interop_plugin = jsonData.plugins.interop_plugin.replace(/workspace/g, workSpace); + } + + if (jsonData.declgenV1OutPath) { + jsonData.declgenV1OutPath = jsonData.declgenV1OutPath.replace(/workspace/g, workSpace); + } + + if (jsonData.declgenBridgeCodePath) { + jsonData.declgenBridgeCodePath = jsonData.declgenBridgeCodePath.replace(/workspace/g, workSpace); + } + + fs.writeFileSync(outJsonFilePath, JSON.stringify(jsonData, null, 2), 'utf8'); +} catch (error) { + console.error('writeFile error:', error); +} diff --git a/arkui-plugins/test/package.json b/arkui-plugins/test/package.json index 46947c865..abdbcd3e9 100644 --- a/arkui-plugins/test/package.json +++ b/arkui-plugins/test/package.json @@ -20,9 +20,10 @@ "test:gdb": "npm run clean:all && npm run compile:plugins && cd .. && npm run test:gdb", "localtest": "rm -rf dist && node localtest_config.js && npm run compile:plugins && LD_LIBRARY_PATH=$INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/lib node $INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/driver/build-system/dist/entry.js ./demo/localtest/build_config.json", "localtest_gdb": "LD_LIBRARY_PATH=$INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/lib gdb --args node $INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/driver/build-system/dist/entry.js ./demo/localtest/build_config.json", - "localtest_decl": "rm -rf dist && node localtest_decl_config.js && npm run compile:plugins && LD_LIBRARY_PATH=$INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/lib node $INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/driver/build-system/dist/entry.js ./demo/localtest/build_decl_config.json", + "localtest_decl": "rm -rf demo/hello_world/ && node localtest_decl_config.js && npm run compile:plugins && LD_LIBRARY_PATH=$INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/lib node $INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/driver/build-system/dist/entry.js ./demo/localtest/build_decl_config.json", "localtest_all": "npm run localtest_decl && npm run localtest", "es2panda:compile": "node ./arktsconfig_gen.js && $INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/bin/es2panda --arktsconfig=./dist/cache/arktsconfig.json", - "es2panda:test": "$INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/bin/es2panda ./demo/localtest/entry/test.ets --output=test.abc" + "es2panda:test": "$INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/bin/es2panda ./demo/localtest/entry/test.ets --output=test.abc", + "mirrortest:decl": "rm -rf mirror/demo/hello_world/ && node mirror_decl_config.js && npm run compile:plugins && LD_LIBRARY_PATH=$INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/lib node $INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/driver/build-system/dist/entry.js ./mirror/mirror_decl_config.json" } } diff --git a/koala-wrapper/BUILD.gn b/koala-wrapper/BUILD.gn index 6f56b0623..c7363952f 100644 --- a/koala-wrapper/BUILD.gn +++ b/koala-wrapper/BUILD.gn @@ -20,7 +20,7 @@ mirror_ui2abc_dir = "//foundation/arkui/ace_engine/frameworks/bridge/arkts_front action("gen_sdk_ts_wrapper") { script = "build_ts_wrapper.py" deps = [ "./native:es2panda" ] - external_deps = [ "ace_engine:es2panda" ] + external_deps = [ "ace_engine:libarkts" ] args = [ "--source_path", rebase_path(get_path_info(".", "abspath")), diff --git a/koala-wrapper/build_ts_wrapper.py b/koala-wrapper/build_ts_wrapper.py index fb98cf6b8..b9bbcd605 100755 --- a/koala-wrapper/build_ts_wrapper.py +++ b/koala-wrapper/build_ts_wrapper.py @@ -18,28 +18,15 @@ import os import shutil import subprocess import sys -from pathlib import Path -import re - -def process_mirror_output(root_path): - root = Path(root_path) - js_files = [file for file in root.rglob('*.js') if file.is_file()] - for file in js_files: - content = file.read_text(encoding='utf-8') - new_content = re.sub(r'require\("@koalaui/(\w+?)"\)', r'require("#koalaui/\1")', content) - new_content = re.sub(r'require\("@common/(\w+?)"\)', r'require("#common/\1")', new_content) - new_content = re.sub(r'require\("@platform/(\w+?)"\)', r'require("#platform/\1")', new_content) - if new_content != content: - file.write_text(new_content, encoding='utf-8') - -def copy_files(source_path, dest_path, is_file=False, ignore_func=None): + +def copy_files(source_path, dest_path, is_file=False): try: if is_file: os.makedirs(os.path.dirname(dest_path), exist_ok=True) shutil.copy(source_path, dest_path) else: shutil.copytree(source_path, dest_path, dirs_exist_ok=True, - symlinks=True, ignore=ignore_func) + symlinks=True) except Exception as err: raise Exception("Copy files failed. Error: " + str(err)) from err @@ -61,26 +48,9 @@ def build(options): def copy_output(options): run_cmd(['rm', '-rf', os.path.join(options.source_path, 'mirror/build')]) - # only keep .d.ts files - def copy_to_source_ignore_func(dirname, filenames): - return [f for f in filenames - if os.path.isfile(os.path.join(dirname, f)) and not f.endswith('.d.ts')] - - copy_files(os.path.join(options.mirror_ui2abc_dir, 'libarkts/build'), - os.path.join(options.source_path, 'mirror/build'), - ignore_func=copy_to_source_ignore_func) - - copy_files(os.path.join(options.mirror_ui2abc_dir, '../incremental/common/build'), - os.path.join(options.source_path, 'mirror/build/koalaui/common/dist'), - ignore_func=copy_to_source_ignore_func) - - copy_files(os.path.join(options.mirror_ui2abc_dir, '../incremental/compat/build'), - os.path.join(options.source_path, 'mirror/build/koalaui/compat/dist'), - ignore_func=copy_to_source_ignore_func) - - copy_files(os.path.join(options.mirror_ui2abc_dir, '../interop/build/'), - os.path.join(options.source_path, 'mirror/build/koalaui/interop/dist'), - ignore_func=copy_to_source_ignore_func) + + copy_files(os.path.join(options.mirror_ui2abc_dir, 'libarkts/lib/build'), + os.path.join(options.source_path, 'mirror/build')) run_cmd(['rm', '-rf', options.output_path]) copy_files(os.path.join(options.source_path, 'build/lib'), @@ -92,33 +62,9 @@ def copy_output(options): copy_files(os.path.join(options.source_path, 'package.json'), os.path.join(options.output_path, 'package.json'), True) - # only keep JS files - def copy_to_out_ignore_func(dirname, filenames): - return [f for f in filenames - if os.path.isfile(os.path.join(dirname, f)) and not f.endswith('.js')] - - copy_files(os.path.join(options.mirror_ui2abc_dir, 'libarkts/build'), - os.path.join(options.output_path, 'mirror/build'), - ignore_func=copy_to_out_ignore_func) - - copy_files(os.path.join(options.mirror_ui2abc_dir, '../incremental/common/build'), - os.path.join(options.output_path, 'mirror/build/koalaui/common/dist'), - ignore_func=copy_to_out_ignore_func) - - copy_files(os.path.join(options.mirror_ui2abc_dir, '../incremental/compat/build'), - os.path.join(options.output_path, 'mirror/build/koalaui/compat/dist'), - ignore_func=copy_to_out_ignore_func) - - copy_files(os.path.join(options.mirror_ui2abc_dir, '../interop/build/'), - os.path.join(options.output_path, 'mirror/build/koalaui/interop/dist'), - ignore_func=copy_to_out_ignore_func) - - copy_files(os.path.join(options.source_path, 'mirror/package.json'), - os.path.join(options.output_path, 'mirror/package.json'), True) - - # replace package imports with alias imports - process_mirror_output(os.path.join(options.output_path, 'mirror/build')) - + copy_files(os.path.join(options.mirror_ui2abc_dir, 'libarkts/lib/libarkts.js'), + os.path.join(options.output_path, 'mirror/build/libarkts.js'), True) + if options.current_os == "mingw" : copy_files(os.path.join(options.root_out_dir, 'libes2panda.dll'), os.path.join(options.output_path, 'build/native/es2panda.node'), True) diff --git a/koala-wrapper/mirror/package.json b/koala-wrapper/mirror/package.json index 2942a72a8..06ca6f774 100644 --- a/koala-wrapper/mirror/package.json +++ b/koala-wrapper/mirror/package.json @@ -3,54 +3,8 @@ "version": "1.0.0", "private": true, "main": "./build/index.js", - "types": "./build/arkts-api/index.d.ts", + "types": "./build/index.d.ts", "exports": { - ".": "./build/arkts-api/index.js", - "./build/lib/es2panda": "./build/index.js" - }, - "files": [ - "./build/*" - ], - "config": { - "gen_version": "3.0.19" - }, - "devDependencies": { - "@babel/cli": "7.20.7", - "@babel/core": "7.20.12", - "@babel/plugin-proposal-class-properties": "7.18.6", - "@babel/preset-env": "7.20.2", - "@babel/preset-typescript": "7.18.6", - "@babel/runtime": "7.20.13", - "@tsconfig/recommended": "1.0.8", - "node-addon-api": "^8.3.0", - "typescript": "^5.0.0", - "@types/node": "^18.0.0" - }, - "imports": { - "#koalaui/interop": { - "default": "./build/koalaui/interop/dist/lib/src/interop/index.js" - }, - "#koalaui/common": { - "default": "./build/koalaui/common/dist/lib/src/index.js" - }, - "#koalaui/compat": { - "default": "./build/koalaui/compat/dist/src/index.js" - }, - "#common/wrappers": { - "browser": "./build/koalaui/interop/dist/lib/src/wasm/wrappers/index.js", - "node": "./build/koalaui/interop/dist/lib/src/napi/wrappers/index.js" - }, - "#common/wrappers/*": { - "browser": "./build/koalaui/interop/dist/lib/src/wasm/wrappers/*.js", - "node": "./build/koalaui/interop/dist/lib/src/napi/wrappers/*.js", - "default": "./build/koalaui/interop/dist/lib/src/napi/wrappers/*.js" - }, - "#platform": { - "ark": "./build/koalaui/compat/dist/src/ohos/index.js", - "ios": "./build/koalaui/compat/dist/src/typescript/index.js", - "browser": "./build/koalaui/compat/dist/src/typescript/index.js", - "node": "./build/koalaui/compat/dist/src/typescript/index.js", - "default": "./build/koalaui/compat/dist/src/typescript/index.js" - } + ".": "./build/index.js" } } diff --git a/koala-wrapper/src/Es2pandaEnums.ts b/koala-wrapper/src/Es2pandaEnums.ts index 9bb06c7d6..7c287f65a 100644 --- a/koala-wrapper/src/Es2pandaEnums.ts +++ b/koala-wrapper/src/Es2pandaEnums.ts @@ -174,7 +174,7 @@ export enum Es2pandaAstNodeType { AST_NODE_TYPE_YIELD_EXPRESSION, AST_NODE_TYPE_OPAQUE_TYPE_NODE, AST_NODE_TYPE_BLOCK_EXPRESSION, - AST_NODE_TYPE_ERROR_TYPE_NODE, + AST_NODE_TYPE_BROKEN_TYPE_NODE, AST_NODE_TYPE_ARRAY_EXPRESSION, AST_NODE_TYPE_ARRAY_PATTERN, AST_NODE_TYPE_ASSIGNMENT_EXPRESSION, -- Gitee From 51164ef5a49c41662dd5a00f2038b9a10e8d3437 Mon Sep 17 00:00:00 2001 From: xieziang Date: Mon, 7 Jul 2025 17:34:31 +0800 Subject: [PATCH 3/5] add mirror test Signed-off-by: xieziang Change-Id: I2d18f910823d62122039d0bce7f994bb0d578b0c --- arkui-plugins/BUILD.gn | 22 +++++++++- arkui-plugins/test_koala_mirror.py | 70 ++++++++++++++++++++++++++++++ koala-wrapper/build_ts_wrapper.py | 10 ++--- 3 files changed, 96 insertions(+), 6 deletions(-) create mode 100755 arkui-plugins/test_koala_mirror.py diff --git a/arkui-plugins/BUILD.gn b/arkui-plugins/BUILD.gn index 707355e1d..d2b3f2f26 100755 --- a/arkui-plugins/BUILD.gn +++ b/arkui-plugins/BUILD.gn @@ -50,8 +50,28 @@ action("build_ets_sysResource") { ] } +action("test_koala_mirror") { + deps = [ ":gen_ui_plugins" ] + script = "test_koala_mirror.py" + outputs = [ "$target_out_dir/test_koala_mirror" ] + args = [ + "--test_path", + rebase_path(get_path_info("./test", "abspath")), + "--npm", + rebase_path(npm_path), + ] +} + ohos_copy("ui_plugin") { - deps = [":gen_ui_plugins", ":build_ets_sysResource" ] + deps = [ + ":gen_ui_plugins", + ":build_ets_sysResource" + ] + + if (current_os == "linux") { + deps += [":test_koala_mirror(//build/toolchain/linux:clang_x64)"] + } + sources = [ rebase_path("$target_gen_dir") ] outputs = [ target_out_dir + "/$target_name" ] module_source_dir = target_out_dir + "/$target_name" diff --git a/arkui-plugins/test_koala_mirror.py b/arkui-plugins/test_koala_mirror.py new file mode 100755 index 000000000..31ff9be73 --- /dev/null +++ b/arkui-plugins/test_koala_mirror.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# 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 subprocess +import argparse +import sys + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('--npm', help='path to a npm exetuable') + parser.add_argument('--test_path', help='current_os') + + options = parser.parse_args() + return options + +def run_cmd(cmd, execution_path=None): + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=execution_path) + stdout, stderr = proc.communicate(timeout=1000) + if proc.returncode != 0: + raise Exception(stderr.decode()) + +EXPECTED_DECL_RESULT=""" +import { State, Link, Prop } from "@ohos.arkui.stateManagement"; +@Component +export declare struct MyStateSample { + @State + stateVar: string; + message: string; + public changeValue(): void; + public build(): void; +} +@Component +export declare struct Child { + @Link + linkVar: string; + @Prop + propVar: string; + public changeValue1(): void; + public changeValue2(): void; + public build(): void; +} +""" + +def main(): + options = parse_args() + run_cmd([options.npm, 'run', 'mirrortest:decl'], options.test_path) + decl_test_file_path = f"{options.test_path}/mirror/demo/hello_world/declgenV1OutPath/entry/src/main/ets/pages/new.d.ets" + with open(decl_test_file_path, 'r', encoding='utf-8') as f: + content = f.read() + print(content) + if content.strip() != EXPECTED_DECL_RESULT.strip(): + raise Exception("Error: failed to pass koala-mirror test") + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file diff --git a/koala-wrapper/build_ts_wrapper.py b/koala-wrapper/build_ts_wrapper.py index b9bbcd605..649a5a48b 100755 --- a/koala-wrapper/build_ts_wrapper.py +++ b/koala-wrapper/build_ts_wrapper.py @@ -47,11 +47,6 @@ def build(options): def copy_output(options): - run_cmd(['rm', '-rf', os.path.join(options.source_path, 'mirror/build')]) - - copy_files(os.path.join(options.mirror_ui2abc_dir, 'libarkts/lib/build'), - os.path.join(options.source_path, 'mirror/build')) - run_cmd(['rm', '-rf', options.output_path]) copy_files(os.path.join(options.source_path, 'build/lib'), os.path.join(options.output_path, 'build/lib')) @@ -73,6 +68,11 @@ def copy_output(options): os.path.join(options.output_path, 'mirror/build/native/build/es2panda.node'), True) if options.current_os == "linux" or options.current_os == "mac" : + run_cmd(['rm', '-rf', os.path.join(options.source_path, 'mirror/build')]) + + copy_files(os.path.join(options.mirror_ui2abc_dir, 'libarkts/lib/build'), + os.path.join(options.source_path, 'mirror/build')) + copy_files(os.path.join(options.root_out_dir, 'libes2panda.node'), os.path.join(options.output_path, 'build/native/es2panda.node'), True) -- Gitee From 4722ce3a2d892a5a0759a626ca2f30c07a8f61aa Mon Sep 17 00:00:00 2001 From: xieziang Date: Tue, 8 Jul 2025 22:03:17 +0800 Subject: [PATCH 4/5] update ui-plugin Signed-off-by: xieziang Change-Id: I692bb8a11dac166359afbe38fec06bf48016b2c5 --- arkui-plugins/.gitignore | 1 + arkui-plugins/BUILD.gn | 2 +- .../collectors/memo-collectors/utils.ts | 841 ++++++++++++++++++ .../mirror-replace/common/arkts-utils.ts | 96 ++ .../common/compat-koala-wrapper.ts | 4 +- .../common/declaration-collector.ts | 86 ++ .../mirror-replace/common/import-collector.ts | 105 +++ .../mirror-replace/common/plugin-context.ts | 4 + .../mirror-replace/common/predefines.ts | 26 + .../mirror-replace/common/program-visitor.ts | 7 +- .../mirror-replace/common/safe-types.ts | 43 + .../interop-plugins/decl_transformer.ts | 2 +- .../mirror-replace/interop-plugins/index.ts | 1 + .../ui-plugins/component-transformer.ts | 634 +++++++++++++ .../mirror-replace/ui-plugins/customdialog.ts | 471 ++++++++++ .../ui-plugins/entry-translators/factory.ts | 479 ++++++++++ .../ui-plugins/entry-translators/utils.ts | 95 ++ .../mirror-replace/ui-plugins/index.ts | 173 ++++ .../ui-plugins/interop/interop.ts | 459 ++++++++++ .../property-translators/factory.ts | 799 +++++++++++++++++ .../ui-plugins/property-translators/utils.ts | 443 +++++++++ .../mirror-replace/ui-plugins/ui-factory.ts | 394 ++++++++ .../mirror-replace/ui-plugins/utils.ts | 291 ++++++ .../entry/src/main/ets/pages/new.ets | 6 +- .../demo/entry/src/main/ets/pages/new.ets | 2 +- .../mirror/mirror_test_config_template.json | 43 + arkui-plugins/test/mirror_test_config.js | 93 ++ arkui-plugins/test/package.json | 3 +- arkui-plugins/tsconfig.json | 3 +- .../ui-plugins/component-transformer.ts | 8 +- .../ui-plugins/entry-translators/factory.ts | 2 + 31 files changed, 5601 insertions(+), 15 deletions(-) create mode 100644 arkui-plugins/mirror-replace/collectors/memo-collectors/utils.ts create mode 100644 arkui-plugins/mirror-replace/common/declaration-collector.ts create mode 100644 arkui-plugins/mirror-replace/common/import-collector.ts create mode 100644 arkui-plugins/mirror-replace/common/safe-types.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/component-transformer.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/customdialog.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/entry-translators/factory.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/entry-translators/utils.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/index.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/interop/interop.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/factory.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/utils.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/ui-factory.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/utils.ts create mode 100755 arkui-plugins/test/mirror/mirror_test_config_template.json create mode 100644 arkui-plugins/test/mirror_test_config.js diff --git a/arkui-plugins/.gitignore b/arkui-plugins/.gitignore index 7a115dac7..ce50ce324 100644 --- a/arkui-plugins/.gitignore +++ b/arkui-plugins/.gitignore @@ -21,5 +21,6 @@ test/demo/localtest/build_config.json test/demo/localtest/build_decl_config.json test/demo/hello_world +test/mirror/mirror_test_config.json test/mirror/mirror_decl_config.json test/mirror/demo/hello_world diff --git a/arkui-plugins/BUILD.gn b/arkui-plugins/BUILD.gn index d2b3f2f26..59a1037cb 100755 --- a/arkui-plugins/BUILD.gn +++ b/arkui-plugins/BUILD.gn @@ -69,7 +69,7 @@ ohos_copy("ui_plugin") { ] if (current_os == "linux") { - deps += [":test_koala_mirror(//build/toolchain/linux:clang_x64)"] + # deps += [":test_koala_mirror(//build/toolchain/linux:clang_x64)"] } sources = [ rebase_path("$target_gen_dir") ] diff --git a/arkui-plugins/mirror-replace/collectors/memo-collectors/utils.ts b/arkui-plugins/mirror-replace/collectors/memo-collectors/utils.ts new file mode 100644 index 000000000..28a69320a --- /dev/null +++ b/arkui-plugins/mirror-replace/collectors/memo-collectors/utils.ts @@ -0,0 +1,841 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +// import { annotation, forEachArgWithParam, isDecoratorAnnotation } from '../../common/arkts-utils'; +// import { ImportCollector } from '../../common/import-collector'; +// import { DecoratorNames, GenSymPrefix, MEMO_IMPORT_SOURCE_NAME } from '../../common/predefines'; +// import { MemoFunctionCollector } from './function-collector'; + +export enum MemoNames { + MEMO = 'memo', + MEMO_SKIP = 'memo_skip', + MEMO_INTRINSIC = 'memo_intrinsic', + MEMO_ENTRY = 'memo_entry', +} + +// export type MemoAstNode = +// | mirrorArkts.ScriptFunction +// | mirrorArkts.ETSParameterExpression +// | mirrorArkts.ClassProperty +// | mirrorArkts.TSTypeAliasDeclaration +// | mirrorArkts.ETSFunctionType +// | mirrorArkts.ArrowFunctionExpression +// | mirrorArkts.ETSUnionType +// | mirrorArkts.VariableDeclaration; + +// interface MemoableAnnotationInfo { +// hasMemo?: boolean; +// hasMemoSkip?: boolean; +// hasMemoIntrinsic?: boolean; +// hasMemoEntry?: boolean; +// hasBuilder?: boolean; +// hasBuilderParam?: boolean; +// } + +// export type MemoableInfo = MemoableAnnotationInfo & { +// hasProperType?: boolean; +// }; + +// export function isMemoAnnotation(node: mirrorArkts.AnnotationUsage, memoName: MemoNames): boolean { +// return node.expr !== undefined && mirrorArkts.isIdentifier(node.expr) && node.expr.name === memoName; +// } + +// export function hasMemoAnnotation(node: T): boolean { +// return node.annotations.some((it) => isMemoAnnotation(it, MemoNames.MEMO)); +// } + +export function addMemoAnnotation(node: T, memoName: MemoNames = MemoNames.MEMO): T { + collectMemoAnnotationSource(memoName); + if (mirrorArkts.isETSUnionType(node)) { + return mirrorArkts.factory.updateUnionType( + node, + node.types.map((type) => { + if (mirrorArkts.isETSFunctionType(type)) { + return addMemoAnnotation(type, memoName); + } + return type; + }) + ) as T; + } + const newAnnotations: mirrorArkts.AnnotationUsage[] = [ + ...node.annotations.filter((it) => !isMemoAnnotation(it, memoName)), + annotation(memoName), + ]; + collectMemoAnnotationImport(memoName); + if (mirrorArkts.isEtsParameterExpression(node)) { + node.annotations = newAnnotations; + mirrorArkts.NodeCache.getInstance().collect(node); + return node; + } + const newNode = node.setAnnotations(newAnnotations) as T; + mirrorArkts.NodeCache.getInstance().collect(newNode); + return newNode; +} + +// export function hasMemoableAnnotation(node: T): MemoableAnnotationInfo { +// let hasBuilder: boolean = false; +// let hasBuilderParam: boolean = false; +// let hasMemo: boolean = false; +// let hasMemoSkip: boolean = false; +// let hasMemoIntrinsic: boolean = false; +// let hasMemoEntry: boolean = false; +// node.annotations.forEach((it) => { +// hasBuilder ||= isDecoratorAnnotation(it, DecoratorNames.BUILDER); +// hasBuilderParam ||= isDecoratorAnnotation(it, DecoratorNames.BUILDER_PARAM); +// hasMemo ||= isMemoAnnotation(it, MemoNames.MEMO); +// hasMemoSkip ||= isMemoAnnotation(it, MemoNames.MEMO_SKIP); +// hasMemoIntrinsic ||= isMemoAnnotation(it, MemoNames.MEMO_INTRINSIC); +// hasMemoEntry ||= isMemoAnnotation(it, MemoNames.MEMO_ENTRY); +// }); +// return { +// ...(hasMemo ? { hasMemo } : {}), +// ...(hasMemoSkip ? { hasMemoSkip } : {}), +// ...(hasMemoIntrinsic ? { hasMemoIntrinsic } : {}), +// ...(hasMemoEntry ? { hasMemoEntry } : {}), +// ...(hasBuilder ? { hasBuilder } : {}), +// ...(hasBuilderParam ? { hasBuilderParam } : {}), +// }; +// } + +// 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: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { +// let currInfo = info ?? {}; +// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// return { ...currInfo, hasMemo: true, hasProperType: true }; +// } +// if (!mirrorArkts.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: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { +// let currInfo = info ?? {}; +// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// return { ...currInfo, hasMemo: true, hasProperType: true }; +// } +// if (!mirrorArkts.isETSTypeReference(node) || !node.part || !mirrorArkts.isETSTypeReferencePart(node.part)) { +// return currInfo; +// } +// const expr = node.part.name; +// let decl: mirrorArkts.AstNode | undefined; +// if (!expr || !(decl = mirrorArkts.getDecl(expr))) { +// return currInfo; +// } +// return { +// ...currInfo, +// ...collectMemoableInfoInTypeAlias(decl), +// }; +// } + +// export function collectMemoableInfoInFunctionType(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { +// let currInfo = info ?? {}; +// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// return { ...currInfo, hasMemo: true, hasProperType: true }; +// } +// if (!mirrorArkts.isETSFunctionType(node)) { +// return currInfo; +// } +// currInfo.hasProperType = true; +// currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; +// return currInfo; +// } + +// export function collectMemoableInfoInTypeAlias(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { +// let currInfo = info ?? {}; +// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// return { ...currInfo, hasMemo: true, hasProperType: true }; +// } +// if (!mirrorArkts.isTSTypeAliasDeclaration(node)) { +// return currInfo; +// } +// currInfo = { +// ...currInfo, +// ...hasMemoableAnnotation(node), +// }; +// if (!!node.typeAnnotation) { +// return { +// ...currInfo, +// ...collectMemoableInfoInType(node.typeAnnotation), +// }; +// } +// return currInfo; +// } + +// export function collectMemoableInfoInParameter(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { +// let currInfo = info ?? {}; +// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// return { ...currInfo, hasMemo: true, hasProperType: true }; +// } +// if (!mirrorArkts.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: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { +// let currInfo = info ?? {}; +// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// return { ...currInfo, hasMemo: true, hasProperType: true }; +// } +// if (!mirrorArkts.isVariableDeclarator(node)) { +// return currInfo; +// } +// if (!!node.name.typeAnnotation) { +// currInfo = { +// ...currInfo, +// ...collectMemoableInfoInType(node.name.typeAnnotation), +// }; +// } +// if (!!node.initializer && mirrorArkts.isArrowFunctionExpression(node.initializer)) { +// currInfo = { +// ...currInfo, +// ...collectMemoableInfoInArrowFunction(node.initializer), +// }; +// } +// if (!!node.parent && mirrorArkts.isVariableDeclaration(node.parent)) { +// currInfo = { +// ...currInfo, +// ...hasMemoableAnnotation(node.parent), +// }; +// } +// const decl = mirrorArkts.getDecl(node.name); +// if (!decl) { +// return currInfo; +// } +// if (mirrorArkts.isMethodDefinition(decl)) { +// currInfo = { +// ...currInfo, +// ...collectMemoableInfoInScriptFunction(decl.scriptFunction), +// }; +// } else if (mirrorArkts.isClassProperty(decl)) { +// currInfo = { +// ...currInfo, +// ...collectMemoableInfoInClassProperty(decl), +// }; +// } +// return currInfo; +// } + +// export function collectMemoableInfoInProperty(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { +// let currInfo = info ?? {}; +// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// const property = node as mirrorArkts.Property; +// const hasProperType = !!property.value && mirrorArkts.isArrowFunctionExpression(property.value); +// return { ...currInfo, hasMemo: true, hasProperType }; +// } +// if (!mirrorArkts.isProperty(node) || !node.key || !mirrorArkts.isIdentifier(node.key)) { +// return currInfo; +// } +// const decl = mirrorArkts.getDecl(node.key); +// if (!decl || !mirrorArkts.isMethodDefinition(decl)) { +// return currInfo; +// } +// const hasReceiver = decl.scriptFunction.hasReceiver; +// const isSetter = decl.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; +// const isGetter = decl.kind === mirrorArkts.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.hasProperType = false; +// if (!!node.value && mirrorArkts.isArrowFunctionExpression(node.value)) { +// currInfo = { +// ...currInfo, +// ...collectMemoableInfoInScriptFunction(node.value.scriptFunction), +// }; +// } +// return currInfo; +// } + +// export function collectMemoableInfoInClassProperty(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { +// let currInfo = info ?? {}; +// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// return { ...currInfo, hasMemo: true, hasProperType: true }; +// } +// if (!mirrorArkts.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: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { +// let currInfo = info ?? {}; +// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// return { ...currInfo, hasMemo: true, hasProperType: true }; +// } +// if (!mirrorArkts.isArrowFunctionExpression(node)) { +// return currInfo; +// } +// currInfo.hasProperType = true; +// currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; +// if (!!node.scriptFunction) { +// currInfo = { +// ...currInfo, +// ...collectMemoableInfoInScriptFunction(node.scriptFunction), +// }; +// } +// if (!!node.parent && mirrorArkts.isAssignmentExpression(node.parent) && !!node.parent.left) { +// const expr = mirrorArkts.isMemberExpression(node.parent.left) ? node.parent.left.property : node.parent.left; +// const decl = mirrorArkts.getDecl(expr); +// if (!decl) { +// return currInfo; +// } +// if (mirrorArkts.isClassProperty(decl)) { +// currInfo = { +// ...currInfo, +// ...collectMemoableInfoInClassProperty(decl), +// }; +// } +// } +// return currInfo; +// } + +// export function collectMemoableInfoInScriptFunction(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { +// let currInfo = info ?? {}; +// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// return { ...currInfo, hasMemo: true, hasProperType: true }; +// } +// if (!mirrorArkts.isScriptFunction(node)) { +// return currInfo; +// } +// currInfo.hasProperType = true; +// currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; +// return currInfo; +// } + +// export function collectMemoableInfoInMethod(node: mirrorArkts.MethodDefinition): MemoableInfo { +// const hasReceiver = node.scriptFunction.hasReceiver; +// const isSetter = node.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; +// const isGetter = node.kind === mirrorArkts.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); +// } +// return collectMemoableInfoInScriptFunction(node.scriptFunction, info); +// } + +// export function collectMemoableInfoInType(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { +// let currInfo = info ?? {}; +// return { +// ...currInfo, +// ...collectMemoableInfoInFunctionType(node), +// ...collectMemoableInfoInUnionType(node), +// ...collectMemoableInfoInTypeReference(node), +// }; +// } + +// export function collectMemoableInfoInFunctionReturnType(node: mirrorArkts.ScriptFunction): MemoableInfo { +// if (!!node.returnTypeAnnotation) { +// let memoableInfo: MemoableInfo; +// if (mirrorArkts.NodeCache.getInstance().has(node.returnTypeAnnotation)) { +// memoableInfo = { hasMemo: true, hasProperType: true }; +// } else { +// memoableInfo = collectMemoableInfoInType(node.returnTypeAnnotation); +// } +// if ((memoableInfo.hasMemo || memoableInfo.hasBuilder) && memoableInfo.hasProperType) { +// mirrorArkts.NodeCache.getInstance().collect(node.returnTypeAnnotation); +// } +// return memoableInfo; +// } +// return {}; +// } + +// export function collectGensymDeclarator(declarator: mirrorArkts.VariableDeclarator, info: MemoableInfo): void { +// if (!info.hasMemo && !info.hasBuilder) { +// return; +// } +// mirrorArkts.NodeCache.getInstance().collect(declarator); +// const initializer = declarator.initializer; +// if (!initializer || !mirrorArkts.isConditionalExpression(initializer)) { +// return; +// } +// const alternate = initializer.alternate; +// if (!alternate) { +// return; +// } +// let arrowFunc: mirrorArkts.ArrowFunctionExpression | undefined; +// if (mirrorArkts.isTSAsExpression(alternate) && !!alternate.expr && mirrorArkts.isArrowFunctionExpression(alternate.expr)) { +// arrowFunc = alternate.expr; +// } else if (mirrorArkts.isArrowFunctionExpression(alternate)) { +// arrowFunc = alternate; +// } +// if (!!arrowFunc) { +// const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(arrowFunc.scriptFunction); +// const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams(arrowFunc.scriptFunction); +// if (!!arrowFunc.scriptFunction.body && mirrorArkts.isBlockStatement(arrowFunc.scriptFunction.body)) { +// collectMemoScriptFunctionBody( +// arrowFunc.scriptFunction.body, +// returnMemoableInfo, +// paramMemoableInfoMap, +// gensymCount +// ); +// } +// } +// } + +// export function collectMemoableInfoMapInFunctionParams( +// node: mirrorArkts.ScriptFunction, +// shouldCollectParameter: boolean = true +// ): [Map, number] { +// const hasReceiver = node.hasReceiver; +// const paramMap: Map = new Map(); +// let gensymCount: number = 0; +// node.params.slice(hasReceiver ? 1 : 0).forEach((p) => { +// const info = collectMemoableInfoInFunctionParam(node, p, gensymCount, shouldCollectParameter); +// gensymCount = info.gensymCount; +// info.peers.forEach((peer) => paramMap.set(peer, info.memoableInfo)); +// }); +// return [paramMap, gensymCount]; +// } + +// interface FunctionParamCollectInfo { +// peers: mirrorArkts.AstNode['peer'][]; +// gensymCount: number; +// memoableInfo: MemoableInfo; +// } + +// function collectMemoableInfoInFunctionParam( +// node: mirrorArkts.ScriptFunction, +// param: mirrorArkts.Expression, +// gensymCount: number, +// shouldCollectParameter: boolean = true +// ): FunctionParamCollectInfo { +// const peers: mirrorArkts.AstNode['peer'][] = []; +// let memoableInfo: MemoableInfo; +// const _param = param as mirrorArkts.ETSParameterExpression; +// if (mirrorArkts.NodeCache.getInstance().has(_param)) { +// const metadata = mirrorArkts.NodeCache.getInstance().get(_param)!.metadata ?? {}; +// const { hasMemoSkip } = metadata; +// memoableInfo = { hasMemo: true, hasMemoSkip, hasProperType: true }; +// } else { +// memoableInfo = collectMemoableInfoInParameter(_param); +// } +// if (_param.identifier.name.startsWith(GenSymPrefix.INTRINSIC) && !!node.body && mirrorArkts.isBlockStatement(node.body)) { +// const declaration = node.body.statements.at(gensymCount); +// if (!!declaration && mirrorArkts.isVariableDeclaration(declaration) && declaration.declarators.length > 0) { +// const declarator = declaration.declarators[0]; +// collectGensymDeclarator(declarator, memoableInfo); +// if (!memoableInfo.hasMemoSkip && shouldCollectParameter) { +// peers.push(declarator.name.peer); +// } +// gensymCount++; +// } +// } +// if (checkIsMemoFromMemoableInfo(memoableInfo)) { +// mirrorArkts.NodeCache.getInstance().collect(_param, { hasMemoSkip: memoableInfo.hasMemoSkip }); +// } +// if (!memoableInfo.hasMemoSkip && shouldCollectParameter) { +// peers.push(_param.identifier.peer); +// } +// return { peers, memoableInfo, gensymCount }; +// } + +/** + * Collect `@memo` annotated `mirrorArkts.TypeNode` node. And find whether it can be `@memo` annotated. + * + * @param node `mirrorArkts.TypeNode` node. + * @returns true if it is not `@memo` annotated but can add `@memo` to it. + */ +export function findCanAddMemoFromTypeAnnotation( + typeAnnotation: mirrorArkts.AstNode | undefined +): typeAnnotation is mirrorArkts.ETSFunctionType { + if (!typeAnnotation) { + return false; + } + const memoableInfo = collectMemoableInfoInType(typeAnnotation); + if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { + mirrorArkts.NodeCache.getInstance().collect(typeAnnotation); + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +} + +// /** +// * Collect `@memo` annotated `mirrorArkts.Property` node. And find whether it can be `@memo` annotated. +// * +// * @param node `mirrorArkts.Property` node. +// * @returns true if it is not `@memo` annotated but can add `@memo` to it. +// */ +// export function findCanAddMemoFromProperty(property: mirrorArkts.AstNode): property is mirrorArkts.Property { +// const memoableInfo = collectMemoableInfoInProperty(property); +// if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { +// mirrorArkts.NodeCache.getInstance().collect(property); +// } +// return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +// } + +// /** +// * Collect `@memo` annotated `mirrorArkts.ClassProperty` node. And find whether it can be `@memo` annotated. +// * +// * @param node `mirrorArkts.ClassProperty` node. +// * @returns true if it is not `@memo` annotated but can add `@memo` to it. +// */ +// export function findCanAddMemoFromClassProperty(property: mirrorArkts.AstNode): property is mirrorArkts.ClassProperty { +// const memoableInfo = collectMemoableInfoInClassProperty(property); +// if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { +// mirrorArkts.NodeCache.getInstance().collect(property); +// } +// return ( +// (!!memoableInfo.hasBuilder || !!memoableInfo.hasBuilderParam) && +// !memoableInfo.hasMemo && +// !!memoableInfo.hasProperType +// ); +// } + +// /** +// * Collect `@memo` annotated `mirrorArkts.ETSParameterExpression` node. And find whether it can be `@memo` annotated. +// * +// * @param node `mirrorArkts.ETSParameterExpression` node. +// * @returns true if it is not `@memo` annotated but can add `@memo` to it. +// */ +// export function findCanAddMemoFromParameter(param: mirrorArkts.AstNode | undefined): param is mirrorArkts.ETSParameterExpression { +// if (!param) { +// return false; +// } +// const memoableInfo = collectMemoableInfoInParameter(param); +// if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { +// mirrorArkts.NodeCache.getInstance().collect(param, { hasMemoSkip: memoableInfo.hasMemoSkip }); +// } +// return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +// } + +// /** +// * Collect `@memo` annotated `mirrorArkts.ArrowFunctionExpression` node. And find whether it can be `@memo` annotated. +// * +// * @param node `mirrorArkts.ArrowFunctionExpression` node. +// * @returns true if it is not `@memo` annotated but can add `@memo` to it. +// */ +// export function findCanAddMemoFromArrowFunction(node: mirrorArkts.AstNode): node is mirrorArkts.ArrowFunctionExpression { +// if (!mirrorArkts.isArrowFunctionExpression(node)) { +// return false; +// } +// const memoableInfo = collectMemoableInfoInArrowFunction(node); +// const { hasMemoEntry, hasMemoIntrinsic } = memoableInfo; +// const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(node.scriptFunction); +// const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams( +// node.scriptFunction, +// !hasMemoEntry && !hasMemoIntrinsic +// ); +// const isMemoReturnType = checkIsMemoFromMemoableInfo(returnMemoableInfo); +// if (isMemoReturnType) { +// mirrorArkts.NodeCache.getInstance().collect(node.scriptFunction.returnTypeAnnotation!); +// } +// const isMemo = checkIsMemoFromMemoableInfo(memoableInfo); +// if (isMemo && !mirrorArkts.NodeCache.getInstance().has(node)) { +// mirrorArkts.NodeCache.getInstance().collect(node, { hasMemoEntry, hasMemoIntrinsic }); +// if (!!node.scriptFunction.body && mirrorArkts.isBlockStatement(node.scriptFunction.body)) { +// const disableCollectReturn = hasMemoEntry || hasMemoIntrinsic; +// collectMemoScriptFunctionBody( +// node.scriptFunction.body, +// returnMemoableInfo, +// paramMemoableInfoMap, +// gensymCount, +// disableCollectReturn +// ); +// } +// } +// return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +// } + +// /** +// * Collect `@memo` annotated `mirrorArkts.TSTypeAliasDeclaration` node. And find whether it can be `@memo` annotated. +// * +// * @param node `mirrorArkts.TSTypeAliasDeclaration` node. +// * @returns true if it is not `@memo` annotated but can add `@memo` to it. +// */ +// export function findCanAddMemoFromTypeAlias(node: mirrorArkts.AstNode): node is mirrorArkts.TSTypeAliasDeclaration { +// const memoableInfo = collectMemoableInfoInTypeAlias(node); +// if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { +// mirrorArkts.NodeCache.getInstance().collect(node); +// } +// return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +// } + +// /** +// * Collect `@memo` annotated `mirrorArkts.MethodDefinition` node. And find whether it can be `@memo` annotated. +// * +// * @param node `mirrorArkts.MethodDefinition` node. +// * @returns true if it is not `@memo` annotated but can add `@memo` to it. +// */ +// export function findCanAddMemoFromMethod(node: mirrorArkts.AstNode): node is mirrorArkts.MethodDefinition { +// if (!mirrorArkts.isMethodDefinition(node)) { +// return false; +// } +// const memoableInfo = collectMemoableInfoInMethod(node); +// const { hasMemoEntry, hasMemoIntrinsic } = memoableInfo; +// const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(node.scriptFunction); +// const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams( +// node.scriptFunction, +// !hasMemoEntry && !hasMemoIntrinsic +// ); +// const isMemo = checkIsMemoFromMemoableInfo(memoableInfo); +// const isMemoReturnType = checkIsMemoFromMemoableInfo(returnMemoableInfo); +// if (isMemoReturnType) { +// mirrorArkts.NodeCache.getInstance().collect(node.scriptFunction.returnTypeAnnotation!); +// } +// if (isMemo && !mirrorArkts.NodeCache.getInstance().has(node)) { +// const metadata = collectMetadataInMethod(node); +// mirrorArkts.NodeCache.getInstance().collect(node, { +// ...metadata, +// hasMemoEntry, +// hasMemoIntrinsic, +// }); +// if (!!node.scriptFunction.body && mirrorArkts.isBlockStatement(node.scriptFunction.body)) { +// const disableCollectReturn = hasMemoEntry || hasMemoIntrinsic; +// collectMemoScriptFunctionBody( +// node.scriptFunction.body, +// returnMemoableInfo, +// paramMemoableInfoMap, +// gensymCount, +// disableCollectReturn +// ); +// } +// } +// return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +// } + +// /** +// * Collect `@memo` annotated `mirrorArkts.CallExpression` node from corresponding declared method, +// * as well as collect each `@memo` annotated argument from corresponding declared method parameter. +// * +// * @param node `mirrorArkts.CallExpression` node. +// */ +// export function collectMemoFromCallExpression(node: mirrorArkts.CallExpression): void { +// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// return; +// } +// const expr = findIdentifierFromCallee(node.expression); +// const decl = (expr && getDeclResolveAlias(expr)) ?? node.expression; +// if (!decl) { +// return; +// } +// let isCollected: boolean = false; +// if (mirrorArkts.NodeCache.getInstance().has(decl)) { +// mirrorArkts.NodeCache.getInstance().collect(node); +// isCollected = true; +// } else if (mirrorArkts.isMethodDefinition(decl)) { +// isCollected = collectCallWithDeclaredMethod(node, decl); +// } else if (mirrorArkts.isClassProperty(decl)) { +// isCollected = collectCallWithDeclaredClassProperty(node, decl); +// } +// if (isCollected && mirrorArkts.isTSAsExpression(node.expression) && node.expression.typeAnnotation) { +// mirrorArkts.NodeCache.getInstance().collect(node.expression.typeAnnotation); +// } +// } + +// export function collectCallWithDeclaredClassProperty(node: mirrorArkts.CallExpression, decl: mirrorArkts.ClassProperty): boolean { +// if (mirrorArkts.NodeCache.getInstance().has(decl)) { +// mirrorArkts.NodeCache.getInstance().collect(node); +// return true; +// } +// const memoableInfo = collectMemoableInfoInClassProperty(decl); +// if (checkIsMemoFromMemoableInfo(memoableInfo, false) || memoableInfo.hasBuilder || memoableInfo.hasBuilderParam) { +// mirrorArkts.NodeCache.getInstance().collect(node); +// return true; +// } +// return false; +// } + +// export function collectCallWithDeclaredMethod(node: mirrorArkts.CallExpression, decl: mirrorArkts.MethodDefinition): boolean { +// const hasReceiver = decl.scriptFunction.hasReceiver; +// const params = decl.scriptFunction.params; +// const args = node.arguments; +// const hasRestParameter = decl.scriptFunction.hasRestParameter; +// const isTrailingCall = node.isTrailingCall; +// const options = { hasRestParameter, isTrailingCall }; +// forEachArgWithParam(args, params, collectCallArgsWithMethodParams, options); +// if (mirrorArkts.NodeCache.getInstance().has(decl)) { +// const { hasMemoEntry, hasMemoIntrinsic } = mirrorArkts.NodeCache.getInstance().get(decl)!.metadata ?? {}; +// mirrorArkts.NodeCache.getInstance().collect(node, { hasReceiver, hasMemoEntry, hasMemoIntrinsic }); +// return true; +// } else { +// const memoableInfo = collectMemoableInfoInScriptFunction(decl.scriptFunction); +// if (checkIsMemoFromMemoableInfo(memoableInfo, true)) { +// const { hasMemoEntry, hasMemoIntrinsic } = memoableInfo; +// mirrorArkts.NodeCache.getInstance().collect(node, { hasReceiver, hasMemoEntry, hasMemoIntrinsic }); +// return true; +// } +// } +// return false; +// } + +// export function collectCallArgsWithMethodParams(arg: mirrorArkts.Expression | undefined, param: mirrorArkts.Expression): void { +// if (!arg) { +// return; +// } +// let info: MemoableInfo; +// if (mirrorArkts.NodeCache.getInstance().has(param)) { +// info = { hasMemo: true, hasProperType: true }; +// } else { +// info = collectMemoableInfoInParameter(param); +// } +// if (checkIsMemoFromMemoableInfo(info) && mirrorArkts.isArrowFunctionExpression(arg)) { +// mirrorArkts.NodeCache.getInstance().collect(arg); +// const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(arg.scriptFunction); +// const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams(arg.scriptFunction); +// if (!!arg.scriptFunction.body && mirrorArkts.isBlockStatement(arg.scriptFunction.body)) { +// collectMemoScriptFunctionBody( +// arg.scriptFunction.body, +// returnMemoableInfo, +// paramMemoableInfoMap, +// gensymCount +// ); +// } +// } +// } + +// export function findIdentifierFromCallee(callee: mirrorArkts.AstNode | undefined): mirrorArkts.Identifier | undefined { +// if (!callee) { +// return undefined; +// } +// if (mirrorArkts.isIdentifier(callee)) { +// return callee; +// } +// if (mirrorArkts.isMemberExpression(callee)) { +// return findIdentifierFromCallee(callee.property); +// } +// if (mirrorArkts.isTSAsExpression(callee)) { +// return findIdentifierFromCallee(callee.expr); +// } +// if (mirrorArkts.isTSNonNullExpression(callee)) { +// return findIdentifierFromCallee(callee.expr); +// } +// return undefined; +// } + +// export function collectMemoScriptFunctionBody( +// body: mirrorArkts.BlockStatement, +// returnMemoableInfo: MemoableInfo, +// paramMemoableInfoMap: Map, +// gensymCount: number, +// disableCollectReturn?: boolean +// ): void { +// const collector = new MemoFunctionCollector(); +// body.statements.forEach((st, index) => { +// if (index < gensymCount) { +// return; +// } +// if (disableCollectReturn) { +// collector.disableCollectReturn(); +// } +// collector.registerReturnInfo(returnMemoableInfo).registerParamInfoMap(paramMemoableInfoMap).visitor(st); +// collector.reset(); +// }); +// } + +// export function collectMetadataInMethod(node: mirrorArkts.MethodDefinition): mirrorArkts.AstNodeCacheValue['metadata'] { +// const callName = node.name.name; +// const hasReceiver = node.scriptFunction.hasReceiver; +// const isSetter = node.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; +// const isGetter = node.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; +// return { callName, hasReceiver, isSetter, isGetter }; +// } + +// export function checkIsMemoFromMemoableInfo(info: MemoableInfo, ignoreType: boolean = false): boolean { +// return ( +// (!!info.hasMemo || !!info.hasMemoIntrinsic || !!info.hasMemoEntry || !!info.hasBuilder) && +// (ignoreType || !!info.hasProperType) +// ); +// } + +// export function getDeclResolveAlias(node: mirrorArkts.AstNode): mirrorArkts.AstNode | undefined { +// const decl = mirrorArkts.getDecl(node); +// if (!!decl && !!decl.parent && mirrorArkts.isIdentifier(decl) && mirrorArkts.isVariableDeclarator(decl.parent)) { +// if (!!decl.parent.initializer && mirrorArkts.isIdentifier(decl.parent.initializer)) { +// return getDeclResolveAlias(decl.parent.initializer); +// } +// if (!!decl.parent.initializer && mirrorArkts.isMemberExpression(decl.parent.initializer)) { +// return getDeclResolveAlias(decl.parent.initializer.property); +// } +// } +// return decl; +// } + +// export function parametersBlockHasReceiver(params: readonly mirrorArkts.Expression[]): boolean { +// return params.length > 0 && mirrorArkts.isEtsParameterExpression(params[0]) && isThisParam(params[0]); +// } + +// export function parametrizedNodeHasReceiver(node: mirrorArkts.ScriptFunction | mirrorArkts.ETSFunctionType | undefined): boolean { +// if (node === undefined) { +// return false; +// } +// return parametersBlockHasReceiver(node.params); +// } + +// function isThisParam(node: mirrorArkts.Expression | undefined): boolean { +// if (node === undefined || !mirrorArkts.isEtsParameterExpression(node)) { +// return false; +// } +// return node.identifier?.isReceiver ?? false; +// } diff --git a/arkui-plugins/mirror-replace/common/arkts-utils.ts b/arkui-plugins/mirror-replace/common/arkts-utils.ts index aaa6f23c7..d27b87a3a 100644 --- a/arkui-plugins/mirror-replace/common/arkts-utils.ts +++ b/arkui-plugins/mirror-replace/common/arkts-utils.ts @@ -12,6 +12,53 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import * as mirrorArkts from '@koalaui/libarkts-mirror'; +import { DeclarationCollector } from './declaration-collector'; +import { ARKUI_IMPORT_PREFIX_NAMES, DecoratorNames } from './predefines'; + +/** + * create and insert `import { as } from ` to the top of script's statements. + */ +export function createAndInsertImportDeclaration( + source: mirrorArkts.StringLiteral, + imported: mirrorArkts.Identifier, + local: mirrorArkts.Identifier, + importKind: mirrorArkts.Es2pandaImportKinds, + program: mirrorArkts.Program +): void { + const importDecl: mirrorArkts.ETSImportDeclaration = mirrorArkts.factory.createETSImportDeclaration( + source, + [mirrorArkts.factory.createImportSpecifier(imported, local)], + importKind + ); + if (program?.fileNameWithExtension === 'new.ets') { + console.log(importDecl.dumpSrc()) + console.log("program?.fileNameWithExtension === 'new.ets'") + console.log(program.ast) + } + mirrorArkts.importDeclarationInsert(importDecl, program); + if (program?.fileNameWithExtension === 'new.ets') { + console.log('--------------------------------------------') + console.log(program.ast) + console.log(program.ast.dumpSrc()) + console.log('--------------------------------------------') + } + return; +} + +export function annotation(name: string): mirrorArkts.AnnotationUsage { + const ident: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier(name).setAnnotationUsage(); + const annotation: mirrorArkts.AnnotationUsage = mirrorArkts.factory.createAnnotationUsage(ident, []); + + annotation.modifierFlags = mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_ANNOTATION_USAGE; + ident.parent = annotation; + + return annotation; +} + +export function isAnnotation(node: mirrorArkts.AnnotationUsage, annoName: string) { + return node.expr !== undefined && mirrorArkts.isIdentifier(node.expr) && node.expr.name === annoName; +} export function matchPrefix(prefixCollection: (string | RegExp)[], name: string): boolean { for (const prefix of prefixCollection) { @@ -28,4 +75,53 @@ export function matchPrefix(prefixCollection: (string | RegExp)[], name: string) } } return false; +} + +export function filterDefined(value: (T | undefined)[]): T[] { + return value.filter((it: T | undefined): it is T => it != undefined); +} + +export function collect(...value: (ReadonlyArray | T | undefined)[]): T[] { + const empty: (T | undefined)[] = []; + return filterDefined(empty.concat(...value)); +} + +export function isDecoratorAnnotation( + anno: mirrorArkts.AnnotationUsage, + decoratorName: DecoratorNames, + ignoreDecl?: boolean +): boolean { + if (!(!!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName)) { + return false; + } + if (!ignoreDecl) { + const decl = mirrorArkts.getDecl(anno.expr); + if (!decl) { + return false; + } + const moduleName: string = mirrorArkts.getProgramFromAstNode(decl).moduleName; + if (!moduleName || !matchPrefix(ARKUI_IMPORT_PREFIX_NAMES, moduleName)) { + return false; + } + DeclarationCollector.getInstance().collect(decl); + } + return true; +} + +export function expectName(node: mirrorArkts.AstNode | undefined): string { + if (!node) { + throw new Error('Expected an identifier, got empty node'); + } + if (!mirrorArkts.isIdentifier(node)) { + throw new Error('Expected an identifier, got: ' + mirrorArkts.nodeType(node).toString()); + } + return node.name; +} + +export function mangle(value: string): string { + return `__${value}`; +} + +export function backingField(originalName: string): string { + return mangle(`backing_${originalName}`); } \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/common/compat-koala-wrapper.ts b/arkui-plugins/mirror-replace/common/compat-koala-wrapper.ts index 1d84ac252..7506a19f3 100644 --- a/arkui-plugins/mirror-replace/common/compat-koala-wrapper.ts +++ b/arkui-plugins/mirror-replace/common/compat-koala-wrapper.ts @@ -17,9 +17,9 @@ import * as arkts from '@koalaui/libarkts'; import * as mirrorArkts from '@koalaui/libarkts-mirror'; import { PluginContext } from './plugin-context'; -export function copyGlobalFromKoalaWrapper(this: PluginContext): void { +export function copyGlobalFromKoalaWrapper(context: PluginContext): void { mirrorArkts.arktsGlobal.config = arkts.arktsGlobal.config; - const contextPtr = arkts.arktsGlobal.compilerContext?.peer ?? this.getContextPtr() + const contextPtr = arkts.arktsGlobal.compilerContext?.peer ?? context.getContextPtr() mirrorArkts.arktsGlobal.compilerContext = new mirrorArkts.Context(contextPtr) console.log('[MIRROR]: copied config and context from koala-wrapper') } \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/common/declaration-collector.ts b/arkui-plugins/mirror-replace/common/declaration-collector.ts new file mode 100644 index 000000000..4e858a19c --- /dev/null +++ b/arkui-plugins/mirror-replace/common/declaration-collector.ts @@ -0,0 +1,86 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { IMPORT_SOURCE_MAP_V2, INTERMEDIATE_IMPORT_SOURCE } from './predefines'; +// import { ImportCollector } from './import-collector'; + +export class DeclarationCollector { + private fromExternalSourceNameMap: Map; + private fromExternalSourceNodePeerMap: Map; + static instance: DeclarationCollector; + + private constructor() { + this.fromExternalSourceNameMap = new Map(); + this.fromExternalSourceNodePeerMap = new Map(); + } + + static getInstance(): DeclarationCollector { + if (!this.instance) { + this.instance = new DeclarationCollector(); + } + return this.instance; + } + + private collectIntermediateImportSource(symbol: string, declSourceName: string): void { + let sourceName: string; + if (IMPORT_SOURCE_MAP_V2.has(symbol)) { + sourceName = IMPORT_SOURCE_MAP_V2.get(symbol)!; + } else { + sourceName = declSourceName; + } + // ImportCollector.getInstance().collectSource(symbol, sourceName); + } + + collect(decl: mirrorArkts.AstNode | undefined): void { + // if (!decl) { + // return; + // } + // let declName: string | undefined; + // if (mirrorArkts.isAnnotationDeclaration(decl) && !!decl.expr && mirrorArkts.isIdentifier(decl.expr)) { + // declName = decl.expr.name; + // } else if (mirrorArkts.isMethodDefinition(decl)) { + // declName = decl.name.name; + // } else if (mirrorArkts.isIdentifier(decl)) { + // declName = decl.name; + // } else if (mirrorArkts.isClassProperty(decl) && !!decl.key && mirrorArkts.isIdentifier(decl.key)) { + // declName = decl.key.name; + // } else if (mirrorArkts.isEtsParameterExpression(decl)) { + // declName = decl.identifier.name; + // } + // if (!declName) { + // return; + // } + // let sourceName: string = mirrorArkts.getProgramFromAstNode(decl).moduleName; + // this.fromExternalSourceNameMap.set(declName, sourceName); + // this.fromExternalSourceNodePeerMap.set(decl.peer, sourceName); + + // INTERMEDIATE_IMPORT_SOURCE.get(declName)?.forEach((symbol) => { + // this.collectIntermediateImportSource(symbol, sourceName); + // }); + } + + findExternalSourceFromName(declName: string): string | undefined { + return this.fromExternalSourceNameMap.get(declName); + } + + findExternalSourceFromNode(decl: mirrorArkts.AstNode): string | undefined { + return this.fromExternalSourceNodePeerMap.get(decl.peer); + } + + reset(): void { + this.fromExternalSourceNameMap.clear(); + this.fromExternalSourceNodePeerMap.clear(); + } +} diff --git a/arkui-plugins/mirror-replace/common/import-collector.ts b/arkui-plugins/mirror-replace/common/import-collector.ts new file mode 100644 index 000000000..9622ba2fe --- /dev/null +++ b/arkui-plugins/mirror-replace/common/import-collector.ts @@ -0,0 +1,105 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { createAndInsertImportDeclaration } from './arkts-utils'; + +interface ImportInfo { + imported: string; + local: string; + source: string; + kind: mirrorArkts.Es2pandaImportKinds; +} + +function insertImport(importInfo: ImportInfo, program?: mirrorArkts.Program): void { + const source: mirrorArkts.StringLiteral = mirrorArkts.factory.createStringLiteral(importInfo.source); + const imported: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier(importInfo.imported); + const local: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier(importInfo.local); + // Insert this import at the top of the script's statements. + if (!program) { + throw Error('Failed to insert import: Transformer has no program'); + } + createAndInsertImportDeclaration(source, imported, local, importInfo.kind, program); +} + +export class ImportCollector { + public importInfos: ImportInfo[]; + public localMap: Map; + public sourceMap: Map; + private static instance: ImportCollector; + + /** this set is used for keeping the import sentence unique */ + private imported: Set; + + private constructor() { + this.importInfos = []; + this.imported = new Set(); + this.localMap = new Map(); + this.sourceMap = new Map(); + } + + static getInstance(): ImportCollector { + if (!this.instance) { + this.instance = new ImportCollector(); + } + return this.instance; + } + + reset(): void { + this.importInfos = []; + this.imported.clear(); + this.localMap.clear(); + this.sourceMap.clear(); + } + + collectSource(imported: string, source: string): void { + if (!this.sourceMap.has(imported)) { + this.sourceMap.set(imported, source); + } + } + + collectImport( + imported: string, + local?: string, + kind: mirrorArkts.Es2pandaImportKinds = mirrorArkts.Es2pandaImportKinds.IMPORT_KINDS_TYPES + ): void { + if (!this.sourceMap.has(imported)) { + throw new Error(`ImportCollector: import ${imported}'s source haven't been collected yet.`); + } + if (this.imported.has(imported)) { + return; + } + const source: string = this.sourceMap.get(imported)!; + const _local: string = local ?? imported; + this.importInfos.push({ + source, + imported, + local: _local, + kind, + }); + this.localMap.set(imported, _local); + this.imported.add(imported); + } + + getLocal(imported: string): string | undefined { + return this.localMap.get(imported); + } + + insertCurrentImports(program?: mirrorArkts.Program): void { + this.importInfos.forEach((importInfo) => { + insertImport(importInfo, program); + }); + } +} diff --git a/arkui-plugins/mirror-replace/common/plugin-context.ts b/arkui-plugins/mirror-replace/common/plugin-context.ts index 3621ff891..27f633de2 100644 --- a/arkui-plugins/mirror-replace/common/plugin-context.ts +++ b/arkui-plugins/mirror-replace/common/plugin-context.ts @@ -72,6 +72,10 @@ export interface ProjectConfig { moduleRootPath: string; aceModuleJsonPath: string; ignoreError: boolean; + projectPath: string; + projectRootPath: string; + integratedHsp: boolean; + frameworkMode?: string; } export type PluginHandlerFunction = () => void; diff --git a/arkui-plugins/mirror-replace/common/predefines.ts b/arkui-plugins/mirror-replace/common/predefines.ts index 6e51e18f7..d1416d9d5 100644 --- a/arkui-plugins/mirror-replace/common/predefines.ts +++ b/arkui-plugins/mirror-replace/common/predefines.ts @@ -248,4 +248,30 @@ export const IMPORT_SOURCE_MAP_V2: Map = new Map export enum GetSetTypes { GET = 'get', SET = 'set', +} + +export enum NavigationNames { + NAVINTERFACE = 'NavInterface', + BUNDLE_NAME = 'bundleName', + MODULE_NAME = 'moduleName', + PAGE_PATH = 'pagePath', + PAGE_FULL_PATH = 'pageFullPath', + INTEGRATED_HSP = 'integratedHsp', +} + +export enum EntryWrapperNames { + ENTRY_FUNC = 'entry', + WRAPPER_CLASS_NAME = '__EntryWrapper', + ENTRY_STORAGE_LOCAL_STORAGE_PROPERTY_NAME = '_entry_local_storage_', + ENTRY_POINT_CLASS_NAME = 'EntryPoint', + REGISTER_NAMED_ROUTER = 'RegisterNamedRouter', + ROUTER_NAME = 'routerName', + INSTANCE = 'instance', + PARAM = 'param' +} + +export enum EntryParamNames { + ENTRY_STORAGE = 'storage', + ENTRY_USE_SHARED_STORAGE = 'useSharedStorage', + ENTRY_ROUTE_NAME = 'routeName' } \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/common/program-visitor.ts b/arkui-plugins/mirror-replace/common/program-visitor.ts index 6838f5f24..37881143c 100644 --- a/arkui-plugins/mirror-replace/common/program-visitor.ts +++ b/arkui-plugins/mirror-replace/common/program-visitor.ts @@ -201,7 +201,7 @@ export class ProgramVisitor extends AbstractVisitor { programVisitor(program: mirrorArkts.Program): mirrorArkts.Program { this.visitExternalSources(program, [program]); let programScript = program.ast; - programScript = this.visitor(programScript, program, this.externalSourceName); + this.visitor(programScript, program, this.externalSourceName); const visitorsToReset = flattenVisitorsInHooks(this.hooks, this.state); visitorsToReset.forEach((visitor) => visitor.reset()); @@ -250,12 +250,11 @@ export class ProgramVisitor extends AbstractVisitor { // pre-run visitors hook = isExternal ? this.hooks?.external : this.hooks?.source; this.preVisitor(hook, node, program, externalSourceName); - for (const transformer of this.visitors) { // if (this.legacyStructMap.size > 0 && transformer instanceof ComponentTransformer) { // transformer.registerMap(this.legacyStructMap); // } - this.visitTransformer(transformer, script, externalSourceName, program); + script = this.visitTransformer(transformer, script, externalSourceName, program); transformer.reset(); mirrorArkts.setAllParents(script); if (!transformer.isExternal) { @@ -296,7 +295,7 @@ export class ProgramVisitor extends AbstractVisitor { transformer.externalSourceName = externalSourceName; transformer.program = program; const newScript = transformer.visitor(script) as mirrorArkts.ETSModule; - program?.ast.setStatements(newScript.statements) + program?.setAst(newScript) return newScript; } } diff --git a/arkui-plugins/mirror-replace/common/safe-types.ts b/arkui-plugins/mirror-replace/common/safe-types.ts new file mode 100644 index 000000000..264a3a6df --- /dev/null +++ b/arkui-plugins/mirror-replace/common/safe-types.ts @@ -0,0 +1,43 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +export type PartialExcept = Partial & Pick; + +type PartialArray = T extends readonly any[] | any[] ? T | undefined : T; +type PartialAstNode = T extends mirrorArkts.AstNode ? T | undefined : T; +type PartialObject = T extends object ? { [P in keyof T]?: T[P] } : T; +type PartialPrimitive = T; + +export type PartialNested = { + [P in keyof T]?: T[P] extends readonly any[] | any[] + ? PartialArray + : T[P] extends mirrorArkts.AstNode + ? PartialAstNode + : T[P] extends object + ? PartialObject + : PartialPrimitive; +}; + +type NestedKey = { + [P in keyof T]: P extends K ? T[P] : T[P] extends object ? NestedKey : T[P]; + }; + +export type PickNested = { + [P in keyof T]: P extends K ? T[P] : T[P] extends object ? NestedKey : T[P]; +}; + +export type PartialNestedExcept = PartialNested> & PickNested; \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/interop-plugins/decl_transformer.ts b/arkui-plugins/mirror-replace/interop-plugins/decl_transformer.ts index 1ec5a9b05..19e0f222f 100644 --- a/arkui-plugins/mirror-replace/interop-plugins/decl_transformer.ts +++ b/arkui-plugins/mirror-replace/interop-plugins/decl_transformer.ts @@ -30,7 +30,7 @@ export class DeclTransformer extends AbstractVisitor { throw 'Non Empty className expected for Component'; } - let newDec: mirrorArkts.ClassDeclaration = mirrorArkts.factory.createClassDeclaration(node.definition); + let newDec: mirrorArkts.ClassDeclaration = mirrorArkts.factory.createClassDeclaration(node.definition, node.modifierFlags); const newDefinition = mirrorArkts.factory.updateClassDefinition( newDec.definition!, diff --git a/arkui-plugins/mirror-replace/interop-plugins/index.ts b/arkui-plugins/mirror-replace/interop-plugins/index.ts index a54d9eec1..7468cfd4b 100644 --- a/arkui-plugins/mirror-replace/interop-plugins/index.ts +++ b/arkui-plugins/mirror-replace/interop-plugins/index.ts @@ -41,6 +41,7 @@ function parsedTransform(this: PluginContext): mirrorArkts.ETSModule | undefined if (!!contextPtr) { let program = mirrorArkts.arkts.getOrUpdateGlobalContext(contextPtr).program; script = program.ast as mirrorArkts.ETSModule; + console.log(script.dumpJson()) if (script) { debugLog(`[MIRROR]: interopTransform: script before parsing: ${script?.dumpSrc()}`) const declTransformer = new DeclTransformer({ diff --git a/arkui-plugins/mirror-replace/ui-plugins/component-transformer.ts b/arkui-plugins/mirror-replace/ui-plugins/component-transformer.ts new file mode 100644 index 000000000..ecba1b3b1 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/component-transformer.ts @@ -0,0 +1,634 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror' +import { getArktsMirrorPath } from '../../path'; +const interop = require(getArktsMirrorPath()); +const nullptr = interop.nullptr; +import { AbstractVisitor, VisitorOptions } from '../common/abstract-visitor'; +import { ProjectConfig } from '../common/plugin-context'; +import { StructMap } from '../common/program-visitor'; +import { + CustomComponentInfo, + CustomComponentNames, + collectCustomComponentScopeInfo, + findLocalImport, + isComponentStruct, + getCustomComponentOptionsName +} from './utils'; +import { + CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, + DecoratorIntrinsicNames, + DecoratorNames, + DECORATOR_TYPE_MAP, + ENTRY_POINT_IMPORT_SOURCE_NAME, + NavigationNames, + EntryWrapperNames +} from '../common/predefines'; +import { factory } from './ui-factory'; +import { + createAndInsertImportDeclaration, + filterDefined, + collect, + expectName, + isDecoratorAnnotation, + backingField, + annotation +} from '../common/arkts-utils'; +import { getEntryParams } from './entry-translators/utils'; +import { factory as entryFactory } from './entry-translators/factory'; +import { factory as propertyFactory } from './property-translators/factory' +import { hasDecoratorName, findDecoratorInfos } from './property-translators/utils'; +import { generateInstantiateInterop } from './interop/interop'; +import { createCustomDialogMethod, isNewCustomDialogController, transformController } from './customdialog'; + +export interface ComponentTransformerOptions extends VisitorOptions { + arkui?: string; + projectConfig?: ProjectConfig, +} + +type ScopeInfo = CustomComponentInfo; + +export interface InteropContext { + className: string; + path: string; + line?: number; + col?: number; + arguments?: mirrorArkts.ObjectExpression; +} + +export class ComponentTransformer extends AbstractVisitor { + private scopeInfos: ScopeInfo[] = []; + private componentInterfaceCollection: mirrorArkts.TSInterfaceDeclaration[] = []; + 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(); + private legacyCallMap: Map = new Map(); + private customDialogController: string = ''; + private projectConfig: ProjectConfig | undefined; + private entryRouteName: string | undefined; + + constructor(options?: ComponentTransformerOptions) { + const _options: ComponentTransformerOptions = options ?? {}; + super(_options); + this.projectConfig = options?.projectConfig; + } + + reset(): void { + super.reset(); + this.scopeInfos = []; + this.componentInterfaceCollection = []; + 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(); + this.legacyCallMap = new Map(); + this.customDialogController = ''; + } + + + enter(node: mirrorArkts.AstNode) { + /** + * find custom components and their annotations. + */ + if (mirrorArkts.isETSStructDeclaration(node) && !!node.definition && !!node.definition.ident) { + const info: ScopeInfo | undefined = collectCustomComponentScopeInfo(node); + if (info) { + this.scopeInfos.push(info); + } + } + // this.scopeInfos.forEach(console.log) + /** + * has the CustomComponent been imported? + */ + if (mirrorArkts.isETSImportDeclaration(node) && !this.isCustomComponentImported) { + this.isCustomComponentImported = !!findLocalImport( + node, + CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, + CustomComponentNames.COMPONENT_CLASS_NAME + ); + } + /** + * has the CustomComponentV2 been imported? + */ + if (mirrorArkts.isETSImportDeclaration(node) && !this.isCustomComponentV2Imported) { + this.isCustomComponentV2Imported = !!findLocalImport( + node, + CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, + CustomComponentNames.COMPONENT_V2_CLASS_NAME + ); + } + /** + * has the EntryPoint been imported? + */ + if (mirrorArkts.isETSImportDeclaration(node) && !this.isEntryPointImported) { + this.isEntryPointImported = !!findLocalImport( + node, + ENTRY_POINT_IMPORT_SOURCE_NAME, + EntryWrapperNames.ENTRY_POINT_CLASS_NAME + ); + } + /** + * has the PageLifeCycle been imported? + */ + if (mirrorArkts.isETSImportDeclaration(node) && !this.isPageLifeCycleImported) { + this.isPageLifeCycleImported = !!findLocalImport( + node, + CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, + CustomComponentNames.PAGE_LIFE_CYCLE + ); + } + /** + * has the LayoutCallback been imported? + */ + if (mirrorArkts.isETSImportDeclaration(node) && !this.isLayoutCallbackImported) { + this.isLayoutCallbackImported = !!findLocalImport( + node, + CUSTOM_COMPONENT_IMPORT_SOURCE_NAME, + CustomComponentNames.LAYOUT_CALLBACK + ); + } + if (this.isCustomDialog() && mirrorArkts.isClassProperty(node)) { + this.hasCustomDialogController(node); + } + } + + /** + * @customDialog in class + */ + isCustomDialog(): boolean { + if (this.scopeInfos.length === 0) { + return false; + } + const scopeInfo: ScopeInfo = this.scopeInfos[this.scopeInfos.length - 1]; + return !!scopeInfo.annotations.customdialog; + } + + hasController(node: mirrorArkts.ETSTypeReference, key_name: string): void { + const ident = node.part?.name; + if (ident && mirrorArkts.isIdentifier(ident) && ident.name === 'CustomDialogController') { + this.customDialogController = key_name; + } + } + + hasCustomDialogController(node: mirrorArkts.ClassProperty): void { + const key = node.key; + if (!(key && mirrorArkts.isIdentifier(key) && node.typeAnnotation)) { + return; + } + const typeAnno = node.typeAnnotation; + if (mirrorArkts.isETSUnionType(typeAnno)) { + for (const type of typeAnno.types) { + if (mirrorArkts.isETSTypeReference(type)) { + this.hasController(type, key.name); + } + } + } else if (mirrorArkts.isETSTypeReference(typeAnno)) { + this.hasController(typeAnno, key.name); + } + } + + /** + * clean up + */ + exit(node: mirrorArkts.AstNode) { + if (mirrorArkts.isETSStructDeclaration(node) || mirrorArkts.isClassDeclaration(node)) { + if (!node.definition || !node.definition.ident || this.scopeInfos.length === 0) return; + if (this.scopeInfos[this.scopeInfos.length - 1]?.name === node.definition.ident.name) { + this.scopeInfos.pop(); + } + } + } + + createImportDeclaration(sourceName: string, importedName: string): void { + const source: mirrorArkts.StringLiteral = mirrorArkts.factory.createStringLiteral(sourceName); + const imported: mirrorArkts.Identifier = mirrorArkts.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'); + } + createAndInsertImportDeclaration( + source, + imported, + imported, + mirrorArkts.Es2pandaImportKinds.IMPORT_KINDS_ALL, + this.program + ); + } + + processETSModule(node: mirrorArkts.ETSModule): mirrorArkts.ETSModule { + /** + * do something to arkui.UserView + */ + if (this.isExternal && this.externalSourceName === ENTRY_POINT_IMPORT_SOURCE_NAME) { + const navInterface = entryFactory.createNavInterface(); + navInterface.modifierFlags = mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_EXPORT; + return mirrorArkts.factory.updateETSModule(node, [...node.statements, navInterface], node.ident, node.getNamespaceFlag(), node.program); + } + if (this.isExternal && this.componentInterfaceCollection.length === 0 && this.entryNames.length === 0) { + return node; + } + const updateStatements: mirrorArkts.Statement[] = []; + if (this.shouldAddLinkIntrinsic) { + const expr = mirrorArkts.factory.createIdentifier(DecoratorIntrinsicNames.LINK); + updateStatements.push(factory.createIntrinsicAnnotationDeclaration({ expr })); + } + /** + * add importation + */ + if (this.componentInterfaceCollection.length > 0) { + 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); + } + /** + * process @Entry + */ + if (this.entryNames.length > 0) { + 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)); + updateStatements.push( + entryFactory.callRegisterNamedRouter( + this.entryRouteName, + this.projectConfig, + this.program?.absoluteName + ) + ); + this.createImportDeclaration(ENTRY_POINT_IMPORT_SOURCE_NAME, NavigationNames.NAVINTERFACE); + } + if (node.program?.fileNameWithExtension === 'new.ets') { + console.log('node.program?.fileNameWithExtension === \'new.ets\'') + console.log(node) + } + if (updateStatements.length > 0) { + return mirrorArkts.factory.updateETSModule(node, [...node.statements, ...updateStatements], node.ident, node.getNamespaceFlag(), node.program); + } + return node; + } + + collectComponentMembers(node: mirrorArkts.ETSStructDeclaration, className: string): void { + if (!node.definition) { + return; + } + const members = filterDefined( + collect( + ...node.definition.body.filter(mirrorArkts.isClassProperty).map((it) => { + if (hasDecoratorName(it, DecoratorNames.PROVIDE)) { + factory.processNoAliasProvideVariable(it); + } + // console.log(it.dumpSrc()) + return this.createInterfaceInnerMember(it); + }) + ) + ); + this.structMembersMap.set(className, members); + } + + generateComponentInterface( + name: string, + modifiers: number, + annotations?: readonly mirrorArkts.AnnotationUsage[] + ): mirrorArkts.TSInterfaceDeclaration { + const interfaceNode = mirrorArkts.factory + .createInterfaceDeclaration( + [], + mirrorArkts.factory.createIdentifier(getCustomComponentOptionsName(name)), + nullptr, + mirrorArkts.factory.createTSInterfaceBody([...(this.structMembersMap.get(name) || [])]), + false, + false + ) + .setAnnotations(annotations ?? []); + interfaceNode.modifiers = modifiers; + // console.log(interfaceNode.dumpSrc()) + return interfaceNode; + } + + createInterfaceInnerMember(member: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty[] { + const originalName: string = expectName(member.key); + const originMember: mirrorArkts.ClassProperty = propertyFactory.createOptionalClassProperty( + originalName, + member, + undefined, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC + ); + const infos = findDecoratorInfos(member); + const buildParamInfo = infos.find((it) => + isDecoratorAnnotation(it.annotation, DecoratorNames.BUILDER_PARAM, true) + ); + if (!!buildParamInfo) { + originMember.setAnnotations([buildParamInfo.annotation.clone()]); + return [originMember]; + } + const targetInfo = infos.find((it) => DECORATOR_TYPE_MAP.has(it.name)); + if (!!targetInfo) { + const newName: string = backingField(originalName); + const newMember: mirrorArkts.ClassProperty = propertyFactory + .createOptionalClassProperty( + newName, + member, + undefined, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC + ) + .setAnnotations([targetInfo.annotation.clone()]); + if (isDecoratorAnnotation(targetInfo.annotation, DecoratorNames.LINK, true)) { + this.shouldAddLinkIntrinsic = true; + originMember.setAnnotations([annotation(DecoratorIntrinsicNames.LINK)]); + } + return [originMember, newMember]; + } + return [originMember]; + } + + + /** + * process custom component + */ + processComponent( + node: mirrorArkts.ClassDeclaration | mirrorArkts.ETSStructDeclaration + ): mirrorArkts.ClassDeclaration | mirrorArkts.ETSStructDeclaration { + const scopeInfo = this.scopeInfos[this.scopeInfos.length - 1]; + const className = node.definition?.ident?.name; + if (!className || scopeInfo?.name !== className) { + return node; + } + if (mirrorArkts.isETSStructDeclaration(node)) { + this.collectComponentMembers(node, className); + } + /** + * generate interface for customComponent + */ + const customComponentInterface = this.generateComponentInterface( + className, + node.modifierFlags | mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_EXPORT, + Object.values(scopeInfo.annotations ?? {}).map((anno) => anno.clone()) + ); + // console.log(customComponentInterface.dumpSrc()) + this.componentInterfaceCollection.push(customComponentInterface); + + /** + * add `__setDialogController__` method + */ + const definition: mirrorArkts.ClassDefinition = node.definition!; + const newDefinitionBody: mirrorArkts.AstNode[] = []; + if (!!scopeInfo.annotations?.entry) { + this.entryNames.push(className); + const { storage, useSharedStorage, routeName } = getEntryParams(definition); + entryFactory.transformStorageParams(storage, useSharedStorage, definition); + if (routeName && routeName.value && mirrorArkts.isStringLiteral(routeName.value)) { + this.entryRouteName = routeName.value.str; + } + } + if (!!scopeInfo.annotations.customdialog) { + const setDialogController = createCustomDialogMethod(this.customDialogController); + newDefinitionBody.push(setDialogController); + this.customDialogController = ''; + } + const newDefinition: mirrorArkts.ClassDefinition = this.createNewDefinition( + node, + className, + definition, + newDefinitionBody + ); + + if (mirrorArkts.isETSStructDeclaration(node)) { + newDefinition.setFromStructModifier() + const _node = mirrorArkts.factory.createClassDeclaration(newDefinition, node.modifierFlags); + _node.startPosition = node.startPosition; + _node.endPosition = node.endPosition; + return _node; + } else { + return mirrorArkts.factory.updateClassDeclaration(node, newDefinition); + } + } + + createStaticMethod(definition: mirrorArkts.ClassDefinition): mirrorArkts.MethodDefinition { + const param: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier( + CustomComponentNames.OPTIONS, + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(getCustomComponentOptionsName(definition.ident!.name)) + ) + ) + ), + false + ); + + const script = mirrorArkts.factory.createScriptFunction( + mirrorArkts.factory.createBlockStatement([mirrorArkts.factory.createReturnStatement()]), + undefined, + [param], + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC | mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC, + undefined, + undefined + ); + + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + mirrorArkts.factory.createIdentifier(CustomComponentNames.BUILDCOMPATIBLENODE), + mirrorArkts.factory.createFunctionExpression(undefined, script), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC | mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC, + false + ); + } + + /** + * append `newDefinitionBody` to the original `definition` + */ + createNewDefinition( + node: mirrorArkts.ClassDeclaration | mirrorArkts.ETSStructDeclaration, + className: string, + definition: mirrorArkts.ClassDefinition, + newDefinitionBody: mirrorArkts.AstNode[] + ): mirrorArkts.ClassDefinition { + const staticMethodBody: mirrorArkts.AstNode[] = []; + const hasExportFlag = + (node.modifierFlags & mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_EXPORT) === + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_EXPORT; + if (hasExportFlag) { + const buildCompatibleNode: mirrorArkts.MethodDefinition = this.createStaticMethod(definition); + if (!!buildCompatibleNode) { + staticMethodBody.push(buildCompatibleNode); + } + } + const scopeInfo = this.scopeInfos[this.scopeInfos.length - 1]; + /** + * add inheritance info + */ + const extendsName: string = scopeInfo.annotations.component + ? CustomComponentNames.COMPONENT_CLASS_NAME + : CustomComponentNames.COMPONENT_V2_CLASS_NAME; + return mirrorArkts.factory + .createClassDefinition( + definition.ident, + undefined, + undefined, // superTypeParams doen't work + [...definition.implements, ...factory.generateImplementsForStruct(scopeInfo.annotations)], + undefined, + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(extendsName), + mirrorArkts.factory.createTSTypeParameterInstantiation([ + factory.createTypeReferenceFromString(className), + factory.createTypeReferenceFromString( + `${CustomComponentNames.COMPONENT_INTERFACE_PREFIX}${className}` + ), + ]) + ) + ), + [ + ...newDefinitionBody, + ...definition.body.map((st: mirrorArkts.AstNode) => factory.PreprocessClassPropertyModifier(st)), + ...staticMethodBody, + ], + definition.modifiers, + mirrorArkts.classDefinitionFlags(definition) | mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_FINAL + ) + .setAnnotations(definition.annotations); + } + + updateEntryPoint(node: mirrorArkts.ClassDeclaration): mirrorArkts.ClassDeclaration { + if (!node.definition) { + return node; + } + return mirrorArkts.factory.updateClassDeclaration( + node, + mirrorArkts.factory.updateClassDefinition( + node.definition, + node.definition.ident, + node.definition.typeParams, + node.definition.superTypeParams, + node.definition.implements, + undefined, + node.definition.super, + [entryFactory.generateRegisterNamedRouter(), ...node.definition.body], + node.definition.modifiers, + mirrorArkts.classDefinitionFlags(node.definition) + ) + ); + } + + processInteropImport(node: mirrorArkts.ETSImportDeclaration): void { + const source = node.source?.str!; + const specifiers = node.specifiers as mirrorArkts.ImportSpecifier[]; + if (this.legacyStructMap.has(source)) { + const structMap = this.legacyStructMap.get(source); + if (!structMap) { + return; + } + for (const specifier of specifiers) { + const name = (specifier as mirrorArkts.ImportSpecifier)!.local!.name; + if (structMap[name]) { + this.legacyCallMap.set(name, structMap[name]); + } + } + } + } + + processInteropCall(node: mirrorArkts.CallExpression): mirrorArkts.CallExpression { + const ident = node.callee; + if (!(ident instanceof mirrorArkts.Identifier)) { + return node; + } + const className = ident.name; + if (this.legacyCallMap.has(className)) { + const path = this.legacyCallMap.get(className)!; + const args = node.arguments; + const context: InteropContext = { + className: className, + path: path, + arguments: args && args.length === 1 && args[0] instanceof mirrorArkts.ObjectExpression ? args[0] : undefined, + }; + return generateInstantiateInterop(context); + } + return node; + } + + visitor(node: mirrorArkts.AstNode): mirrorArkts.AstNode { + this.enter(node); + const newNode = this.visitEachChild(node); + if (mirrorArkts.isETSModule(newNode)) { + /** + * @notice visitEachChild returns a new ETSModule node now, so that we must update the progrom.ast pointer + */ + this.program?.setAst(newNode) + let res = this.processETSModule(newNode); + return res; + } + if (isNewCustomDialogController(newNode)) { + return transformController(newNode as mirrorArkts.ETSNewClassInstanceExpression); + } + /** + * do something to CustomComponent + */ + if ( + mirrorArkts.isETSStructDeclaration(newNode) && + this.scopeInfos.length > 0 && + isComponentStruct(newNode, this.scopeInfos[this.scopeInfos.length - 1]) + ) { + const updateNode = this.processComponent(newNode); + this.exit(newNode); + return updateNode; + } + if ( + mirrorArkts.isClassDeclaration(newNode) && + this.externalSourceName === ENTRY_POINT_IMPORT_SOURCE_NAME && + newNode.definition?.ident?.name === EntryWrapperNames.ENTRY_POINT_CLASS_NAME + ) { + return this.updateEntryPoint(newNode); + } + // process interop code + if (!this.hasLegacy) { + return newNode; + } + if (mirrorArkts.isETSImportDeclaration(newNode)) { + this.processInteropImport(newNode); + } + if (mirrorArkts.isCallExpression(newNode)) { + return this.processInteropCall(newNode); + } + return newNode; + } +} \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/ui-plugins/customdialog.ts b/arkui-plugins/mirror-replace/ui-plugins/customdialog.ts new file mode 100644 index 000000000..488d18f97 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/customdialog.ts @@ -0,0 +1,471 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { factory } from './ui-factory'; +import { + CustomComponentNames, + getCustomComponentOptionsName, +} from './utils'; +import { stat } from 'fs'; +import { createAndInsertImportDeclaration } from '../common/arkts-utils'; + +export function createCustomDialogMethod(controller: string): mirrorArkts.MethodDefinition { + const param: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier( + 'controller', + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(CustomComponentNames.CUSTOMDIALOG_CONTROLLER) + ) + ) + ), + false + ); + + const block = mirrorArkts.factory.createBlockStatement( + (controller.length !== 0) ? [ + mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createAssignmentExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier(controller), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + mirrorArkts.factory.createIdentifier('controller'), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION + ) + ) + ] : [] + ); + + const script = mirrorArkts.factory.createScriptFunction( + block, + undefined, + [param], + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ); + + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + mirrorArkts.factory.createIdentifier(CustomComponentNames.SETDIALOGCONTROLLER_METHOD), + mirrorArkts.factory.createFunctionExpression(undefined, script), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); +} + +// export function transformCallToArrow(value: mirrorArkts.CallExpression): mirrorArkts.ArrowFunctionExpression { +// const className = value.expression.name; +// const args = value.arguments; +// const as_value = mirrorArkts.factory.createExpressionStatement( +// mirrorArkts.factory.updateCallExpression( +// value, +// value.expression, +// value.typeArguments, +// args.length === 0 ? [] : [ +// mirrorArkts.factory.createTSAsExpression( +// args[0], +// mirrorArkts.factory.createTypeReference( +// mirrorArkts.factory.createTypeReferencePart( +// mirrorArkts.factory.createIdentifier(getCustomComponentOptionsName(className)) +// ) +// ), +// false +// ) +// ] +// ) +// ); +// const newValue = mirrorArkts.factory.createArrowFunction( +// factory.createScriptFunction( +// { +// body: mirrorArkts.factory.createBlock([as_value]), +// flags: mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, +// modifiers: mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, +// } +// ) +// ); +// return newValue; +// } + +export function transformController(newInstance: mirrorArkts.ETSNewClassInstanceExpression): mirrorArkts.ETSNewClassInstanceExpression { + const arg = newInstance.getArguments[0]; + if (!mirrorArkts.isObjectExpression(arg)) { + throw new Error('Error CustomDialogOptions'); + } + const properties = arg.properties as mirrorArkts.Property[]; + const property = properties[0]; + const value = property?.value; + if (!(value && mirrorArkts.isCallExpression(value) && mirrorArkts.isIdentifier(value.expression))) { + return newInstance; + } + + const memoArrow = transformCallToArrow(value); + properties[0] = mirrorArkts.Property.updateProperty( + property, + property.key, + memoArrow + ); + const newObj = mirrorArkts.ObjectExpression.updateObjectExpression( + arg, + mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, + properties, + false + ); + const asOptions = mirrorArkts.factory.createTSAsExpression( + newObj, + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(CustomComponentNames.CUSTOMDIALOG_CONTROLLER_OPTIONS) + ) + ), + false + ); + return mirrorArkts.factory.updateETSNewClassInstanceExpression( + newInstance, + newInstance.typeRef, + [asOptions] + ); +} + +// function createVarExpression(key_name: string, isProperty: boolean): mirrorArkts.Expression { +// if (!isProperty) { +// return mirrorArkts.factory.createIdentifier(key_name + '_Temp'); +// } +// return mirrorArkts.factory.createMemberExpression( +// mirrorArkts.factory.createThisExpression(), +// mirrorArkts.factory.createIdentifier(key_name), +// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, +// false, +// false +// ); +// } + +// function createInvoke(key_name: string, isProperty: boolean): mirrorArkts.AstNode[] { +// const statements: mirrorArkts.AstNode[] = []; +// const varExpression = createVarExpression(key_name, isProperty); +// if (!isProperty) { +// const createVar = mirrorArkts.factory.createVariableDeclaration( +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, +// mirrorArkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_CONST, +// [ +// mirrorArkts.factory.createVariableDeclarator( +// mirrorArkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_CONST, +// mirrorArkts.factory.createIdentifier((varExpression as mirrorArkts.Identifier).name), +// mirrorArkts.factory.createIdentifier(key_name) +// ) +// ] +// ); +// statements.push(createVar); +// } +// const invoke = mirrorArkts.factory.createExpressionStatement( +// mirrorArkts.factory.createCallExpression( +// mirrorArkts.factory.createMemberExpression( +// mirrorArkts.factory.createIdentifier('newInstance'), +// mirrorArkts.factory.createIdentifier(CustomComponentNames.SETDIALOGCONTROLLER_METHOD), +// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, +// false, +// false +// ), +// undefined, +// [ +// mirrorArkts.factory.createTSAsExpression( +// varExpression, +// mirrorArkts.factory.createTypeReference( +// mirrorArkts.factory.createTypeReferencePart( +// mirrorArkts.factory.createIdentifier(CustomComponentNames.CUSTOMDIALOG_CONTROLLER) +// ) +// ), +// false +// ) +// ] +// ) +// ); +// statements.push(invoke); +// return statements; +// } + +// function updateStyleBlock(key_name: string, dialogName: string, isProperty: boolean): mirrorArkts.BlockStatement { +// const invokeSetController = createInvoke(key_name, isProperty); +// return mirrorArkts.factory.createBlock( +// [ +// mirrorArkts.factory.createVariableDeclaration( +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, +// mirrorArkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET, +// [ +// mirrorArkts.factory.createVariableDeclarator( +// mirrorArkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET, +// mirrorArkts.factory.createIdentifier('newInstance'), +// mirrorArkts.factory.createETSNewClassInstanceExpression( +// mirrorArkts.factory.createTypeReference( +// mirrorArkts.factory.createTypeReferencePart(mirrorArkts.factory.createIdentifier(dialogName)) +// ), +// [] +// ) +// ) +// ] +// ), +// ...invokeSetController, +// mirrorArkts.factory.createReturnStatement( +// mirrorArkts.factory.createIdentifier('newInstance') +// ) +// ] +// ); +// } + +// function updateStyle(style: mirrorArkts.ArrowFunctionExpression, key_name: string, dialogName: string, isProperty: boolean): mirrorArkts.ArrowFunctionExpression { +// const block = updateStyleBlock(key_name, dialogName, isProperty); +// return mirrorArkts.factory.updateArrowFunction( +// style, +// factory.createScriptFunction( +// { +// body: block, +// returnTypeAnnotation: mirrorArkts.factory.createTypeReference( +// mirrorArkts.factory.createTypeReferencePart( +// mirrorArkts.factory.createIdentifier(dialogName) +// ) +// ), +// flags: mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, +// modifiers: mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE +// } +// ) +// ); +// } + +// export function updateArrow(arrow: mirrorArkts.ArrowFunctionExpression, controller: string, isProperty: boolean): mirrorArkts.ArrowFunctionExpression { +// const scriptFunction = arrow.scriptFunction as mirrorArkts.ScriptFunction; +// const statement = scriptFunction.body!.statements[0] as mirrorArkts.ExpressionStatement; +// const call = statement.expression as mirrorArkts.CallExpression; +// const member = call.expression as mirrorArkts.MemberExpression; + +// const dialogName = member.object.name; +// const styleArrow = call.arguments[1] as mirrorArkts.ArrowFunctionExpression; +// const newStyle = updateStyle(styleArrow, controller, dialogName, isProperty); +// const newScriptFunction = factory.createScriptFunction( +// { +// body: mirrorArkts.factory.createBlock([ +// mirrorArkts.factory.createExpressionStatement( +// mirrorArkts.factory.updateCallExpression( +// call, +// member, +// call.typeArguments, +// [ +// call.arguments[0], +// newStyle, +// ...call.arguments.slice(2) +// ] +// ) +// ) +// ]), +// flags: mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, +// modifiers: mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE +// } +// ); +// const newArrow = mirrorArkts.factory.updateArrowFunction( +// arrow, +// newScriptFunction +// ); +// return newArrow; +// } + +// export function updateCtor(ctor: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { +// const script = ctor.scriptFunction; +// const newScriptFunction = mirrorArkts.factory.createScriptFunction( +// script.body, +// mirrorArkts.factory.createFunctionSignature( +// undefined, +// [ +// ...script.params, +// mirrorArkts.factory.createParameterDeclaration( +// mirrorArkts.factory.createIdentifier( +// 'component', +// mirrorArkts.factory.createTypeReference( +// mirrorArkts.factory.createTypeReferencePart( +// mirrorArkts.factory.createIdentifier('ExtendableComponent') +// ) +// ) +// ), +// undefined +// ) +// ], +// undefined, +// false +// ), +// script.flags, +// script.modifiers +// ); +// const newCtor = mirrorArkts.factory.updateMethodDefinition( +// ctor, +// ctor.kind, +// mirrorArkts.factory.createIdentifier(ctor.name.name), +// newScriptFunction, +// ctor.modifiers, +// false +// ); +// return newCtor; +// } + +// export function updateBody(body: mirrorArkts.Statement[]): mirrorArkts.Statement[] { +// let result: mirrorArkts.Statement[] = []; +// for (const statement of body) { +// if (mirrorArkts.isMethodDefinition(statement) && statement.name.name === 'constructor') { +// const ctor = updateCtor(statement); +// result.push(ctor); +// } else { +// result.push(statement); +// } +// } +// return result; +// } + + +// export function insertImportDeclaration(program: mirrorArkts.Program | undefined): void { +// if (!program) { +// throw Error('Failed to insert import: Transformer has no program'); +// } +// const imported = mirrorArkts.factory.createIdentifier('ExtendableComponent'); +// createAndInsertImportDeclaration( +// mirrorArkts.factory.createStringLiteral('./extendableComponent'), +// imported, +// imported, +// mirrorArkts.Es2pandaImportKinds.IMPORT_KINDS_VALUE, +// program +// ); +// } + +// export function transformDeclaration(node: mirrorArkts.ClassDeclaration): mirrorArkts.ClassDeclaration { +// const definition = node.definition!; +// const newBody = updateBody(definition.body as mirrorArkts.Statement[]); +// const newDefinition = mirrorArkts.factory.updateClassDefinition( +// definition, +// definition?.ident, +// undefined, +// definition.superTypeParams, +// definition.implements, +// undefined, +// definition.super, +// newBody, +// definition.modifiers, +// mirrorArkts.classDefinitionFlags(definition) +// ); +// const declaration = mirrorArkts.factory.updateClassDeclaration( +// node, +// newDefinition +// ); +// return declaration; +// } + +// export function updateNewClassInstanceExpression(node: mirrorArkts.ETSNewClassInstanceExpression, varName: string, +// isProperty: boolean): mirrorArkts.ETSNewClassInstanceExpression { +// const asExression = node.getArguments[0] as mirrorArkts.TSAsExpression; +// const arg = asExression.expr as mirrorArkts.ObjectExpression; +// if (!mirrorArkts.isObjectExpression(arg)) { +// throw new Error('Error CustomDialogOptions'); +// } +// const properties = arg.properties as mirrorArkts.Property[]; +// const builder = properties[0]; +// const builder_value = builder.value as mirrorArkts.ArrowFunctionExpression; +// const newBuilderValue = updateArrow(builder_value, varName, isProperty); +// const newProperty = mirrorArkts.factory.updateProperty( +// builder, +// builder.key, +// newBuilderValue +// ); +// const newObj = mirrorArkts.factory.updateObjectExpression( +// arg, +// mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, +// [newProperty, ...properties.slice(1)], +// false +// ); +// const newAsExpression = mirrorArkts.factory.updateTSAsExpression( +// asExression, +// newObj, +// asExression.typeAnnotation, +// asExression.isConst +// ); +// const typeRef = node.getTypeRef as mirrorArkts.ETSTypeReference; +// const newNode = mirrorArkts.factory.updateETSNewClassInstanceExpression( +// node, +// typeRef, +// [newAsExpression, mirrorArkts.factory.createThisExpression()] +// ); +// return newNode; +// } + +export function isNewCustomDialogController(node: mirrorArkts.AstNode | undefined): boolean { + if (node && mirrorArkts.isETSNewClassInstanceExpression(node) && + /** + * @todo Check if this is equivalent to `node.getTypeRef?.part?.name.name` + */ + (node.typeRef as mirrorArkts.ETSTypeReference)?.baseName?.name === 'CustomDialogController') { + return true; + } + return false; +} + +// function updateVar(node: mirrorArkts.VariableDeclarator): mirrorArkts.VariableDeclarator { +// if (node.flag !== mirrorArkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET) { +// throw Error('Error VariableDeclarator CustomDialogController'); +// } +// return mirrorArkts.factory.updateVariableDeclarator( +// node, +// node.flag, +// node.name, +// mirrorArkts.factory.createUndefinedLiteral() +// ); +// } + +// export function checkCustomDialogController(node: mirrorArkts.BlockStatement): mirrorArkts.BlockStatement { +// const statements = node.statements; +// const newStatements: mirrorArkts.AstNode[] = []; +// for (let i = 0; i < statements.length; i++) { +// const statement = statements[i]; +// if (mirrorArkts.isVariableDeclaration(statement) && statement.declarators.length > 0 && +// isNewCustomDialogController(statement.declarators[0].initializer)) { +// const varDeclare = statement.declarators[0]; +// const varName = varDeclare.name.name; +// const classInstance = varDeclare.initializer; +// const newClass = updateNewClassInstanceExpression(classInstance as mirrorArkts.ETSNewClassInstanceExpression, varName, false); +// const newVar = mirrorArkts.factory.updateVariableDeclaration( +// statement, +// 0, +// statement.declarationKind, +// [updateVar(statement.declarators[0])] +// ); +// newStatements.push(newVar); +// const initVar = mirrorArkts.factory.createAssignmentExpression( +// mirrorArkts.factory.createIdentifier(varName), +// mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, +// newClass +// ); +// const initStatement = mirrorArkts.factory.createExpressionStatement(initVar); +// newStatements.push(initStatement); +// } else { +// newStatements.push(statement); +// } +// } +// return mirrorArkts.factory.updateBlock( +// node, +// newStatements +// ); + +// } \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/ui-plugins/entry-translators/factory.ts b/arkui-plugins/mirror-replace/ui-plugins/entry-translators/factory.ts new file mode 100644 index 000000000..fc87dfc03 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/entry-translators/factory.ts @@ -0,0 +1,479 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror' +import * as path from 'path'; +import { annotation, createAndInsertImportDeclaration } from '../../common/arkts-utils'; +import { ENTRY_POINT_IMPORT_SOURCE_NAME, EntryWrapperNames, NavigationNames } from '../../common/predefines'; +import { ProjectConfig } from '../../common/plugin-context'; +import { factory as uiFactory } from '../ui-factory'; +import { getRelativePagePath } from './utils'; +// import { addMemoAnnotation } from '../../collectors/memo-collectors/utils'; + +export class factory { + // /** + // * insert an 'entry' method to an entry wrapper class. + // * + // * @param node entry wrapper class declaration node. + // */ + // static registerEntryFunction(node: mirrorArkts.ClassDeclaration): mirrorArkts.AstNode { + // const definition: mirrorArkts.ClassDefinition | undefined = node.definition; + // const classname = node?.definition?.ident?.name; + // if (!definition || !classname) { + // throw new Error('Node definition is undefined'); + // } + // const updateClassDef: mirrorArkts.ClassDefinition = mirrorArkts.factory.updateClassDefinition( + // definition, + // definition.ident, + // definition.typeParams, + // definition.superTypeParams, + // definition.implements, + // undefined, + // definition.super, + // [...definition.body, factory.generateEntryFunction(classname)], + // definition.modifiers, + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE + // ); + // return mirrorArkts.factory.updateClassDeclaration(node, updateClassDef); + // } + + // /** + // * insert an 'entry' property to an entry wrapper class. + // * + // * @param node entry wrapper class declaration node. + // * @deprecated + // */ + // static registerEntryProperty(node: mirrorArkts.ClassDeclaration): mirrorArkts.AstNode { + // const definition: mirrorArkts.ClassDefinition | undefined = node.definition; + // const classname = node?.definition?.ident?.name; + // if (!definition || !classname) { + // throw new Error('Node definition is undefined'); + // } + // const updateClassDef: mirrorArkts.ClassDefinition = mirrorArkts.factory.updateClassDefinition( + // definition, + // definition.ident, + // definition.typeParams, + // definition.superTypeParams, + // definition.implements, + // undefined, + // definition.super, + // [...definition.body, factory.generateEntryProperty(classname)], + // definition.modifiers, + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE + // ); + // return mirrorArkts.factory.updateClassDeclaration(node, updateClassDef); + // } + + /** + * create `entry(): void { (); }` class method for the entry wrapper class, + * which calls the struct within the method. + * + * @param name class/struct name that has `@Entry` annotation. + */ + static generateEntryFunction(name: string): mirrorArkts.MethodDefinition { + const exp = mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createCallExpression(mirrorArkts.factory.createIdentifier(name), [], undefined) + ); + const key: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier(EntryWrapperNames.ENTRY_FUNC); + const block = mirrorArkts.factory.createBlockStatement([]); + const entryScript = mirrorArkts.factory + .createScriptFunction( + block, + undefined, + [], + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ); + + const def = mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + key, + mirrorArkts.factory.createFunctionExpression(undefined, entryScript), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); + // console.log("createCallExpression", mirrorArkts.factory.createCallExpression(mirrorArkts.factory.createIdentifier(name), [], undefined)) + // console.log("entryScript: ", entryScript.dumpSrc()) + // console.log("functionExpression: ", mirrorArkts.factory.createFunctionExpression(key, entryScript)) + // console.log("def: ", def.dumpSrc()) + return def; + } + + static generateConstructor(): mirrorArkts.MethodDefinition { + const key: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier('constructor'); + const block = mirrorArkts.factory.createBlockStatement([]) + const entryScript = mirrorArkts.factory + .createScriptFunction( + block, + undefined, [], undefined, false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_CONSTRUCTOR | + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_IMPLICIT_SUPER_CALL_NEEDED, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC | + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_CONSTRUCTOR, + undefined, + undefined + ) + const def = mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_CONSTRUCTOR, + key, + mirrorArkts.factory.createFunctionExpression(key, entryScript), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + false + ); + // console.trace() + // console.log("entryScript: ", entryScript.dumpSrc()) + // console.log("functionExpression: ", mirrorArkts.factory.createFunctionExpression(key, entryScript).dumpSrc()) + // console.log("def: ", def.dumpSrc()) + return def; + } + + // /** + // * create `entry = (): void => { (); }` class property for the entry wrapper class, + // * which calls the struct within the arrow function. + // * + // * @param name class/struct name that has `@Entry` annotation. + // * @deprecated + // */ + // static generateEntryProperty(name: string): mirrorArkts.ClassProperty { + // const exp = mirrorArkts.factory.createExpressionStatement( + // mirrorArkts.factory.createCallExpression(mirrorArkts.factory.createIdentifier(name), undefined, []) + // ); + // const key: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier(EntryWrapperNames.ENTRY_FUNC); + // const block: mirrorArkts.BlockStatement = mirrorArkts.factory.createBlock([exp]); + // const signature: mirrorArkts.FunctionSignature = mirrorArkts.FunctionSignature.createFunctionSignature( + // undefined, + // [], + // mirrorArkts.factory.createPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + // false + // ); + // const entryScript: mirrorArkts.ScriptFunction = mirrorArkts.factory + // .createScriptFunction( + // block, + // signature, + // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC + // ) + // .setIdent(key); + + // const def = mirrorArkts.factory.createClassProperty( + // key, + // mirrorArkts.factory.createArrowFunction(entryScript), + // mirrorArkts.factory.createFunctionType(signature, mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW), + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + // false + // ); + + // return def; + // } + + /** + * create `__EntryWrapper_Entry` entry wrapper class that contains an 'entry' method that + * calls the struct within the method. + * + * @param name class/struct name that has `@Entry` annotation. + */ + static generateEntryWrapper(name: string): mirrorArkts.ClassDeclaration { + const ctor = factory.generateConstructor(); + const definition: mirrorArkts.ClassDefinition = mirrorArkts.factory + .createClassDefinition( + mirrorArkts.factory.createIdentifier(EntryWrapperNames.WRAPPER_CLASS_NAME), + undefined, + undefined, + [], + undefined, + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(EntryWrapperNames.ENTRY_POINT_CLASS_NAME) + ) + ), + [factory.generateEntryFunction(name), ctor], + mirrorArkts.Es2pandaClassDefinitionModifiers.CLASS_DEFINITION_MODIFIERS_CLASS_DECL | + mirrorArkts.Es2pandaClassDefinitionModifiers.CLASS_DEFINITION_MODIFIERS_DECLARATION | + mirrorArkts.Es2pandaClassDefinitionModifiers.CLASS_DEFINITION_MODIFIERS_ID_REQUIRED, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE + ) + .setCtor(ctor as any); + const newClass = mirrorArkts.factory.createClassDeclaration(definition); + newClass.modifierFlags = mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE; + // console.log('classDefinition', definition.dumpSrc()) + // console.log('classDeclaration:', newClass.dumpSrc()) + return newClass; + } + + // /** + // * add `@memo` to all class methods that are named 'entry'. + // * + // * @param node class declaration node + // */ + // static addMemoToEntryWrapperClassMethods(node: mirrorArkts.ClassDeclaration): void { + // node.definition?.body.forEach((member) => { + // if ( + // mirrorArkts.isMethodDefinition(member) && + // !!member.scriptFunction.id && + // member.scriptFunction.id.name === EntryWrapperNames.ENTRY_FUNC + // ) { + // addMemoAnnotation(member.scriptFunction); + // mirrorArkts.NodeCache.getInstance().collect(member); + // } + // }); + // } + + // /** + // * add `@memo` to the class property's value (expecting an arrow function), where the property is named 'entry'. + // * + // * @param node class declaration node + // * @deprecated + // */ + // static addMemoToEntryWrapperPropertyValue(node: mirrorArkts.ClassDeclaration): void { + // node.definition?.body.forEach((member) => { + // if ( + // mirrorArkts.isClassProperty(member) && + // !!member.value && + // mirrorArkts.isArrowFunctionExpression(member.value) && + // !!member.key && + // mirrorArkts.isIdentifier(member.key) && + // member.key.name === EntryWrapperNames.ENTRY_FUNC + // ) { + // member.setAnnotations([annotation('memo')]); + // } + // }); + // } + + // /** + // * create `private _entry_local_storage_ = ;` class property + // * from `{storage: ""}` in `@Entry`'s properties. + // * + // * @param annotation `@Entry` annotation. + // */ + // static createEntryLocalStorageInClass(property: mirrorArkts.ClassProperty) { + // const value = property.value as mirrorArkts.StringLiteral; + // return mirrorArkts.factory.createClassProperty( + // mirrorArkts.factory.createIdentifier(EntryWrapperNames.ENTRY_STORAGE_LOCAL_STORAGE_PROPERTY_NAME), + // mirrorArkts.factory.createIdentifier(value.str), + // undefined, + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + // false + // ); + // } + + /** + * create and insert `import { EntryPoint as EntryPoint } from "@ohos.arkui.UserView";` + * to the top of script's statements. + */ + static createAndInsertEntryPointImport(program?: mirrorArkts.Program) { + const source: mirrorArkts.StringLiteral = mirrorArkts.factory.createStringLiteral(ENTRY_POINT_IMPORT_SOURCE_NAME); + const imported: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier(EntryWrapperNames.ENTRY_POINT_CLASS_NAME); + // Insert this import at the top of the script's statements. + if (!program) { + throw Error('Failed to insert import: Transformer has no program'); + } + createAndInsertImportDeclaration( + source, + imported, + imported, + mirrorArkts.Es2pandaImportKinds.IMPORT_KINDS_ALL, + program + ); + } + + /** + * transform `@Entry` storage params, e.g. `@Entry`({useSharedStorage: ..., storage: ...}) + */ + static transformStorageParams( + storage: mirrorArkts.ClassProperty | undefined, + useSharedStorage: mirrorArkts.ClassProperty | undefined, + definition: mirrorArkts.ClassDefinition + ): void { + if (!storage && !useSharedStorage) { + return; + } + const ctor: mirrorArkts.MethodDefinition | undefined = definition.body.find( + (member) => + mirrorArkts.isMethodDefinition(member) && + member.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_CONSTRUCTOR + ) as mirrorArkts.MethodDefinition | undefined; + if (!ctor) { + return; + } + let sharedArg = mirrorArkts.factory.createBooleanLiteral(false); + if (useSharedStorage && useSharedStorage.value && mirrorArkts.isBooleanLiteral(useSharedStorage.value)) { + sharedArg = useSharedStorage.value; + } + let storageArg: mirrorArkts.UndefinedLiteral | mirrorArkts.CallExpression = mirrorArkts.factory.createUndefinedLiteral(); + if (storage && storage.value && mirrorArkts.isStringLiteral(storage.value)) { + storageArg = mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createIdentifier(storage.value.str), + [], + undefined + ); + } + const superCall = mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createCallExpression(mirrorArkts.factory.createSuperExpression(), [ + sharedArg, + storageArg, + ], undefined) + ); + if (!!ctor.function && ctor.function.body && mirrorArkts.isBlockStatement(ctor.function.body)) { + ctor.function.setBody( + mirrorArkts.factory.updateBlockStatement(ctor.function.body, [...ctor.function.body.statements, superCall]) + ); + } + } + + /** + * helper for callRegisterNamedRouter to generate NavInterface arg + */ + static navInterfaceArg( + projectConfig: ProjectConfig | undefined, + fileAbsName: string | undefined + ): mirrorArkts.TSAsExpression { + const projectRoot = projectConfig?.moduleRootPath + ? path.join(projectConfig.moduleRootPath, 'src', 'main', 'ets') + : ''; + const pageFullPath = getRelativePagePath(projectConfig?.projectPath ?? '', fileAbsName ?? ''); + const pagePath = getRelativePagePath(projectRoot, fileAbsName ?? ''); + return mirrorArkts.factory.createTSAsExpression( + mirrorArkts.factory.createObjectExpression( + mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, + [ + factory.createNavProperty(NavigationNames.BUNDLE_NAME, projectConfig?.bundleName), + factory.createNavProperty(NavigationNames.MODULE_NAME, projectConfig?.moduleName), + factory.createNavProperty(NavigationNames.PAGE_PATH, pagePath), + factory.createNavProperty(NavigationNames.PAGE_FULL_PATH, pageFullPath), + factory.createNavProperty(NavigationNames.INTEGRATED_HSP, projectConfig?.integratedHsp?.toString()), + ], + false + ), + uiFactory.createTypeReferenceFromString(NavigationNames.NAVINTERFACE), + false + ); + } + + /** + * helper for navInterfaceArg to generate class properties, e.g. buneleName: '...' + */ + static createNavProperty(key: NavigationNames, value: string | undefined): mirrorArkts.Property { + return mirrorArkts.factory.createProperty( + mirrorArkts.Es2pandaPropertyKind.PROPERTY_KIND_INIT, + mirrorArkts.factory.createIdentifier(key), + mirrorArkts.factory.createStringLiteral(value ?? ''), + false, + false + ); + } + + /** + * generate __EntryWrapper.RegisterNamedRouter(...) + */ + static callRegisterNamedRouter( + entryRouteName: string | undefined, + projectConfig: ProjectConfig | undefined, + fileAbsName: string | undefined + ): mirrorArkts.ExpressionStatement { + return mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createIdentifier(EntryWrapperNames.WRAPPER_CLASS_NAME), + mirrorArkts.factory.createIdentifier(EntryWrapperNames.REGISTER_NAMED_ROUTER), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + [ + mirrorArkts.factory.createStringLiteral(entryRouteName ?? ''), + mirrorArkts.factory.createETSNewClassInstanceExpression( + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(EntryWrapperNames.WRAPPER_CLASS_NAME) + ) + ), + [] + ), + factory.navInterfaceArg(projectConfig, fileAbsName), + ], + undefined, + ) + ); + } + + /** + * generate interface NavInterface in header arkui.UserView + */ + static createNavInterface(): mirrorArkts.TSInterfaceDeclaration { + return mirrorArkts.factory.createInterfaceDeclaration( + [], + mirrorArkts.factory.createIdentifier(NavigationNames.NAVINTERFACE), + undefined, + mirrorArkts.factory.createTSInterfaceBody([ + this.createClassProp(NavigationNames.BUNDLE_NAME), + this.createClassProp(NavigationNames.MODULE_NAME), + this.createClassProp(NavigationNames.PAGE_PATH), + this.createClassProp(NavigationNames.PAGE_FULL_PATH), + this.createClassProp(NavigationNames.INTEGRATED_HSP), + ]), + false, + false + ); + } + + /** + * helper for createNavInterface to generate class properties + */ + static createClassProp(propName: string): mirrorArkts.ClassProperty { + return mirrorArkts.factory.createClassProperty( + mirrorArkts.factory.createIdentifier(propName), + undefined, + uiFactory.createTypeReferenceFromString('string'), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); + } + + /** + * helper for generateRegisterNamedRouter to generate param decl, e.g: `routerName: string` + */ + static registerRouteParam(name: EntryWrapperNames, type: string): mirrorArkts.ETSParameterExpression { + return mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier(name, uiFactory.createTypeReferenceFromString(type)), + false + ); + } + + /** + * generate generateRegisterNamedRouter method in header arkui.UserView + */ + static generateRegisterNamedRouter(): mirrorArkts.MethodDefinition { + const params = [ + factory.registerRouteParam(EntryWrapperNames.ROUTER_NAME, 'string'), + factory.registerRouteParam(EntryWrapperNames.INSTANCE, EntryWrapperNames.ENTRY_POINT_CLASS_NAME), + factory.registerRouteParam(EntryWrapperNames.PARAM, NavigationNames.NAVINTERFACE), + ]; + return uiFactory.createMethodDefinition({ + key: mirrorArkts.factory.createIdentifier(EntryWrapperNames.REGISTER_NAMED_ROUTER), + kind: mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + function: { + body: mirrorArkts.factory.createBlockStatement([]), + params: params, + flags: mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + modifiers: mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC, + }, + modifiers: mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC, + }); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/entry-translators/utils.ts b/arkui-plugins/mirror-replace/ui-plugins/entry-translators/utils.ts new file mode 100644 index 000000000..da6b74fe8 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/entry-translators/utils.ts @@ -0,0 +1,95 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror' +import * as path from 'path'; +import { factory } from './factory'; +import { isAnnotation } from '../../common/arkts-utils'; +import { StructDecoratorNames, EntryParamNames, EntryWrapperNames } from '../../common/predefines'; + +/** + * @deprecated + */ +export class EntryHandler { + private entryDefClassName: Set; + + private static instance: EntryHandler; + + private constructor() { + this.entryDefClassName = new Set(); + } + + public static getInstance(): EntryHandler { + if (!this.instance) { + this.instance = new EntryHandler(); + } + return this.instance; + } + + public rememberEntryFunction(classname: string): void { + this.entryDefClassName.add(classname); + } + + public createEntryWrapper(): mirrorArkts.ClassDeclaration[] { + let result: mirrorArkts.ClassDeclaration[] = []; + this.entryDefClassName.forEach((classname) => { + result.push(factory.generateEntryWrapper(classname)); + }); + return result; + } +} + +export function isEntryWrapperClass(node: mirrorArkts.AstNode): node is mirrorArkts.ClassDeclaration { + if (!mirrorArkts.isClassDeclaration(node)) return false; + const className = node?.definition?.ident?.name; + if (!className) return false; + return className === EntryWrapperNames.WRAPPER_CLASS_NAME; +} + +/** + * get annotation's properties in `@Entry()`: storage, useSharedStorage, routeName. + * + * @param node class definition node + */ +export function getEntryParams(node: mirrorArkts.ClassDefinition): Record { + const annotation = node.annotations.find((anno) => isAnnotation(anno, StructDecoratorNames.ENTRY)); + const result = { + storage: undefined, + useSharedStorage: undefined, + routeName: undefined, + } as Record; + if (!annotation || !annotation.properties) { + return result; + } + for (const prop of annotation.properties) { + if (mirrorArkts.isClassProperty(prop) && prop.key && mirrorArkts.isIdentifier(prop.key)) { + const name = prop.key.name as EntryParamNames; + if (name in result) { + result[name] = prop; + } + } + } + return result; +} + +/** + * Computes and formats a relative path by removing `.ets` extension and normalizing path separators to `/`. + */ +export function getRelativePagePath(from: string, to: string): string { + return path + .relative(from, to) + .replace(/\\/g, '/') + .replace(/\.ets$/, ''); +} \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/ui-plugins/index.ts b/arkui-plugins/mirror-replace/ui-plugins/index.ts new file mode 100644 index 000000000..afc1ab7ef --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/index.ts @@ -0,0 +1,173 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { ComponentTransformer } from './component-transformer'; +import { Plugins, PluginContext, ProjectConfig } from '../common/plugin-context'; +import { ProgramVisitor } from '../common/program-visitor'; +import { EXTERNAL_SOURCE_PREFIX_NAMES } from '../common/predefines'; +import { debugDump, debugLog, getDumpFileName } from '../common/debug'; +import { copyGlobalFromKoalaWrapper } from '../common/compat-koala-wrapper'; + +export function uiTransform(): Plugins { + return { + name: 'ui-plugin', + parsed: parsedTransform, + checked: checkedTransform, + clean() { + // mirrorArkts.arktsGlobal.clearContext(); + }, + }; +} + +function parsedTransform(this: PluginContext): mirrorArkts.ETSModule | undefined { + copyGlobalFromKoalaWrapper(this); + + let script: mirrorArkts.ETSModule | undefined; + console.log('[UI PLUGIN] AFTER PARSED ENTER'); + // mirrorArkts.Performance.getInstance().memoryTrackerPrintCurrent('ArkTS:Parse'); + // mirrorArkts.Performance.getInstance().memoryTrackerReset(); + // mirrorArkts.Performance.getInstance().startMemRecord('Node:UIPlugin:AfterParse'); + const contextPtr = this.getContextPtr() ?? mirrorArkts.arktsGlobal.compilerContext?.peer; + if (!!contextPtr) { + let program = mirrorArkts.arkts.getOrUpdateGlobalContext(contextPtr).program; + script = program.ast as mirrorArkts.ETSModule; + const cachePath: string | undefined = this.getProjectConfig()?.cachePath; + // const canSkipPhases = program.canSkipPhases(); + debugLog('[BEFORE PARSED SCRIPT] script: ', script.dumpSrc()); + debugDump( + script.dumpSrc(), + getDumpFileName(0, 'SRC', 1, 'UI_AfterParse_Begin'), + true, + cachePath, + program.fileNameWithExtension + ); + // mirrorArkts.Performance.getInstance().createEvent('ui-parsed'); + // program = parsedProgramVisit(program, this, canSkipPhases); + program = parsedProgramVisit(program, this); + script = program.ast as mirrorArkts.ETSModule + // mirrorArkts.Performance.getInstance().stopEvent('ui-parsed', true); + debugLog('[AFTER PARSED SCRIPT] script: ', script.dumpSrc()); + debugDump( + script.dumpSrc(), + getDumpFileName(0, 'SRC', 2, 'UI_AfterParse_End'), + true, + cachePath, + program.fileNameWithExtension + ); + // mirrorArkts.Performance.getInstance().memoryTrackerGetDelta('UIPlugin:AfterParse'); + // mirrorArkts.Performance.getInstance().memoryTrackerReset(); + // mirrorArkts.Performance.getInstance().stopMemRecord('Node:UIPlugin:AfterParse'); + console.log('[UI PLUGIN] AFTER PARSED EXIT'); + return script; + } + console.log('[UI PLUGIN] AFTER PARSED EXIT WITH NO TRANSFORM'); + return script; +} + +function parsedProgramVisit( + program: mirrorArkts.Program, + context: PluginContext, + canSkipPhases: boolean = false +): mirrorArkts.Program { + if (canSkipPhases) { + debugLog('[SKIP PHASE] phase: ui-parsed, moduleName: ', program.moduleName); + } else { + debugLog('[CANT SKIP PHASE] phase: ui-parsed, moduleName: ', program.moduleName); + const componentTransformer = new ComponentTransformer({ + projectConfig: context.getProjectConfig(), + }); + const programVisitor = new ProgramVisitor({ + pluginName: uiTransform.name, + state: mirrorArkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, + visitors: [componentTransformer], + skipPrefixNames: EXTERNAL_SOURCE_PREFIX_NAMES, + pluginContext: context, + }); + program = programVisitor.programVisitor(program); + } + return program; +} + +function checkedTransform(this: PluginContext): mirrorArkts.ETSModule | undefined { + copyGlobalFromKoalaWrapper(this); + + let script: mirrorArkts.ETSModule | undefined; + console.log('[UI PLUGIN] AFTER CHECKED ENTER'); + // mirrorArkts.Performance.getInstance().memoryTrackerPrintCurrent('ArkTS:Check'); + // mirrorArkts.Performance.getInstance().memoryTrackerGetDelta('ArkTS:Check'); + // mirrorArkts.Performance.getInstance().memoryTrackerReset(); + // mirrorArkts.Performance.getInstance().startMemRecord('Node:UIPlugin:UI-AfterCheck'); + const contextPtr = this.getContextPtr() ?? mirrorArkts.arktsGlobal.compilerContext?.peer; + if (!!contextPtr) { + let program = mirrorArkts.arkts.getOrUpdateGlobalContext(contextPtr).program; + script = program.ast as mirrorArkts.ETSModule; + const cachePath: string | undefined = this.getProjectConfig()?.cachePath; + // const canSkipPhases = program.canSkipPhases(); + debugLog('[BEFORE STRUCT SCRIPT] script: ', script.dumpSrc()); + debugDump( + script.dumpSrc(), + getDumpFileName(0, 'SRC', 3, 'UI_AfterCheck_Begin'), + true, + cachePath, + program.fileNameWithExtension + ); + // mirrorArkts.Performance.getInstance().createEvent('ui-checked'); + // program = checkedProgramVisit(program, this, canSkipPhases); + program = checkedProgramVisit(program, this); + script = program.ast as mirrorArkts.ETSModule + // mirrorArkts.Performance.getInstance().stopEvent('ui-checked', true); + debugLog('[AFTER STRUCT SCRIPT] script: ', script.dumpSrc()); + debugDump( + script.dumpSrc(), + getDumpFileName(0, 'SRC', 4, 'UI_AfterCheck_End'), + true, + cachePath, + program.fileNameWithExtension + ); + // mirrorArkts.Performance.getInstance().memoryTrackerGetDelta('UIPlugin:UI-AfterCheck'); + // mirrorArkts.Performance.getInstance().stopMemRecord('Node:UIPlugin:UI-AfterCheck'); + console.log('[UI PLUGIN] AFTER CHECKED EXIT'); + return script; + } + console.log('[UI PLUGIN] AFTER CHECKED EXIT WITH NO TRANSFORM'); + return script; +} + +function checkedProgramVisit( + program: mirrorArkts.Program, + context: PluginContext, + canSkipPhases: boolean = false +): mirrorArkts.Program { + if (canSkipPhases) { + debugLog('[SKIP PHASE] phase: ui-checked, moduleName: ', program.moduleName); + } else { + const projectConfig: ProjectConfig | undefined = context.getProjectConfig(); + if (projectConfig && !projectConfig.appResource) { + projectConfig.ignoreError = true; + } + // const checkedTransformer = new CheckedTransformer(projectConfig); + const programVisitor = new ProgramVisitor({ + pluginName: uiTransform.name, + state: mirrorArkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, + // visitors: [checkedTransformer], + visitors: [], + skipPrefixNames: EXTERNAL_SOURCE_PREFIX_NAMES, + pluginContext: context, + }); + program = programVisitor.programVisitor(program); + } + return program; +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/interop/interop.ts b/arkui-plugins/mirror-replace/ui-plugins/interop/interop.ts new file mode 100644 index 000000000..2490c575f --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/interop/interop.ts @@ -0,0 +1,459 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +// import { ESValueMethodNames, InteroperAbilityNames } from './predefines'; +// import { getCustomComponentOptionsName } from '../utils'; +import { InteropContext } from '../component-transformer'; +// import { createVariableLet, initialArgs} from './initstatevar'; +// import { createProvideInterop, setAndResetFindProvide } from './provide'; +// import { getPropertyESValue, getWrapValue, setPropertyESValue, createEmptyESValue } from './utils'; +// import { ImportCollector } from '../../common/import-collector'; + + +// function paramsLambdaDeclaration(name: string, args?: mirrorArkts.ObjectExpression): mirrorArkts.Statement[] { +// const result: mirrorArkts.Statement[] = []; +// result.push( +// mirrorArkts.factory.createVariableDeclaration( +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, +// mirrorArkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET, +// [ +// mirrorArkts.factory.createVariableDeclarator( +// mirrorArkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET, +// mirrorArkts.factory.createIdentifier(InteroperAbilityNames.PARAMSLAMBDA), +// mirrorArkts.factory.createArrowFunction( +// mirrorArkts.factory.createScriptFunction( +// mirrorArkts.factory.createBlock([mirrorArkts.factory.createReturnStatement( +// args ? args : mirrorArkts.ObjectExpression.createObjectExpression( +// mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, +// [], +// false +// ), +// )]), +// mirrorArkts.factory.createFunctionSignature( +// undefined, +// [], +// mirrorArkts.factory.createTypeReference( +// mirrorArkts.factory.createTypeReferencePart( +// mirrorArkts.factory.createIdentifier(getCustomComponentOptionsName(name)) +// ) +// ), +// false +// ), +// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, +// ) +// ) +// ), + +// ] +// ) +// ); +// return result; +// } + +// function createInitReturn(componentName: string): mirrorArkts.ReturnStatement { +// return mirrorArkts.factory.createReturnStatement( +// mirrorArkts.ObjectExpression.createObjectExpression( +// mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, +// [ +// mirrorArkts.Property.createProperty( +// mirrorArkts.factory.createIdentifier(InteroperAbilityNames.COMPONENT), +// mirrorArkts.factory.createIdentifier(InteroperAbilityNames.COMPONENT) +// ), +// mirrorArkts.Property.createProperty( +// mirrorArkts.factory.createIdentifier('name'), +// mirrorArkts.factory.createStringLiteral(componentName) +// ) +// ], +// false +// ), +// ); +// } + +// function createExtraInfo(properties: string[], value: string[]): mirrorArkts.Statement[] { +// const body: mirrorArkts.AstNode[] = []; +// body.push(createEmptyESValue(InteroperAbilityNames.EXTRAINFO)); +// properties.forEach((prop, index) => { +// const val = value[index]; +// body.push(setPropertyESValue( +// InteroperAbilityNames.EXTRAINFO, +// prop, +// mirrorArkts.factory.createStringLiteral(val)) +// ); +// }); +// return body; +// } + + +// function createGlobal(): mirrorArkts.Statement { +// return mirrorArkts.factory.createVariableDeclaration( +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, +// mirrorArkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET, +// [mirrorArkts.factory.createVariableDeclarator( +// mirrorArkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET, +// mirrorArkts.factory.createIdentifier(InteroperAbilityNames.GLOBAL), +// mirrorArkts.factory.createCallExpression( +// mirrorArkts.factory.createMemberExpression( +// mirrorArkts.factory.createIdentifier(ESValueMethodNames.ESVALUE), +// mirrorArkts.factory.createIdentifier('getGlobal'), +// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, +// false, +// false +// ), +// undefined, +// undefined +// ) +// )] +// ); +// } + +// function createELMTID(): mirrorArkts.Statement[] { +// const body: mirrorArkts.Statement[] = []; +// const viewStackProcessor = getPropertyESValue('viewStackProcessor', InteroperAbilityNames.GLOBAL, 'ViewStackProcessor'); +// body.push(viewStackProcessor); +// const createId = getPropertyESValue('createId', 'viewStackProcessor', 'AllocateNewElmetIdForNextComponent'); +// body.push(createId); +// const elmtId = mirrorArkts.factory.createVariableDeclaration( +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, +// mirrorArkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET, +// [mirrorArkts.factory.createVariableDeclarator( +// mirrorArkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET, +// mirrorArkts.factory.createIdentifier(InteroperAbilityNames.ELMTID), +// mirrorArkts.factory.createCallExpression( +// mirrorArkts.factory.createMemberExpression( +// mirrorArkts.factory.createIdentifier('createId'), +// mirrorArkts.factory.createIdentifier(ESValueMethodNames.INVOKE), +// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, +// false, +// false +// ), +// undefined, +// undefined +// ) +// )] +// ); +// body.push(elmtId); +// return body; +// } + + +// function generateTSASExpression(expression: mirrorArkts.AstNode): mirrorArkts.Expression { +// return mirrorArkts.factory.createTSAsExpression( +// mirrorArkts.factory.createCallExpression( +// mirrorArkts.factory.createMemberExpression( +// expression, +// mirrorArkts.factory.createIdentifier('unwrap'), +// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, +// false, +// false +// ), +// undefined, +// undefined +// ), +// mirrorArkts.factory.createTypeReference( +// mirrorArkts.factory.createTypeReferencePart( +// mirrorArkts.factory.createIdentifier('Object') +// ) +// ), +// false +// ); +// } + +// function newComponent(className: string): mirrorArkts.Statement { +// return createVariableLet( +// InteroperAbilityNames.COMPONENT, +// getWrapValue( +// mirrorArkts.factory.createETSNewClassInstanceExpression( +// mirrorArkts.factory.createIdentifier(className), +// [ +// generateTSASExpression(getWrapValue(mirrorArkts.factory.createUndefinedLiteral())), +// generateTSASExpression(mirrorArkts.factory.createIdentifier(InteroperAbilityNames.PARAM)), +// generateTSASExpression(getWrapValue(mirrorArkts.factory.createUndefinedLiteral())), +// generateTSASExpression(mirrorArkts.factory.createIdentifier(InteroperAbilityNames.ELMTID)), +// mirrorArkts.factory.createTSAsExpression( +// mirrorArkts.factory.createArrowFunction( +// mirrorArkts.factory.createScriptFunction( +// mirrorArkts.factory.createBlock([]), +// mirrorArkts.factory.createFunctionSignature( +// undefined, +// [], +// undefined, +// false +// ), +// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, +// ) +// ), +// mirrorArkts.factory.createTypeReference( +// mirrorArkts.factory.createTypeReferencePart( +// mirrorArkts.factory.createIdentifier('Object') +// ) +// ), +// false +// ), +// generateTSASExpression(mirrorArkts.factory.createIdentifier(InteroperAbilityNames.EXTRAINFO)) +// ] +// ) +// ) +// ); +// } + +// function createComponent(className: string): mirrorArkts.Statement[] { +// const component = newComponent(className); +// const ViewPU = getPropertyESValue('viewPUCreate', InteroperAbilityNames.GLOBAL, 'viewPUCreate'); +// const create = mirrorArkts.factory.createExpressionStatement( +// mirrorArkts.factory.createCallExpression( +// mirrorArkts.factory.createMemberExpression( +// mirrorArkts.factory.createIdentifier('viewPUCreate'), +// mirrorArkts.factory.createIdentifier('invoke'), +// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, +// false, +// false +// ), +// undefined, +// [ +// mirrorArkts.factory.createIdentifier(InteroperAbilityNames.COMPONENT) +// ] +// ) +// ); +// return [component, ViewPU, create]; +// } + + +// function createWrapperBlock(context: InteropContext, varMap: Map, +// updateProp: mirrorArkts.Property[]): mirrorArkts.BlockStatement { +// const enableStateManagementInterop = false; +// const className: string = context.className; +// const path: string = context.path; +// const args: mirrorArkts.ObjectExpression | undefined = context.arguments; +// const index: number = path.indexOf('/'); +// if (index === -1) { +// throw new Error('Error path of Legacy Component.'); +// } +// const initial = [ +// createGlobal(), +// createEmptyESValue(InteroperAbilityNames.PARAM), +// ...(enableStateManagementInterop ? createProvideInterop() : []) +// ]; +// const initialArgsStatement = args ? initialArgs(args, varMap, updateProp) : []; +// return mirrorArkts.factory.createBlock( +// [ +// ...initial, +// ...initialArgsStatement, +// ...createExtraInfo(['page'], [path]), +// ...createELMTID(), +// ...createComponent(className), +// ...(enableStateManagementInterop ? setAndResetFindProvide() : []), +// createInitReturn(className) +// ] +// ); +// } + +// function createInitializer(context: InteropContext, varMap: Map, +// updateProp: mirrorArkts.Property[]): mirrorArkts.ArrowFunctionExpression { +// const block = createWrapperBlock(context, varMap, updateProp); +// return mirrorArkts.factory.createArrowFunction( +// mirrorArkts.factory.createScriptFunction( +// block, +// mirrorArkts.factory.createFunctionSignature( +// undefined, +// [], +// undefined, +// false, +// ), +// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, +// ) +// ); +// } + +// function createUpdateProp(updateProp: mirrorArkts.Property[]): mirrorArkts.Statement[] { +// const result: mirrorArkts.Statement[] = []; +// const updateParam = createEmptyESValue('updateParam'); +// result.push(updateParam); +// updateProp.forEach((prop) => { +// const key = prop.key as mirrorArkts.Identifier; +// const value = prop.value; +// const insertProperty = setPropertyESValue('updateParam', key.name, value!); +// result.push(insertProperty); +// }); +// return result; +// } + +// function updateStateVars(updateProp: mirrorArkts.Property[]): mirrorArkts.Statement[] { +// const insertProp = createUpdateProp(updateProp); +// return [ +// ...insertProp, +// mirrorArkts.factory.createCallExpression( +// mirrorArkts.factory.createMemberExpression( +// mirrorArkts.factory.createIdentifier(InteroperAbilityNames.INSTANCE), +// mirrorArkts.factory.createIdentifier(ESValueMethodNames.INVOKEMETHOD), +// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, +// false, +// false +// ), +// undefined, +// [ +// mirrorArkts.factory.createStringLiteral('updateStateVars'), +// mirrorArkts.factory.createIdentifier('updateParam') +// ] +// ) +// ]; +// } + +// function createUpdater(updateProp: mirrorArkts.Property[]): mirrorArkts.ArrowFunctionExpression { +// const updateState = (updateProp.length !== 0) ? updateStateVars(updateProp) : []; +// return mirrorArkts.factory.createArrowFunction( +// mirrorArkts.factory.createScriptFunction( +// mirrorArkts.factory.createBlock( +// [ +// ...updateState +// ] +// ), +// mirrorArkts.factory.createFunctionSignature( +// undefined, +// [ +// mirrorArkts.factory.createParameterDeclaration( +// mirrorArkts.factory.createIdentifier(InteroperAbilityNames.INSTANCE, +// mirrorArkts.factory.createTypeReference( +// mirrorArkts.factory.createTypeReferencePart( +// mirrorArkts.factory.createIdentifier(ESValueMethodNames.ESVALUE) +// ) +// ) +// ), +// undefined, +// ), +// ], +// undefined, +// false, +// ), +// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, +// ) +// ); +// } + +// function generateVarMap(node: mirrorArkts.Identifier): Map { +// const decl = mirrorArkts.getDecl(node); +// if (!(decl instanceof mirrorArkts.ClassDefinition)) { +// throw Error("can't find legacy class declaration"); +// } +// const result = new Map(); +// const definition = decl; +// const body = definition.body; +// body.forEach(node => { +// if (node instanceof mirrorArkts.ClassProperty && node.key instanceof mirrorArkts.Identifier) { +// const key = node.key.name; +// result.set(key, node); +// } +// }); +// return result; +// } + +function generateStructInfo(context: InteropContext): mirrorArkts.AstNode[] { + const result: mirrorArkts.AstNode[] = [ + mirrorArkts.factory.createStringLiteral(context.path), + context.line ? mirrorArkts.factory.createIdentifier(context.line.toString()) : mirrorArkts.factory.createUndefinedLiteral(), + context.col ? mirrorArkts.factory.createIdentifier(context.col.toString()) : mirrorArkts.factory.createUndefinedLiteral(), + context.arguments ?? mirrorArkts.factory.createUndefinedLiteral() + ]; + return result; +} + +/** + * + * @param {Object} context - Context information about the parsed CustomComponent. + * @param {string} context.className - Name of the CustomComponent class. + * @param {string} context.path - File path where the CustomComponent is located. + * @param {number} [context.line] - Line number of the CustomComponent in the file (optional). + * @param {number} [context.col] - Column number of the CustomComponent in the file (optional). + * @param {Object} [context.arguments] - Additional arguments passed to the CustomComponent (optional). + * @returns {Object} .instantiate_Interop. + */ +export function generateInstantiateInterop(context: InteropContext): mirrorArkts.CallExpression { + return mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createIdentifier(context.className), + mirrorArkts.factory.createIdentifier('instantiate_Interop'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + generateStructInfo(context) as mirrorArkts.Expression[], + undefined + ); +} + +// /** +// * +// * @param node +// * @returns {boolean} Checks if a given CallExpression represents a call to .instantiate_Interop. +// */ +// export function isArkUICompatible(node: mirrorArkts.AstNode): boolean { +// if (node instanceof mirrorArkts.CallExpression && node.expression instanceof mirrorArkts.MemberExpression && +// node.expression.property instanceof mirrorArkts.Identifier && +// node.expression.property.name === 'instantiate_Interop') { +// ImportCollector.getInstance().collectSource(InteroperAbilityNames.ARKUICOMPATIBLE, InteroperAbilityNames.INTEROP); +// ImportCollector.getInstance().collectImport(InteroperAbilityNames.ARKUICOMPATIBLE); +// ImportCollector.getInstance().collectSource(InteroperAbilityNames.BINDPROVIDEINTEROP, InteroperAbilityNames.INTEROP); +// ImportCollector.getInstance().collectImport(InteroperAbilityNames.BINDPROVIDEINTEROP); +// ImportCollector.getInstance().collectSource(InteroperAbilityNames.GETCOMPATIBLESTATE, InteroperAbilityNames.INTEROP); +// ImportCollector.getInstance().collectImport(InteroperAbilityNames.GETCOMPATIBLESTATE); +// return true; +// } +// return false; +// } + + +// /** +// * +// * @param node +// * @returns After Checked, transform instantiate_Interop -> ArkUICompatible +// */ +// export function generateArkUICompatible(node: mirrorArkts.CallExpression): mirrorArkts.CallExpression { +// const classInterop = (node.expression as mirrorArkts.MemberExpression).object as mirrorArkts.Identifier; +// const className = classInterop.name; +// const args = node.arguments; +// const path = (args[0] as mirrorArkts.StringLiteral).str; +// const line = args[1] instanceof mirrorArkts.UndefinedLiteral ? undefined : (args[1] as mirrorArkts.NumberLiteral).value; +// const col = args[2] instanceof mirrorArkts.UndefinedLiteral ? undefined : (args[2] as mirrorArkts.NumberLiteral).value; +// const options = args[3] instanceof mirrorArkts.UndefinedLiteral ? undefined : args[3] as mirrorArkts.ObjectExpression; +// const context: InteropContext = { +// className: className, +// path: path, +// line: line, +// col: col, +// arguments: options +// }; + +// const varMap: Map = generateVarMap(classInterop); +// const updateProp:mirrorArkts.Property[] = []; +// const initializer = createInitializer(context, varMap, updateProp); +// const updater = createUpdater(updateProp); +// const result = mirrorArkts.factory.updateCallExpression( +// node, +// mirrorArkts.factory.createIdentifier(InteroperAbilityNames.ARKUICOMPATIBLE), +// undefined, +// [ +// initializer, +// updater, +// ] +// ); +// mirrorArkts.NodeCache.getInstance().collect(result); +// return result; +// } \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/factory.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/factory.ts new file mode 100644 index 000000000..f5c75220c --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/factory.ts @@ -0,0 +1,799 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { DecoratorNames, DECORATOR_TYPE_MAP, StateManagementTypes } from '../../common/predefines'; +import { addMemoAnnotation, findCanAddMemoFromTypeAnnotation } from '../../collectors/memo-collectors/utils'; +import { collectStateManagementTypeImport } from './utils'; + +export class factory { + // /** + // * generate an substitution for optional expression ?., e.g. `{let _tmp = xxx; _tmp == null ? undefined : xxx}`. + // * + // * @param object item before ?.. + // * @param key item after ?.. + // */ + // static createBlockStatementForOptionalExpression( + // object: mirrorArkts.AstNode, + // key: string, + // isCall: boolean = false + // ): mirrorArkts.Expression { + // let id = GenSymGenerator.getInstance().id(key); + // const statements: mirrorArkts.Statement[] = [ + // factory.generateLetVariableDecl(mirrorArkts.factory.createIdentifier(id), object), + // factory.generateTernaryExpression(id, key, isCall), + // ]; + // return mirrorArkts.factory.createBlockExpression(statements); + // } + + // /** + // * generate a variable declaration, e.g. `let = `; + // * + // * @param left left expression. + // * @param right right expression. + // */ + // static generateLetVariableDecl(left: mirrorArkts.Identifier, right: mirrorArkts.AstNode): mirrorArkts.VariableDeclaration { + // return mirrorArkts.factory.createVariableDeclaration( + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + // mirrorArkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET, + // [ + // mirrorArkts.factory.createVariableDeclarator( + // mirrorArkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET, + // left, + // right + // ), + // ] + // ); + // } + + // /** + // * generate a ternary expression, e.g. ` ? : `; + // * + // * @param testLeft the left hand of the test condition. + // * @param key item after ?. + // */ + // static generateTernaryExpression( + // testLeft: string, + // key: string, + // isCall: boolean = false + // ): mirrorArkts.ExpressionStatement { + // const test = mirrorArkts.factory.createBinaryExpression( + // mirrorArkts.factory.createIdentifier(testLeft), + // mirrorArkts.factory.createNullLiteral(), + // mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_EQUAL + // ); + // const consequent: mirrorArkts.Expression = mirrorArkts.factory.createUndefinedLiteral(); + // const alternate: mirrorArkts.MemberExpression = mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createIdentifier(testLeft), + // mirrorArkts.factory.createIdentifier(key), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ); + // return mirrorArkts.factory.createExpressionStatement( + // mirrorArkts.factory.createConditionalExpression( + // test, + // consequent, + // isCall ? mirrorArkts.factory.createCallExpression(alternate, undefined, undefined) : alternate + // ) + // ); + // } + + // /** + // * generate an substitution for two optional expression ?., e.g. a?.b?.c. + // * + // * @param node entry wrapper class declaration node. + // */ + // static createDoubleBlockStatementForOptionalExpression( + // object: mirrorArkts.AstNode, + // key1: string, + // key2: string + // ): mirrorArkts.Expression { + // let id = GenSymGenerator.getInstance().id(key1); + // let initial: mirrorArkts.Expression = factory.createBlockStatementForOptionalExpression(object, key1); + // const statements: mirrorArkts.Statement[] = [ + // factory.generateLetVariableDecl(mirrorArkts.factory.createIdentifier(id), initial), + // factory.generateTernaryExpression(id, key2), + // ]; + // return mirrorArkts.factory.createBlockExpression(statements); + // } + + // /** + // * generate an memberExpression with nonNull or optional, e.g. object.property, object?.property or object!.property + // * + // * @param object item before point. + // * @param property item after point. + // */ + // static createNonNullOrOptionalMemberExpression( + // object: string, + // property: string, + // optional: boolean, + // nonNull: boolean + // ): mirrorArkts.Expression { + // const objectNode: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier(object); + // return mirrorArkts.factory.createMemberExpression( + // nonNull ? mirrorArkts.factory.createTSNonNullExpression(objectNode) : objectNode, + // mirrorArkts.factory.createIdentifier(property), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // optional + // ); + // } + + // /* + // * create `(): => { }`. + // */ + // static createArrowFunctionWithParamsAndBody( + // typeParams: mirrorArkts.TSTypeParameterDeclaration | undefined, + // params: mirrorArkts.Expression[] | undefined, + // returnType: mirrorArkts.TypeNode | undefined, + // hasReceiver: boolean, + // bodyStatementsList: mirrorArkts.Statement[] + // ): mirrorArkts.ArrowFunctionExpression { + // return mirrorArkts.factory.createArrowFunction( + // mirrorArkts.factory.createScriptFunction( + // mirrorArkts.BlockStatement.createBlockStatement(bodyStatementsList), + // mirrorArkts.factory.createFunctionSignature(typeParams, params ? params : [], returnType, hasReceiver), + // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE + // ) + // ); + // } + + // /* + // * create @Watch callback, e.g. (propertyName: string): void => {this.(propertyName)}. + // */ + // static createWatchCallback(callbackName: string): mirrorArkts.ArrowFunctionExpression { + // return factory.createArrowFunctionWithParamsAndBody( + // undefined, + // [ + // mirrorArkts.factory.createParameterDeclaration( + // mirrorArkts.factory.createIdentifier('_', UIFactory.createTypeReferenceFromString('string')), + // undefined + // ), + // ], + // mirrorArkts.factory.createPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + // false, + // [ + // mirrorArkts.factory.createExpressionStatement( + // mirrorArkts.factory.createCallExpression(factory.generateThisCall(callbackName), undefined, [ + // mirrorArkts.factory.createIdentifier('_'), + // ]) + // ), + // ] + // ); + // } + + // /* + // * create this. with optional or nonNullable. + // */ + // static generateThisCall(name: string, optional: boolean = false, nonNull: boolean = false): mirrorArkts.Expression { + // const member: mirrorArkts.Expression = mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createThisExpression(), + // mirrorArkts.factory.createIdentifier(`${name}`), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // optional + // ); + // return nonNull ? mirrorArkts.factory.createTSNonNullExpression(member) : member; + // } + + // /* + // * create `initializers!.!.()`. + // */ + // static createBackingGetOrSetCall( + // newName: string, + // getOrSet: string, + // args: mirrorArkts.AstNode[] | undefined + // ): mirrorArkts.CallExpression { + // return mirrorArkts.factory.createCallExpression( + // mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createTSNonNullExpression( + // factory.createNonNullOrOptionalMemberExpression('initializers', newName, false, true) + // ), + // mirrorArkts.factory.createIdentifier(getOrSet), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ), + // undefined, + // args + // ); + // } + + // /* + // * create `new ()`. + // */ + // static createNewDecoratedInstantiate( + // className: string, + // typeAnnotation: mirrorArkts.TypeNode | undefined, + // args: mirrorArkts.Expression[] | undefined + // ): mirrorArkts.ETSNewClassInstanceExpression { + // return mirrorArkts.factory.createETSNewClassInstanceExpression( + // mirrorArkts.factory.createTypeReference( + // mirrorArkts.factory.createTypeReferencePart( + // mirrorArkts.factory.createIdentifier(className), + // mirrorArkts.factory.createTSTypeParameterInstantiation(typeAnnotation ? [typeAnnotation.clone()] : []) + // ) + // ), + // args?.length ? args : [] + // ); + // } + + // /* + // * create `StateMgmtFactory.(this, ...);`. + // */ + // static generateStateMgmtFactoryCall( + // makeType: StateManagementTypes, + // typeArguments: mirrorArkts.TypeNode | undefined, + // args: mirrorArkts.AstNode[], + // argsContainsThis: boolean + // ): mirrorArkts.CallExpression { + // collectStateManagementTypeImport(StateManagementTypes.STATE_MANAGEMENT_FACTORY); + // return mirrorArkts.factory.createCallExpression( + // mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createIdentifier(StateManagementTypes.STATE_MANAGEMENT_FACTORY), + // mirrorArkts.factory.createIdentifier(makeType), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ), + // typeArguments ? [typeArguments] : undefined, + // [...(argsContainsThis ? [mirrorArkts.factory.createThisExpression()] : []), ...args] + // ); + // } + + // /* + // * create if statement in __updateStruct method. + // */ + // static createIfInUpdateStruct( + // originalName: string, + // member: mirrorArkts.Expression, + // args: mirrorArkts.AstNode[] + // ): mirrorArkts.IfStatement { + // const binaryItem = mirrorArkts.factory.createBinaryExpression( + // factory.createBlockStatementForOptionalExpression( + // mirrorArkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME), + // originalName + // ), + // mirrorArkts.factory.createUndefinedLiteral(), + // mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_STRICT_EQUAL + // ); + // return mirrorArkts.factory.createIfStatement( + // binaryItem, + // mirrorArkts.factory.createBlock([ + // mirrorArkts.factory.createExpressionStatement(mirrorArkts.factory.createCallExpression(member, undefined, args)), + // ]) + // ); + // } + + // static judgeIfAddWatchFunc(args: mirrorArkts.Expression[], property: mirrorArkts.ClassProperty): void { + // if (hasDecorator(property, DecoratorNames.WATCH)) { + // const watchStr: string | undefined = getValueInAnnotation(property, DecoratorNames.WATCH); + // if (watchStr) { + // args.push(factory.createWatchCallback(watchStr)); + // } + // } + // } + + static createOptionalClassProperty( + name: string, + property: mirrorArkts.ClassProperty, + stageManagementType: StateManagementTypes | undefined, + modifiers: mirrorArkts.Es2pandaModifierFlags, + needMemo: boolean = false + ): mirrorArkts.ClassProperty { + const newType: mirrorArkts.TypeNode | undefined = property.typeAnnotation?.clone(); + // if (needMemo && findCanAddMemoFromTypeAnnotation(newType)) { + // addMemoAnnotation(newType); + // } + const newProperty = mirrorArkts.factory.createClassProperty( + mirrorArkts.factory.createIdentifier(name), + undefined, + !!stageManagementType ? factory.createStageManagementType(stageManagementType, property) : newType, + modifiers, + false + ); + return mirrorArkts.classPropertySetOptional(newProperty, true); + } + + static createStageManagementType( + stageManagementType: StateManagementTypes, + property: mirrorArkts.ClassProperty + ): mirrorArkts.ETSTypeReference { + collectStateManagementTypeImport(stageManagementType); + return mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(stageManagementType), + mirrorArkts.factory.createTSTypeParameterInstantiation([ + property.typeAnnotation ? property.typeAnnotation.clone() : mirrorArkts.factory.createETSUndefinedType(), + ]) + ) + ); + } + + // /* + // * create watch related members in Observed/Track classes + // */ + // static createWatchMembers(): mirrorArkts.AstNode[] { + // const subscribedWatches: mirrorArkts.ClassProperty = mirrorArkts.factory.createClassProperty( + // mirrorArkts.factory.createIdentifier('subscribedWatches'), + // factory.generateStateMgmtFactoryCall(StateManagementTypes.MAKE_SUBSCRIBED_WATCHES, undefined, [], false), + // mirrorArkts.factory.createTypeReference( + // mirrorArkts.factory.createTypeReferencePart( + // mirrorArkts.factory.createIdentifier(StateManagementTypes.SUBSCRIBED_WATCHES) + // ) + // ), + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + // false + // ); + // subscribedWatches.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); + // collectStateManagementTypeImport(StateManagementTypes.SUBSCRIBED_WATCHES); + + // const addWatchSubscriber = factory.createWatchMethod( + // 'addWatchSubscriber', + // mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID, + // 'watchId', + // StateManagementTypes.WATCH_ID_TYPE, + // false + // ); + // const removeWatchSubscriber = factory.createWatchMethod( + // 'removeWatchSubscriber', + // mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_BOOLEAN, + // 'watchId', + // StateManagementTypes.WATCH_ID_TYPE, + // true + // ); + // collectStateManagementTypeImport(StateManagementTypes.WATCH_ID_TYPE); + + // const executeOnSubscribingWatches = factory.createWatchMethod( + // 'executeOnSubscribingWatches', + // mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID, + // 'propertyName', + // 'string', + // false + // ); + + // return [subscribedWatches, addWatchSubscriber, removeWatchSubscriber, executeOnSubscribingWatches]; + // } + + // /* + // * helper for createWatchMembers to create watch methods + // */ + // static createWatchMethod( + // methodName: string, + // returnType: mirrorArkts.Es2pandaPrimitiveType, + // paramName: string, + // paramType: string, + // isReturnStatement: boolean + // ): mirrorArkts.MethodDefinition { + // return mirrorArkts.factory.createMethodDefinition( + // mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + // mirrorArkts.factory.createIdentifier(methodName), + // mirrorArkts.factory.createScriptFunction( + // mirrorArkts.factory.createBlock([ + // isReturnStatement + // ? mirrorArkts.factory.createReturnStatement( + // mirrorArkts.factory.createCallExpression( + // factory.thisSubscribedWatchesMember(methodName), + // undefined, + // [mirrorArkts.factory.createIdentifier(paramName)] + // ) + // ) + // : mirrorArkts.factory.createExpressionStatement( + // mirrorArkts.factory.createCallExpression( + // factory.thisSubscribedWatchesMember(methodName), + // undefined, + // [mirrorArkts.factory.createIdentifier(paramName)] + // ) + // ), + // ]), + // mirrorArkts.factory.createFunctionSignature( + // undefined, + // [ + // mirrorArkts.factory.createParameterDeclaration( + // mirrorArkts.factory.createIdentifier( + // paramName, + // mirrorArkts.factory.createTypeReference( + // mirrorArkts.factory.createTypeReferencePart(mirrorArkts.factory.createIdentifier(paramType)) + // ) + // ), + // undefined + // ), + // ], + // mirrorArkts.factory.createPrimitiveType(returnType), + // false + // ), + // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC + // ), + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + // false + // ); + // } + + // /* + // * helper for createWatchMethod, generates this.subscribedWatches.xxx + // */ + // static thisSubscribedWatchesMember(member: string): mirrorArkts.MemberExpression { + // return mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createThisExpression(), + // mirrorArkts.factory.createIdentifier('subscribedWatches'), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ), + // mirrorArkts.factory.createIdentifier(member), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ); + // } + + // /* + // * create ____V1RenderId related members in Observed/Track classes + // */ + // static createV1RenderIdMembers(): mirrorArkts.AstNode[] { + // const v1RenderId: mirrorArkts.ClassProperty = mirrorArkts.factory.createClassProperty( + // mirrorArkts.factory.createIdentifier('____V1RenderId'), + // mirrorArkts.factory.createNumericLiteral(0), + // mirrorArkts.factory.createTypeReference( + // mirrorArkts.factory.createTypeReferencePart( + // mirrorArkts.factory.createIdentifier(StateManagementTypes.RENDER_ID_TYPE) + // ) + // ), + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + // false + // ); + // v1RenderId.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); + // collectStateManagementTypeImport(StateManagementTypes.RENDER_ID_TYPE); + // const setV1RenderId: mirrorArkts.MethodDefinition = factory.setV1RenderId(); + // return [v1RenderId, setV1RenderId]; + // } + + // /* + // * helper for createV1RenderIdMembers to generate setV1RenderId method + // */ + // static setV1RenderId(): mirrorArkts.MethodDefinition { + // const assignRenderId: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( + // mirrorArkts.factory.createAssignmentExpression( + // mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createThisExpression(), + // mirrorArkts.factory.createIdentifier('____V1RenderId'), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ), + // mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + // mirrorArkts.factory.createIdentifier('renderId') + // ) + // ); + // const funcSig: mirrorArkts.FunctionSignature = mirrorArkts.factory.createFunctionSignature( + // undefined, + // [ + // mirrorArkts.factory.createParameterDeclaration( + // mirrorArkts.factory.createIdentifier( + // 'renderId', + // mirrorArkts.factory.createTypeReference( + // mirrorArkts.factory.createTypeReferencePart( + // mirrorArkts.factory.createIdentifier(StateManagementTypes.RENDER_ID_TYPE) + // ) + // ) + // ), + // undefined + // ), + // ], + // mirrorArkts.factory.createPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + // false + // ); + // return mirrorArkts.factory.createMethodDefinition( + // mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + // mirrorArkts.factory.createIdentifier('setV1RenderId'), + // mirrorArkts.factory.createScriptFunction( + // mirrorArkts.factory.createBlock([assignRenderId]), + // funcSig, + // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC + // ), + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + // false + // ); + // } + + // /* + // * create conditionalAddRef method in Observed/Track classes + // */ + // static conditionalAddRef(): mirrorArkts.MethodDefinition { + // const funcSig: mirrorArkts.FunctionSignature = mirrorArkts.factory.createFunctionSignature( + // undefined, + // [ + // mirrorArkts.factory.createParameterDeclaration( + // mirrorArkts.factory.createIdentifier( + // 'meta', + // mirrorArkts.factory.createTypeReference( + // mirrorArkts.factory.createTypeReferencePart( + // mirrorArkts.factory.createIdentifier(StateManagementTypes.MUTABLE_STATE_META) + // ) + // ) + // ), + // undefined + // ), + // ], + // mirrorArkts.factory.createPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + // false + // ); + // collectStateManagementTypeImport(StateManagementTypes.MUTABLE_STATE_META); + // const shouldAddRef: mirrorArkts.IfStatement = factory.shouldAddRef(); + // return mirrorArkts.factory.createMethodDefinition( + // mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + // mirrorArkts.factory.createIdentifier('conditionalAddRef'), + // mirrorArkts.factory.createScriptFunction( + // mirrorArkts.factory.createBlock([shouldAddRef]), + // funcSig, + // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PROTECTED + // ), + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PROTECTED, + // false + // ); + // } + + // /* + // * helper for conditionalAddRef to generate shouldAddRef method + // */ + // static shouldAddRef(): mirrorArkts.IfStatement { + // const test: mirrorArkts.CallExpression = mirrorArkts.factory.createCallExpression( + // mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createIdentifier(StateManagementTypes.OBSERVE), + // mirrorArkts.factory.createIdentifier('shouldAddRef'), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ), + // undefined, + // [ + // mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createThisExpression(), + // mirrorArkts.factory.createIdentifier('____V1RenderId'), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ), + // ] + // ); + // collectStateManagementTypeImport(StateManagementTypes.OBSERVE); + // const consequent: mirrorArkts.BlockStatement = mirrorArkts.factory.createBlock([ + // mirrorArkts.factory.createExpressionStatement( + // mirrorArkts.factory.createCallExpression( + // mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createIdentifier('meta'), + // mirrorArkts.factory.createIdentifier('addRef'), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ), + // undefined, + // undefined + // ) + // ), + // ]); + // return mirrorArkts.factory.createIfStatement(test, consequent); + // } + + // /* + // * helper to create meta field in classes with only @Observe and no @Track + // */ + // static createMetaInObservedClass(): mirrorArkts.ClassProperty { + // collectStateManagementTypeImport(StateManagementTypes.MUTABLE_STATE_META); + // const meta = mirrorArkts.factory.createClassProperty( + // mirrorArkts.factory.createIdentifier(StateManagementTypes.META), + // factory.generateStateMgmtFactoryCall(StateManagementTypes.MAKE_MUTABLESTATE_META, undefined, [], false), + // mirrorArkts.factory.createTypeReference( + // mirrorArkts.factory.createTypeReferencePart( + // mirrorArkts.factory.createIdentifier(StateManagementTypes.MUTABLE_STATE_META) + // ) + // ), + // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + // false + // ); + // meta.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); + // return meta; + // } + + // /** + // * add `@memo` to the `@Builder` methods in class. + // */ + // static addMemoToBuilderClassMethod(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + // if (hasDecorator(method, DecoratorNames.BUILDER)) { + // removeDecorator(method, DecoratorNames.BUILDER); + // addMemoAnnotation(method.scriptFunction); + // } + // return method; + // } + + // static createStorageLinkStateValue( + // property: mirrorArkts.ClassProperty, + // localStorageporpValueStr: string + // ): mirrorArkts.MemberExpression { + // return mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createCallExpression( + // mirrorArkts.factory.createIdentifier(StateManagementTypes.STORAGE_LINK_STATE), + // property.typeAnnotation ? [property.typeAnnotation] : [], + // [ + // mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createThisExpression(), + // mirrorArkts.factory.createIdentifier('_entry_local_storage_'), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ), + // mirrorArkts.factory.createStringLiteral(localStorageporpValueStr), + // property.value ?? mirrorArkts.factory.createUndefinedLiteral(), + // ] + // ), + // mirrorArkts.factory.createIdentifier('value'), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ); + // } + + // /** + // * wrap interface non-undefined property type `T` to ``. + // */ + // static wrapInterfacePropertyType(type: mirrorArkts.TypeNode, wrapTypeName: StateManagementTypes): mirrorArkts.TypeNode { + // if (mirrorArkts.isETSUnionType(type)) { + // return mirrorArkts.factory.updateUnionType(type, [ + // mirrorArkts.factory.createTypeReference( + // mirrorArkts.factory.createTypeReferencePart( + // mirrorArkts.factory.createIdentifier(wrapTypeName), + // mirrorArkts.factory.createTSTypeParameterInstantiation([type.types[0]]) + // ) + // ), + // type.types[1], + // ]); + // } + // return type; + // } + + // /** + // * wrap interface property parameter that has non-undefined type `T` to ``. + // */ + // static wrapInterfacePropertyParamExpr( + // param: mirrorArkts.Expression, + // wrapTypeName: StateManagementTypes + // ): mirrorArkts.Expression { + // if (!mirrorArkts.isEtsParameterExpression(param)) { + // return param; + // } + // if (!param.type || !mirrorArkts.isETSUnionType(param.type)) { + // return param; + // } + // return mirrorArkts.factory.updateParameterDeclaration( + // param, + // mirrorArkts.factory.createIdentifier( + // param.identifier.name, + // factory.wrapInterfacePropertyType(param.type, wrapTypeName) + // ), + // param.initializer + // ); + // } + + // static wrapStateManagementTypeToType( + // type: mirrorArkts.TypeNode | undefined, + // decoratorName: DecoratorNames + // ): mirrorArkts.TypeNode | undefined { + // let newType: mirrorArkts.TypeNode | undefined; + // let wrapTypeName: StateManagementTypes | undefined; + // if (!!type && !!(wrapTypeName = DECORATOR_TYPE_MAP.get(decoratorName))) { + // newType = factory.wrapInterfacePropertyType(type, wrapTypeName); + // collectStateManagementTypeImport(wrapTypeName); + // } + // return newType; + // } + + // static wrapStateManagementTypeToParam( + // param: mirrorArkts.Expression | undefined, + // decoratorName: DecoratorNames + // ): mirrorArkts.Expression | undefined { + // let newParam: mirrorArkts.Expression | undefined; + // let wrapTypeName: StateManagementTypes | undefined; + // if (!!param && !!(wrapTypeName = DECORATOR_TYPE_MAP.get(decoratorName))) { + // newParam = factory.wrapInterfacePropertyParamExpr(param, wrapTypeName); + // collectStateManagementTypeImport(wrapTypeName); + // } + // return newParam; + // } + + // /** + // * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + // * to ` | undefined`, where `` is getting from `DecoratorName`; + // * + // * @param method expecting getter with decorator annotation and a setter with decorator annotation in the overloads. + // */ + // static wrapStateManagementTypeToMethodInInterface( + // method: mirrorArkts.MethodDefinition, + // decorator: DecoratorNames + // ): mirrorArkts.MethodDefinition { + // if (method.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET) { + // const newType: mirrorArkts.TypeNode | undefined = factory.wrapStateManagementTypeToType( + // method.scriptFunction.returnTypeAnnotation, + // decorator + // ); + // const newOverLoads = method.overloads.map((overload) => { + // if (mirrorArkts.isMethodDefinition(overload)) { + // return factory.wrapStateManagementTypeToMethodInInterface(overload, decorator); + // } + // return overload; + // }); + // method.setOverloads(newOverLoads); + // removeDecorator(method, decorator); + // if (!!newType) { + // method.scriptFunction.setReturnTypeAnnotation(newType); + // } + // } else if (method.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET) { + // const newParam: mirrorArkts.Expression | undefined = factory.wrapStateManagementTypeToParam( + // method.scriptFunction.params.at(0), + // decorator + // ); + // removeDecorator(method, decorator); + // if (!!newParam) { + // return UIFactory.updateMethodDefinition(method, { function: { params: [newParam] } }); + // } + // } + // return method; + // } + + // /** + // * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + // * to ` | undefined`, where `` is getting from `DecoratorName`; + // * + // * @param property expecting property with decorator annotation. + // */ + // static wrapStateManagementTypeToPropertyInInterface( + // property: mirrorArkts.ClassProperty, + // decorator: DecoratorNames + // ): mirrorArkts.ClassProperty { + // const newType: mirrorArkts.TypeNode | undefined = factory.wrapStateManagementTypeToType( + // property.typeAnnotation, + // decorator + // ); + // removeDecorator(property, decorator); + // if (!!newType) { + // property.setTypeAnnotation(newType); + // } + // return property; + // } + + // /** + // * create `Type.from()` when translating `@StorageLink`, `@StorageProp`, `@LocalStorageLink` + // * + // * @param typeAnnotation expecting property's original type annotation. + // */ + // static createTypeFrom(typeAnnotation: mirrorArkts.TypeNode | undefined): mirrorArkts.CallExpression { + // return mirrorArkts.factory.createCallExpression( + // mirrorArkts.factory.createMemberExpression( + // mirrorArkts.factory.createIdentifier('Type'), + // mirrorArkts.factory.createIdentifier('from'), + // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + // false, + // false + // ), + // typeAnnotation ? [typeAnnotation] : undefined, + // undefined + // ); + // } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/utils.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/utils.ts new file mode 100644 index 000000000..fd71a216e --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/utils.ts @@ -0,0 +1,443 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { ImportCollector } from '../../common/import-collector'; +import { isDecoratorAnnotation } from '../../common/arkts-utils'; +import { + DecoratorIntrinsicNames, + DecoratorNames, + DECORATOR_TYPE_MAP, + StateManagementTypes, + GetSetTypes, +} from '../../common/predefines'; +// import { +// addMemoAnnotation, +// findCanAddMemoFromParameter, +// findCanAddMemoFromTypeAnnotation, +// } from '../../collectors/memo-collectors/utils'; + +export interface DecoratorInfo { + annotation: mirrorArkts.AnnotationUsage; + name: DecoratorNames; +} + +// export function isDecoratorIntrinsicAnnotation( +// anno: mirrorArkts.AnnotationUsage, +// decoratorName: DecoratorIntrinsicNames +// ): boolean { +// return !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; +// } + +// export function removeDecorator( +// property: mirrorArkts.ClassProperty | mirrorArkts.ClassDefinition | mirrorArkts.MethodDefinition, +// decoratorName: DecoratorNames, +// ignoreDecl?: boolean +// ): void { +// if (mirrorArkts.isMethodDefinition(property)) { +// property.scriptFunction.setAnnotations( +// property.scriptFunction.annotations.filter( +// (anno) => !isDecoratorAnnotation(anno, decoratorName, ignoreDecl) +// ) +// ); +// } else { +// property.setAnnotations( +// property.annotations.filter((anno) => !isDecoratorAnnotation(anno, decoratorName, ignoreDecl)) +// ); +// } +// } + +/** + * checking whether astNode's annotations contain given corresponding decorator name, + * regardless where the annotation's declaration is from arkui declaration files. + */ +export function hasDecoratorName( + property: mirrorArkts.ClassProperty | mirrorArkts.ClassDefinition | mirrorArkts.MethodDefinition, + decoratorName: DecoratorNames +): boolean { + if (mirrorArkts.isMethodDefinition(property)) { + return !!property.function && property.function.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName, true)); + } + return property.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName, true)); +} + +// export function hasDecorator( +// property: +// | mirrorArkts.ClassProperty +// | mirrorArkts.ClassDefinition +// | mirrorArkts.MethodDefinition +// | mirrorArkts.ETSParameterExpression +// | mirrorArkts.ETSFunctionType, +// decoratorName: DecoratorNames +// ): boolean { +// if (mirrorArkts.isMethodDefinition(property)) { +// return property.scriptFunction.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName)); +// } +// return property.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName)); +// } + +/** + * Determine whether the node `` is decorated by decorators that need initializing without assignment. + * + * @param st class property node + */ +export function needDefiniteOrOptionalModifier(st: mirrorArkts.ClassProperty): boolean { + return ( + hasDecoratorName(st, DecoratorNames.LINK) || + hasDecoratorName(st, DecoratorNames.CONSUME) || + hasDecoratorName(st, DecoratorNames.OBJECT_LINK) || + (hasDecoratorName(st, DecoratorNames.PROP) && !st.value) + ); +} + +export function findDecoratorByName( + property: mirrorArkts.ClassProperty | mirrorArkts.ClassDefinition | mirrorArkts.MethodDefinition, + decoratorName: DecoratorNames +): mirrorArkts.AnnotationUsage | undefined { + if (mirrorArkts.isMethodDefinition(property)) { + return property.function ? property.function.annotations.find((anno) => isDecoratorAnnotation(anno, decoratorName, true)) : undefined; + } + return property.annotations.find((anno) => isDecoratorAnnotation(anno, decoratorName, true)); +} + +// export function findDecorator( +// property: mirrorArkts.ClassProperty | mirrorArkts.ClassDefinition | mirrorArkts.MethodDefinition, +// decoratorName: DecoratorNames +// ): mirrorArkts.AnnotationUsage | undefined { +// if (mirrorArkts.isMethodDefinition(property)) { +// return property.scriptFunction.annotations.find((anno) => isDecoratorAnnotation(anno, decoratorName)); +// } +// return property.annotations.find((anno) => isDecoratorAnnotation(anno, decoratorName)); +// } + +/** + * find all annotationUsages of property that are included in DecoratorNames + */ +export function findDecoratorInfos( + property: mirrorArkts.ClassProperty | mirrorArkts.ClassDefinition | mirrorArkts.MethodDefinition +): DecoratorInfo[] { + const decoratorNames = Object.values(DecoratorNames); + const infos: DecoratorInfo[] = []; + for (let i = 0; i < decoratorNames.length; i++) { + const name = decoratorNames[i]; + const annotation: mirrorArkts.AnnotationUsage | undefined = findDecoratorByName(property, name); + if (!!annotation) { + infos.push({ annotation, name }); + } + } + return infos; +} + +// export function getStateManagementType(decoratorInfo: DecoratorInfo): StateManagementTypes { +// const decoratorName = decoratorInfo.name; +// const typeName = DECORATOR_TYPE_MAP.get(decoratorName); +// if (!!typeName) { +// return typeName; +// } +// return StateManagementTypes.MUTABLE_STATE; +// } + +export function collectStateManagementTypeImport(type: StateManagementTypes): void { + ImportCollector.getInstance().collectImport(type); +} + +// export function createGetter( +// name: string, +// type: mirrorArkts.TypeNode | undefined, +// returns: mirrorArkts.Expression, +// needMemo: boolean = false +// ): mirrorArkts.MethodDefinition { +// const returnType: mirrorArkts.TypeNode | undefined = type?.clone(); +// if (needMemo && findCanAddMemoFromTypeAnnotation(returnType)) { +// addMemoAnnotation(returnType); +// } +// const body = mirrorArkts.factory.createBlock([mirrorArkts.factory.createReturnStatement(returns)]); +// const scriptFunction = mirrorArkts.factory.createScriptFunction( +// body, +// mirrorArkts.FunctionSignature.createFunctionSignature(undefined, [], returnType, false), +// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER, +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC +// ); +// return mirrorArkts.factory.createMethodDefinition( +// mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET, +// mirrorArkts.factory.createIdentifier(name), +// scriptFunction, +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, +// false +// ); +// } + +// export function createSetter( +// name: string, +// type: mirrorArkts.TypeNode | undefined, +// left: mirrorArkts.Expression, +// right: mirrorArkts.AstNode, +// needMemo: boolean = false +// ): mirrorArkts.MethodDefinition { +// const body = mirrorArkts.factory.createBlock([ +// mirrorArkts.factory.createExpressionStatement( +// mirrorArkts.factory.createAssignmentExpression( +// left, +// mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, +// right +// ) +// ), +// ]); +// const param: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createParameterDeclaration( +// mirrorArkts.factory.createIdentifier('value', type?.clone()), +// undefined +// ); +// if (needMemo && findCanAddMemoFromParameter(param)) { +// addMemoAnnotation(param); +// } +// const scriptFunction = mirrorArkts.factory.createScriptFunction( +// body, +// mirrorArkts.FunctionSignature.createFunctionSignature(undefined, [param], undefined, false), +// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC +// ); + +// return mirrorArkts.factory.createMethodDefinition( +// mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, +// mirrorArkts.factory.createIdentifier(name), +// scriptFunction, +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, +// false +// ); +// } + +// export function createSetter2( +// name: string, +// type: mirrorArkts.TypeNode | undefined, +// statement: mirrorArkts.AstNode +// ): mirrorArkts.MethodDefinition { +// const body = mirrorArkts.factory.createBlock([statement]); +// const param: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createParameterDeclaration( +// mirrorArkts.factory.createIdentifier('value', type?.clone()), +// undefined +// ); +// const scriptFunction = mirrorArkts.factory.createScriptFunction( +// body, +// mirrorArkts.FunctionSignature.createFunctionSignature(undefined, [param], undefined, false), +// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC +// ); + +// return mirrorArkts.factory.createMethodDefinition( +// mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, +// mirrorArkts.factory.createIdentifier(name), +// scriptFunction, +// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, +// false +// ); +// } + +// export function generateThisBackingValue( +// name: string, +// optional: boolean = false, +// nonNull: boolean = false +// ): mirrorArkts.MemberExpression { +// const member: mirrorArkts.Expression = generateThisBacking(name, optional, nonNull); +// return mirrorArkts.factory.createMemberExpression( +// member, +// mirrorArkts.factory.createIdentifier('value'), +// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, +// false, +// false +// ); +// } + +// export function generateThisBacking( +// name: string, +// optional: boolean = false, +// nonNull: boolean = false +// ): mirrorArkts.Expression { +// const member: mirrorArkts.Expression = mirrorArkts.factory.createMemberExpression( +// mirrorArkts.factory.createThisExpression(), +// mirrorArkts.factory.createIdentifier(`${name}`), +// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, +// false, +// optional +// ); +// return nonNull ? mirrorArkts.factory.createTSNonNullExpression(member) : member; +// } + +// function getValueStr(node: mirrorArkts.AstNode): string | undefined { +// if (!mirrorArkts.isClassProperty(node) || !node.value) return undefined; +// return mirrorArkts.isStringLiteral(node.value) ? node.value.str : undefined; +// } + +// function getAnnotationValue(anno: mirrorArkts.AnnotationUsage, decoratorName: DecoratorNames): string | undefined { +// const isSuitableAnnotation: boolean = +// !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; +// if (isSuitableAnnotation && anno.properties.length === 1) { +// return getValueStr(anno.properties.at(0)!); +// } +// return undefined; +// } + +// export function getValueInAnnotation(node: mirrorArkts.ClassProperty, decoratorName: DecoratorNames): string | undefined { +// const annotations: readonly mirrorArkts.AnnotationUsage[] = node.annotations; +// for (let i = 0; i < annotations.length; i++) { +// const anno: mirrorArkts.AnnotationUsage = annotations[i]; +// const str: string | undefined = getAnnotationValue(anno, decoratorName); +// if (!!str) { +// return str; +// } +// } +// return undefined; +// } + +// export interface ProvideOptions { +// alias: string; +// allowOverride: boolean; +// } + +// export function getValueInProvideAnnotation(node: mirrorArkts.ClassProperty): ProvideOptions | undefined { +// const annotations: readonly mirrorArkts.AnnotationUsage[] = node.annotations; +// for (let i = 0; i < annotations.length; i++) { +// const anno: mirrorArkts.AnnotationUsage = annotations[i]; +// if (anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === DecoratorNames.PROVIDE) { +// const alias: string = getValueInObjectAnnotation(anno, DecoratorNames.PROVIDE, 'alias'); +// const allowOverride: boolean = getValueInObjectAnnotation(anno, DecoratorNames.PROVIDE, 'allowOverride') +// ? true +// : false; +// return { alias, allowOverride }; +// } +// } +// return undefined; +// } + +// function getValueInObjectAnnotation(anno: mirrorArkts.AnnotationUsage, decoratorName: DecoratorNames, key: string): any { +// const isSuitableAnnotation: boolean = +// !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; +// if (!isSuitableAnnotation) { +// return undefined; +// } +// const keyItem: mirrorArkts.AstNode | undefined = anno.properties.find( +// (annoProp: mirrorArkts.AstNode) => +// mirrorArkts.isClassProperty(annoProp) && +// annoProp.key && +// mirrorArkts.isIdentifier(annoProp.key) && +// annoProp.key.name === key +// ); +// if (keyItem && mirrorArkts.isClassProperty(keyItem) && keyItem.value) { +// return getDifferentAnnoTypeValue(keyItem.value); +// } +// return undefined; +// } + +// function getDifferentAnnoTypeValue(value: mirrorArkts.Expression): string | boolean { +// if (mirrorArkts.isBooleanLiteral(value)) { +// return value.value; +// } else if (mirrorArkts.isStringLiteral(value)) { +// return value.str; +// } +// return value.dumpSrc(); +// } + +// export function generateGetOrSetCall(beforCall: mirrorArkts.AstNode, type: GetSetTypes) { +// return mirrorArkts.factory.createCallExpression( +// mirrorArkts.factory.createMemberExpression( +// beforCall, +// mirrorArkts.factory.createIdentifier(type), +// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, +// false, +// false +// ), +// undefined, +// type === 'set' ? [mirrorArkts.factory.createIdentifier('value')] : undefined, +// undefined +// ); +// } + +// export function generateToRecord(newName: string, originalName: string): mirrorArkts.Property { +// return mirrorArkts.Property.createProperty( +// mirrorArkts.factory.createStringLiteral(originalName), +// mirrorArkts.factory.createBinaryExpression( +// mirrorArkts.factory.createMemberExpression( +// mirrorArkts.factory.createIdentifier('paramsCasted'), +// mirrorArkts.factory.createIdentifier(originalName), +// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, +// false, +// false +// ), +// mirrorArkts.ETSNewClassInstanceExpression.createETSNewClassInstanceExpression( +// mirrorArkts.factory.createTypeReference( +// mirrorArkts.factory.createTypeReferencePart(mirrorArkts.factory.createIdentifier('Object')) +// ), +// [] +// ), +// mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING +// ) +// ); +// } + +// // CACHE +// export interface PropertyCachedBody { +// initializeBody?: mirrorArkts.AstNode[]; +// updateBody?: mirrorArkts.AstNode[]; +// toRecordBody?: mirrorArkts.Property[]; +// } + +// export class PropertyCache { +// private _cache: Map; +// private static instance: PropertyCache; + +// private constructor() { +// this._cache = new Map(); +// } + +// static getInstance(): PropertyCache { +// if (!this.instance) { +// this.instance = new PropertyCache(); +// } +// return this.instance; +// } + +// reset(): void { +// this._cache.clear(); +// } + +// getInitializeBody(name: string): mirrorArkts.AstNode[] { +// return this._cache.get(name)?.initializeBody ?? []; +// } + +// getUpdateBody(name: string): mirrorArkts.AstNode[] { +// return this._cache.get(name)?.updateBody ?? []; +// } + +// getToRecordBody(name: string): mirrorArkts.Property[] { +// return this._cache.get(name)?.toRecordBody ?? []; +// } + +// collectInitializeStruct(name: string, initializeStruct: mirrorArkts.AstNode[]): void { +// const initializeBody = this._cache.get(name)?.initializeBody ?? []; +// const newInitializeBody = [...initializeBody, ...initializeStruct]; +// this._cache.set(name, { ...this._cache.get(name), initializeBody: newInitializeBody }); +// } + +// collectUpdateStruct(name: string, updateStruct: mirrorArkts.AstNode[]): void { +// const updateBody = this._cache.get(name)?.updateBody ?? []; +// const newUpdateBody = [...updateBody, ...updateStruct]; +// this._cache.set(name, { ...this._cache.get(name), updateBody: newUpdateBody }); +// } + +// collectToRecord(name: string, toRecord: mirrorArkts.Property[]): void { +// const toRecordBody = this._cache.get(name)?.toRecordBody ?? []; +// const newToRecordBody = [...toRecordBody, ...toRecord]; +// this._cache.set(name, { ...this._cache.get(name), toRecordBody: newToRecordBody }); +// } +// } diff --git a/arkui-plugins/mirror-replace/ui-plugins/ui-factory.ts b/arkui-plugins/mirror-replace/ui-plugins/ui-factory.ts new file mode 100644 index 000000000..6dbd575db --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/ui-factory.ts @@ -0,0 +1,394 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror' +import { + BuilderLambdaNames, + CustomComponentAnontations, + CustomComponentNames, + 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 '../collectors/memo-collectors/utils'; + +export interface ScriptFunctionConfiguration { + key: mirrorArkts.Identifier | undefined; + body: mirrorArkts.AstNode | undefined; + typeParams: mirrorArkts.TSTypeParameterDeclaration | undefined; + params: readonly mirrorArkts.Expression[]; + returnTypeAnnotation: mirrorArkts.TypeNode | undefined; + hasReceiver: boolean; + flags: mirrorArkts.Es2pandaScriptFunctionFlags; + modifiers: mirrorArkts.Es2pandaModifierFlags; + annotations: mirrorArkts.AnnotationUsage[]; +} + +export interface MethodDefinitionConfiguration { + key: mirrorArkts.Identifier; + kind: mirrorArkts.Es2pandaMethodDefinitionKind; + function: ScriptFunctionConfiguration; + overloads: mirrorArkts.MethodDefinition[]; + modifiers: mirrorArkts.Es2pandaModifierFlags; + isComputed: boolean; +} + +export interface IntrinsicAnnotationDeclarationConfiguration { + expr: mirrorArkts.Identifier; + properties: mirrorArkts.AstNode[]; +} + +export class factory { + // /** + // * create `instance: ` as identifier + // */ + // static createInstanceIdentifier(typeName: string): mirrorArkts.Identifier { + // return mirrorArkts.factory.createIdentifier( + // BuilderLambdaNames.STYLE_ARROW_PARAM_NAME, + // factory.createTypeReferenceFromString(typeName) + // ); + // } + + // /** + // * create `instance: ` as parameter + // */ + // static createInstanceParameter(typeName: string): mirrorArkts.ETSParameterExpression { + // return mirrorArkts.factory.createParameterDeclaration(factory.createInstanceIdentifier(typeName), undefined); + // } + + // /** + // * create `(instance: ) => void` + // */ + // static createStyleLambdaFunctionType(typeName: string): mirrorArkts.ETSFunctionType { + // return mirrorArkts.factory.createFunctionType( + // mirrorArkts.FunctionSignature.createFunctionSignature( + // undefined, + // [factory.createInstanceParameter(typeName)], + // factory.createTypeReferenceFromString(typeName), + // false + // ), + // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW + // ); + // } + + // /** + // * create `style: ((instance: ) => void) | undefined` as identifier + // */ + // static createStyleIdentifier(typeName: string): mirrorArkts.Identifier { + // return mirrorArkts.factory.createIdentifier( + // BuilderLambdaNames.STYLE_PARAM_NAME, + // mirrorArkts.factory.createUnionType([ + // factory.createStyleLambdaFunctionType(typeName), + // mirrorArkts.factory.createETSUndefinedType(), + // ]) + // ); + // } + + // /** + // * create `initializers: | undefined` as identifier + // */ + // static createInitializerOptionsIdentifier(optionsName: string): mirrorArkts.Identifier { + // return mirrorArkts.factory.createIdentifier( + // CustomComponentNames.COMPONENT_INITIALIZERS_NAME, + // mirrorArkts.factory.createUnionType([ + // factory.createTypeReferenceFromString(optionsName), + // mirrorArkts.factory.createETSUndefinedType(), + // ]) + // ); + // } + + // /** + // * create `initializers: | undefined` as parameter + // */ + // static createInitializersOptionsParameter(optionsName: string): mirrorArkts.ETSParameterExpression { + // return mirrorArkts.factory.createParameterDeclaration( + // factory.createInitializerOptionsIdentifier(optionsName), + // undefined + // ); + // } + + // /** + // * create `content: (() => void) | undefined` as identifier + // */ + // static createContentIdentifier(): mirrorArkts.Identifier { + // return mirrorArkts.factory.createIdentifier( + // BuilderLambdaNames.CONTENT_PARAM_NAME, + // mirrorArkts.factory.createUnionType([factory.createLambdaFunctionType(), mirrorArkts.factory.createETSUndefinedType()]) + // ); + // } + + // /** + // * create `@memo() content: (() => void) | undefined` as parameter + // */ + // static createContentParameter(): mirrorArkts.ETSParameterExpression { + // const contentParam: mirrorArkts.Identifier = factory.createContentIdentifier(); + // const param: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createParameterDeclaration(contentParam, undefined); + // addMemoAnnotation(param); + // return param; + // } + + /** + * create type from string + */ + static createTypeReferenceFromString(name: string): mirrorArkts.TypeNode { + return mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart(mirrorArkts.factory.createIdentifier(name)) + ); + } + + // /** + // * create complex type from string and type parameter, e.g. `Set` + // */ + // static createComplexTypeFromStringAndTypeParameter(name: string, params: mirrorArkts.TypeNode[]): mirrorArkts.TypeNode { + // return mirrorArkts.factory.createTypeReference( + // mirrorArkts.factory.createTypeReferencePart( + // mirrorArkts.factory.createIdentifier(name), + // mirrorArkts.factory.createTSTypeParameterInstantiation(params) + // ) + // ); + // } + + // /** + // * create `() => `. If returnType is not given, then using `void`. + // */ + // static createLambdaFunctionType( + // params?: mirrorArkts.Expression[], + // returnType?: mirrorArkts.TypeNode | undefined + // ): mirrorArkts.ETSFunctionType { + // return mirrorArkts.factory.createFunctionType( + // mirrorArkts.FunctionSignature.createFunctionSignature( + // undefined, + // params ?? [], + // returnType ?? mirrorArkts.factory.createPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + // false + // ), + // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW + // ); + // } + + // /** + // * create `import { as } ...`. + // */ + // static createAdditionalImportSpecifier(imported: string, local: string): mirrorArkts.ImportSpecifier { + // return mirrorArkts.factory.createImportSpecifier( + // mirrorArkts.factory.createIdentifier(imported), + // mirrorArkts.factory.createIdentifier(local) + // ); + // } + + // /** + // * update ScriptFunction with configurations. + // */ + // static updateScriptFunction( + // original: mirrorArkts.ScriptFunction, + // config: Partial + // ): mirrorArkts.ScriptFunction { + // const newFunc: mirrorArkts.ScriptFunction = mirrorArkts.factory.updateScriptFunction( + // original, + // config.body ?? original.body, + // mirrorArkts.factory.createFunctionSignature( + // config.typeParams ?? original.typeParams, + // config.params ?? original.params, + // config.returnTypeAnnotation ?? original.returnTypeAnnotation, + // config.hasReceiver ?? original.hasReceiver + // ), + // config.flags ?? original.flags, + // config.modifiers ?? original.modifiers + // ); + // if (!!config.key) { + // newFunc.setIdent(config.key); + // } + // if (!!config.annotations) { + // newFunc.setAnnotations(config.annotations); + // } + // return newFunc; + // } + + /** + * create ScriptFunction with configurations. + */ + static createScriptFunction(config: Partial): mirrorArkts.ScriptFunction { + const newFunc: mirrorArkts.ScriptFunction = mirrorArkts.factory.createScriptFunction( + config.body ?? undefined, + config.typeParams ?? undefined, + config.params ?? [], + config.returnTypeAnnotation ?? undefined, + config.hasReceiver ?? false, + config.flags ?? mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_NONE, + config.modifiers ?? mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + config.key, + config.annotations, + ); + return newFunc; + } + + // /** + // * update MethodDefinition with configurations. + // */ + // static updateMethodDefinition( + // original: mirrorArkts.MethodDefinition, + // config: PartialNested + // ): mirrorArkts.MethodDefinition { + // const key: mirrorArkts.Identifier = config.key ?? original.name; + // const newFunc: mirrorArkts.ScriptFunction = factory.updateScriptFunction(original.scriptFunction, { + // ...config.function, + // key, + // }); + // const newMethod: mirrorArkts.MethodDefinition = mirrorArkts.factory.updateMethodDefinition( + // original, + // config.kind ?? original.kind, + // key, + // newFunc, + // config.modifiers ?? original.modifiers, + // config.isComputed ?? false + // ); + // if (!!config.overloads) { + // newMethod.setOverloads(config.overloads); + // } + // return newMethod; + // } + + /** + * create MethodDefinition with configurations. + */ + static createMethodDefinition(config: PartialNested): mirrorArkts.MethodDefinition { + const newFunc: mirrorArkts.ScriptFunction = factory.createScriptFunction({ + ...config.function, + key: config.key, + }); + const newMethod: mirrorArkts.MethodDefinition = mirrorArkts.factory.createMethodDefinition( + config.kind ?? mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_NONE, + config.key!, + mirrorArkts.factory.createFunctionExpression(undefined, newFunc), + config.modifiers ?? mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + config.isComputed ?? false + ); + if (!!config.overloads) { + newMethod.setOverloads(config.overloads); + } + return newMethod; + } + + /** + * create intrinsic `@Retention({policy:"SOURCE"})` AnnotationDeclaration with configurations. + */ + static createIntrinsicAnnotationDeclaration( + config: PartialExcept + ): mirrorArkts.AnnotationDeclaration { + const intrinsicAnnotations: mirrorArkts.AnnotationUsage[] = [ + mirrorArkts.factory.createAnnotationUsage(mirrorArkts.factory.createIdentifier('Retention'), [ + mirrorArkts.factory.createClassProperty( + mirrorArkts.factory.createIdentifier('policy'), + mirrorArkts.factory.createStringLiteral('SOURCE'), + undefined, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ), + ]), + ]; + const newAnnotationDecl: mirrorArkts.AnnotationDeclaration = mirrorArkts.factory + .createAnnotationDeclaration(config.expr, config.properties ?? []) + .setAnnotations(intrinsicAnnotations); + return newAnnotationDecl; + } + + /** + * add alias: to @Provide annotation when no alias in @Provide({...}). + */ + static processNoAliasProvideVariable(property: mirrorArkts.ClassProperty): void { + let annotations: readonly mirrorArkts.AnnotationUsage[] = property.annotations; + if (annotations.length === 0) { + return; + } + const newAnnos: mirrorArkts.AnnotationUsage[] = annotations.map((anno: mirrorArkts.AnnotationUsage) => { + if ( + !!anno.expr && + mirrorArkts.isIdentifier(anno.expr) && + anno.expr.name === DecoratorNames.PROVIDE && + !hasPropertyInAnnotation(anno, 'alias') && + property.key && + mirrorArkts.isIdentifier(property.key) + ) { + return mirrorArkts.factory.update1AnnotationUsage(anno, anno.expr, [ + ...anno.properties, + factory.createAliasClassProperty(property.key), + ]); + } else { + return anno; + } + }); + property.setAnnotations(newAnnos); + } + + /** + * create class property : `alias: `. + */ + static createAliasClassProperty(value: mirrorArkts.Identifier): mirrorArkts.ClassProperty { + return mirrorArkts.factory.createClassProperty( + mirrorArkts.factory.createIdentifier('alias'), + mirrorArkts.factory.createStringLiteral(value.name), + undefined, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + false + ); + } + + /** + * add optional or definite modifier for class property needs initializing without assignment. + */ + static PreprocessClassPropertyModifier(st: mirrorArkts.AstNode): mirrorArkts.AstNode { + if (mirrorArkts.isClassProperty(st) && needDefiniteOrOptionalModifier(st)) { + if (st.typeAnnotation && hasNullOrUndefinedType(st.typeAnnotation)) { + st.modifierFlags |= mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OPTIONAL; + } else { + st.modifierFlags |= mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DEFINITE; + } + } + return st; + } + + /** + * create class implements : `implements `. + */ + static createClassImplements( + interfaceName: string, + typeParameters?: mirrorArkts.TSTypeParameterInstantiation + ): mirrorArkts.TSClassImplements { + return mirrorArkts.factory.createTSClassImplements( + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart(mirrorArkts.factory.createIdentifier(interfaceName)) + ), + typeParameters + ); + } + + /** + * Generate class implements for struct with struct annotations. + * + * @param method method definition node + */ + static generateImplementsForStruct(annotations: CustomComponentAnontations): mirrorArkts.TSClassImplements[] { + const implementsInfo: mirrorArkts.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/mirror-replace/ui-plugins/utils.ts b/arkui-plugins/mirror-replace/ui-plugins/utils.ts new file mode 100644 index 000000000..e7d401a20 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/utils.ts @@ -0,0 +1,291 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror' +import { matchPrefix } from '../common/arkts-utils'; +import { ARKUI_IMPORT_PREFIX_NAMES, StructDecoratorNames } from '../common/predefines'; +import { DeclarationCollector } from '../common/declaration-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_INITIALIZERS_NAME = 'initializers', + BUILDCOMPATIBLENODE = '_buildCompatibleNode', + OPTIONS = 'options', + PAGE_LIFE_CYCLE = 'PageLifeCycle', + LAYOUT_CALLBACK = 'LayoutCallback', + CUSTOMDIALOG_ANNOTATION_NAME = 'CustomDialog', + CUSTOMDIALOG_CONTROLLER = 'CustomDialogController', + CUSTOMDIALOG_CONTROLLER_OPTIONS = 'CustomDialogControllerOptions', + SETDIALOGCONTROLLER_METHOD = '__setDialogController__', +} + +export enum BuilderLambdaNames { + ANNOTATION_NAME = 'ComponentBuilder', + ORIGIN_METHOD_NAME = '$_instantiate', + TRANSFORM_METHOD_NAME = '_instantiateImpl', + STYLE_PARAM_NAME = 'style', + STYLE_ARROW_PARAM_NAME = 'instance', + CONTENT_PARAM_NAME = 'content' +} + +// IMPORT +// export function findImportSourceByName(importName: string): string { +// const source = DeclarationCollector.getInstance().findExternalSourceFromName(importName); +// if (!source) { +// throw new Error(`cannot find import source by name: "${importName}".`); +// } +// return source; +// } + +// export function findImportSourceByNode(declNode: mirrorArkts.AstNode): string { +// const source = DeclarationCollector.getInstance().findExternalSourceFromNode(declNode); +// if (!source) { +// throw new Error(`cannot find import source by peer.`); +// } +// return source; +// } + +export function findLocalImport( + node: mirrorArkts.ETSImportDeclaration, + sourceName: string, + importedName: string +): mirrorArkts.Identifier | undefined { + const isFromSource = !!node.source && node.source.str === sourceName; + if (!isFromSource) return undefined; + + const importSpecifier = node.specifiers.find( + (spec) => mirrorArkts.isImportSpecifier(spec) && !!spec.imported && spec.imported.name === importedName + ) as mirrorArkts.ImportSpecifier | undefined; + // import { x as y}, x = imported y = local + return importSpecifier?.local ?? importSpecifier?.imported; +} + +// AST NODE +export function isStatic(node: mirrorArkts.AstNode): boolean { + return node.isStatic; +} + +/** + * Determine whether the type node includes null or undefined type. + * + * @param type type node + */ +export function hasNullOrUndefinedType(type: mirrorArkts.TypeNode): boolean { + let res: boolean = false; + if (mirrorArkts.isETSUnionType(type)) { + type.types.forEach((item: mirrorArkts.TypeNode) => { + res = res || hasNullOrUndefinedType(item); + }); + } + if (mirrorArkts.isETSUndefinedType(type) || mirrorArkts.isETSNullType(type)) { + res = true; + } + return res; +} + +// TYPE PARAMETER +export function getTypeParamsFromClassDecl(node: mirrorArkts.ClassDeclaration | undefined): readonly mirrorArkts.TSTypeParameter[] { + return node?.definition?.typeParams?.params ?? []; +} + +export function getTypeNameFromTypeParameter(node: mirrorArkts.TSTypeParameter | undefined): string | undefined { + return node?.name?.name; +} + +// GETTER +export function getGettersFromClassDecl(definition: mirrorArkts.ClassDefinition): mirrorArkts.MethodDefinition[] { + return definition.body.filter( + (member) => + mirrorArkts.isMethodDefinition(member) && + mirrorArkts.hasModifierFlag(member, mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_GETTER) + ) as mirrorArkts.MethodDefinition[]; +} + +// ANNOTATION +export function hasPropertyInAnnotation(annotation: mirrorArkts.AnnotationUsage, propertyName: string): boolean { + return !!annotation.properties.find( + (annoProp: mirrorArkts.AstNode) => + mirrorArkts.isClassProperty(annoProp) && + annoProp.key && + mirrorArkts.isIdentifier(annoProp.key) && + annoProp.key.name === propertyName + ); +} + +// CUSTOM COMPONENT +export type CustomComponentInfo = { + name: string; + isDecl: boolean; + annotations: CustomComponentAnontations; +}; + +export type CustomComponentAnontations = { + component?: mirrorArkts.AnnotationUsage; + componentV2?: mirrorArkts.AnnotationUsage; + entry?: mirrorArkts.AnnotationUsage; + reusable?: mirrorArkts.AnnotationUsage; + reusableV2?: mirrorArkts.AnnotationUsage; + customLayout?: mirrorArkts.AnnotationUsage; + customdialog?: mirrorArkts.AnnotationUsage; +}; + +type StructAnnoationInfo = { + isComponent: boolean; + isComponentV2: boolean; + isEntry: boolean; + isReusable: boolean; + isReusableV2: boolean; + isCustomLayout: boolean; + isCustomDialog: boolean; +}; + +export function isCustomComponentAnnotation( + anno: mirrorArkts.AnnotationUsage, + decoratorName: StructDecoratorNames, + ignoreDecl?: boolean +): boolean { + if (!(!!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName)) { + return false; + } + if (!ignoreDecl) { + const decl = mirrorArkts.getDecl(anno.expr); + if (!decl) { + return false; + } + const moduleName: string = mirrorArkts.getProgramFromAstNode(decl).moduleName; + if (!moduleName || !matchPrefix(ARKUI_IMPORT_PREFIX_NAMES, moduleName)) { + return false; + } + DeclarationCollector.getInstance().collect(decl); + } + return true; +} + +export function collectCustomComponentScopeInfo( + node: mirrorArkts.ClassDeclaration | mirrorArkts.ETSStructDeclaration +): CustomComponentInfo | undefined { + const definition: mirrorArkts.ClassDefinition | undefined = node.definition; + if (!definition || !definition?.ident?.name) { + return undefined; + } + const isStruct = mirrorArkts.isETSStructDeclaration(node); + const isDecl: boolean = mirrorArkts.hasModifierFlag(node, mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE); + const isCustomComponentClassDecl = !isStruct && isDecl; + const shouldIgnoreDecl = isStruct || isDecl; + 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, isComponentV2, isEntry, isReusable, isReusableV2, isCustomLayout, isCustomDialog } = + getAnnotationInfoForStruct(anno, shouldIgnoreDecl); + isCustomComponent ||= isComponent || isComponentV2 || isCustomDialog; + 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 }), + ...(isCustomDialog && !annotations?.reusable && { customdialog: anno }), + }; + } + if (!isCustomComponent) { + return undefined; + } + } + return { + name: definition.ident.name, + isDecl, + annotations: annotations as CustomComponentAnontations, + }; +} + +export function getAnnotationInfoForStruct( + anno: mirrorArkts.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); + const isCustomDialog = isCustomComponentAnnotation(anno, StructDecoratorNames.CUSTOMDIALOG, shouldIgnoreDecl); + return { isComponent, isComponentV2, isEntry, isReusable, isReusableV2, isCustomLayout, isCustomDialog }; +} + +export function isComponentStruct(node: mirrorArkts.ETSStructDeclaration, scopeInfo: CustomComponentInfo): boolean { + return !!node.definition && scopeInfo.name === node.definition.ident?.name; +} + +/** + * Determine whether it is a custom component. + * + * @param node class declaration node + */ +// export function isCustomComponentClass(node: mirrorArkts.ClassDeclaration, scopeInfo: CustomComponentInfo): boolean { +// if (!node.definition?.ident?.name) { +// return false; +// } +// const name: string = node.definition.ident.name; +// if (scopeInfo.isDecl) { +// return ( +// name === CustomComponentNames.COMPONENT_CLASS_NAME || name === CustomComponentNames.COMPONENT_V2_CLASS_NAME +// ); +// } +// return name === scopeInfo.name; +// } + +// export function isCustomComponentInterface(node: mirrorArkts.TSInterfaceDeclaration): boolean { +// const checkPrefix = !!node.id?.name.startsWith(CustomComponentNames.COMPONENT_INTERFACE_PREFIX); +// const checkComponent = node.annotations.some((anno) => +// isCustomComponentAnnotation(anno, StructDecoratorNames.COMPONENT) +// ); +// return checkPrefix && checkComponent; +// } + +export function getCustomComponentOptionsName(className: string): string { + return `${CustomComponentNames.COMPONENT_INTERFACE_PREFIX}${className}`; +} + +/** + * Determine whether it is method with specified name. + * + * @param method method definition node + * @param name specified method name + */ +// export function isKnownMethodDefinition(method: mirrorArkts.MethodDefinition, name: string): boolean { +// if (!method || !mirrorArkts.isMethodDefinition(method)) { +// return false; +// } + +// // For now, we only considered matched method name. +// const isNameMatched: boolean = method.name?.name === name; +// return isNameMatched; +// } diff --git a/arkui-plugins/test/demo/localtest/entry/src/main/ets/pages/new.ets b/arkui-plugins/test/demo/localtest/entry/src/main/ets/pages/new.ets index a66597507..501207619 100755 --- a/arkui-plugins/test/demo/localtest/entry/src/main/ets/pages/new.ets +++ b/arkui-plugins/test/demo/localtest/entry/src/main/ets/pages/new.ets @@ -19,7 +19,7 @@ import hilog from '@ohos.hilog' @Entry @Component -struct MyStateSample { +export struct MyStateSample { @State stateVar: string = "state var"; message: string = `click to change state variable, add **`; changeValue() { @@ -41,8 +41,8 @@ struct MyStateSample { } @Component -struct Child { - @Link linkVar: string = ""; // TODO: remove this +export struct Child { + @Link linkVar: string; // TODO: remove this @Prop propVar: string = "Prop"; changeValue1() { diff --git a/arkui-plugins/test/mirror/demo/entry/src/main/ets/pages/new.ets b/arkui-plugins/test/mirror/demo/entry/src/main/ets/pages/new.ets index b3627f723..47f8c45c2 100755 --- a/arkui-plugins/test/mirror/demo/entry/src/main/ets/pages/new.ets +++ b/arkui-plugins/test/mirror/demo/entry/src/main/ets/pages/new.ets @@ -17,7 +17,7 @@ import { Text, Column, Component, Entry, Button, ClickEvent } from "@ohos.arkui. import { State, Link, Prop } from "@ohos.arkui.stateManagement" import hilog from '@ohos.hilog' -@Entry +@Entry({useSharedStorage:true}) @Component export struct MyStateSample { @State stateVar: string = "state var"; diff --git a/arkui-plugins/test/mirror/mirror_test_config_template.json b/arkui-plugins/test/mirror/mirror_test_config_template.json new file mode 100755 index 000000000..7845d9163 --- /dev/null +++ b/arkui-plugins/test/mirror/mirror_test_config_template.json @@ -0,0 +1,43 @@ +{ + "plugins": { + "ui_plugin": "workspace/out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ui-plugins/lib/mirror-replace/ui-plugins/index" + }, + "compileFiles": [ + "./mirror/demo/entry/src/main/ets/pages/new.ets" + ], + "entryFiles": [ + "./mirror/demo/entry/src/main/ets/pages/new.ets" + ], + "buildMode": "Debug", + "projectRootPath": "./mirror/demo/", + "moduleRootPath": "./mirror/demo/entry/", + "cachePath": "./mirror/dist/cache", + "loaderOutPath": "./mirror/dist", + "compileSdkVersion": 20, + "compatibleSdkVersion": 20, + "bundleName": "com.example.myapplication", + "useNormalizedOHMUrl": true, + "buildType": "build", + "packageName": "entry", + "buildSdkPath": "workspace/out/sdk/ohos-sdk/linux/ets/ets1.2/", + "sourceRoots": [ + "./" + ], + "moduleType": "shared", + "moduleName": "entry", + "dependentModuleList": [], + "hasMainModule": true, + "buildLoaderJson": "", + "integratedHsp": false, + "allowEmptyBundleName": false, + "declgenV2OutPath": "", + "externalApiPaths": [], + "level": { + "level": 20000, + "levelStr": "INFO", + "colour": "green" + }, + "isBuildConfigModified": false, + "aceModuleJsonPath": "./mirror/demo/entry/src/main/module.json5", + "es2pandaMode": 2 +} \ No newline at end of file diff --git a/arkui-plugins/test/mirror_test_config.js b/arkui-plugins/test/mirror_test_config.js new file mode 100644 index 000000000..ddd3f8d5d --- /dev/null +++ b/arkui-plugins/test/mirror_test_config.js @@ -0,0 +1,93 @@ +/* + * 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. + */ + +const fs = require('fs'); +const path = require('path'); + +function changePathToAbsPath(p) { + return path.resolve(p); +} + +// 获取当前目录 +const currentDirectory = process.cwd(); +let workSpace = currentDirectory; +for (let i = 0; i < 4; i++) { + workSpace = path.dirname(workSpace); +} +// JSON 文件路径 +const jsonFilePath = path.join(__dirname, 'mirror/mirror_test_config_template.json'); +const outJsonFilePath = path.join(__dirname, 'mirror/mirror_test_config.json'); + +try { + // 读取 JSON 文件内容 + const data = fs.readFileSync(jsonFilePath, 'utf8'); + const jsonData = JSON.parse(data); + console.log(jsonData) + // 处理 baseUrl 字段 + if (jsonData.buildSdkPath) { + jsonData.buildSdkPath = jsonData.buildSdkPath.replace(/workspace/g, workSpace); + } + + // 处理 plugins 字段 + if (jsonData.plugins.ui_syntax_plugin) { + jsonData.plugins.ui_syntax_plugin = jsonData.plugins.ui_syntax_plugin.replace(/workspace/g, workSpace); + } + if (jsonData.plugins.ui_plugin) { + jsonData.plugins.ui_plugin = jsonData.plugins.ui_plugin.replace(/workspace/g, workSpace); + } + if (jsonData.plugins.memo_plugin) { + jsonData.plugins.memo_plugin = jsonData.plugins.memo_plugin.replace(/workspace/g, workSpace); + } + + // compileFiles + if (jsonData.compileFiles) { + jsonData.compileFiles = jsonData.compileFiles.map((file) => changePathToAbsPath(file)); + } + + // entryFiles + if (jsonData.entryFiles) { + jsonData.entryFiles = jsonData.entryFiles.map((file) => changePathToAbsPath(file)); + } + + // moduleRootPath + if (jsonData.moduleRootPath) { + jsonData.moduleRootPath = changePathToAbsPath(jsonData.moduleRootPath); + } + + // sourceRoots + if (jsonData.sourceRoots) { + jsonData.sourceRoots = jsonData.sourceRoots.map((file) => changePathToAbsPath(file)); + } + + // loaderOutPath + if (jsonData.loaderOutPath) { + jsonData.loaderOutPath = changePathToAbsPath(jsonData.loaderOutPath); + } + + // loaderOutPath + if (jsonData.cachePath) { + jsonData.cachePath = changePathToAbsPath(jsonData.cachePath); + } + + // appModuleJsonPath + if (jsonData.aceModuleJsonPath) { + jsonData.aceModuleJsonPath = changePathToAbsPath(jsonData.aceModuleJsonPath); + } + + // 将修改后的内容写回 JSON 文件 + fs.writeFileSync(outJsonFilePath, JSON.stringify(jsonData, null, 2), 'utf8'); +} catch (error) { + console.error('处理 JSON 文件时出错:', error); +} \ No newline at end of file diff --git a/arkui-plugins/test/package.json b/arkui-plugins/test/package.json index abdbcd3e9..ec4f000cd 100644 --- a/arkui-plugins/test/package.json +++ b/arkui-plugins/test/package.json @@ -24,6 +24,7 @@ "localtest_all": "npm run localtest_decl && npm run localtest", "es2panda:compile": "node ./arktsconfig_gen.js && $INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/bin/es2panda --arktsconfig=./dist/cache/arktsconfig.json", "es2panda:test": "$INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/bin/es2panda ./demo/localtest/entry/test.ets --output=test.abc", - "mirrortest:decl": "rm -rf mirror/demo/hello_world/ && node mirror_decl_config.js && npm run compile:plugins && LD_LIBRARY_PATH=$INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/lib node $INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/driver/build-system/dist/entry.js ./mirror/mirror_decl_config.json" + "mirrortest:decl": "rm -rf mirror/demo/hello_world/ && node mirror_decl_config.js && npm run compile:plugins && LD_LIBRARY_PATH=$INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/lib node $INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/driver/build-system/dist/entry.js ./mirror/mirror_decl_config.json", + "mirrortest": "rm -rf mirror/dist && node mirror_test_config.js && npm run compile:plugins && LD_LIBRARY_PATH=$INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/ets2panda/lib node $INIT_CWD/../../../../out/sdk/ohos-sdk/linux/ets/ets1.2/build-tools/driver/build-system/dist/entry.js ./mirror/mirror_test_config.json" } } diff --git a/arkui-plugins/tsconfig.json b/arkui-plugins/tsconfig.json index 5ea0297ec..a36d9a813 100644 --- a/arkui-plugins/tsconfig.json +++ b/arkui-plugins/tsconfig.json @@ -28,7 +28,8 @@ "./interop-plugins/**/*.ts", "./path.ts", "./test/ut/**/*.ts", - "./test/utils/**/*.ts" + "./test/utils/**/*.ts", + "./mirror-replace/**/*.ts" ], "exclude": [ "./test/demo/", diff --git a/arkui-plugins/ui-plugins/component-transformer.ts b/arkui-plugins/ui-plugins/component-transformer.ts index 6c9642d8e..60b738fa4 100644 --- a/arkui-plugins/ui-plugins/component-transformer.ts +++ b/arkui-plugins/ui-plugins/component-transformer.ts @@ -536,7 +536,13 @@ export class ComponentTransformer extends AbstractVisitor { this.enter(node); const newNode = this.visitEachChild(node); if (arkts.isEtsScript(newNode)) { - return this.processEtsScript(newNode); + let res = this.processEtsScript(newNode); + if (arkts.getProgramFromAstNode(res).fileNameWithExtension === 'new.ets') { + console.log("==========================================================") + console.log(res.dumpSrc()) + console.log("==========================================================") + } + return res } if (isNewCustomDialogController(newNode)) { return transformController(newNode as arkts.ETSNewClassInstanceExpression); diff --git a/arkui-plugins/ui-plugins/entry-translators/factory.ts b/arkui-plugins/ui-plugins/entry-translators/factory.ts index e4dd29d22..6088750a0 100644 --- a/arkui-plugins/ui-plugins/entry-translators/factory.ts +++ b/arkui-plugins/ui-plugins/entry-translators/factory.ts @@ -133,6 +133,8 @@ export class factory { arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, false ); + console.log("entryScript: ", entryScript.dumpSrc()) + // console.log("def: ", def.dumpSrc()) return def; } -- Gitee From 3f40771e0e5b891969b7aa92f683dcbca0be9faa Mon Sep 17 00:00:00 2001 From: xieziang Date: Mon, 14 Jul 2025 22:59:24 +0800 Subject: [PATCH 5/5] update uiplugin part 2 Signed-off-by: xieziang Change-Id: I84852687f51ae639a00645d9d4d0e65329f19cfc --- .../collectors/memo-collectors/factory.ts | 95 ++ .../memo-collectors/function-collector.ts | 218 +++ .../collectors/memo-collectors/utils.ts | 1362 +++++++++-------- .../mirror-replace/common/arkts-utils.ts | 43 + .../common/declaration-collector.ts | 52 +- .../mirror-replace/common/gensym-generator.ts | 60 + .../mirror-replace/common/log-collector.ts | 75 + .../mirror-replace/common/predefines.ts | 88 +- .../builder-lambda-translators/factory.ts | 707 +++++++++ .../builder-lambda-translators/utils.ts | 562 +++++++ .../ui-plugins/checked-transformer.ts | 165 ++ .../ui-plugins/component-transformer.ts | 8 +- .../mirror-replace/ui-plugins/customdialog.ts | 72 +- .../ui-plugins/entry-translators/factory.ts | 4 +- .../mirror-replace/ui-plugins/index.ts | 6 +- .../ui-plugins/interop/interop.ts | 256 ++-- .../ui-plugins/property-translators/base.ts | 87 ++ .../property-translators/builderParam.ts | 171 +++ .../property-translators/consume.ts | 155 ++ .../property-translators/factory.ts | 1321 ++++++++-------- .../ui-plugins/property-translators/index.ts | 133 ++ .../ui-plugins/property-translators/link.ts | 176 +++ .../property-translators/localstoragelink.ts | 191 +++ .../property-translators/localstorageprop.ts | 244 +++ .../property-translators/objectlink.ts | 173 +++ .../property-translators/observedTrack.ts | 287 ++++ .../ui-plugins/property-translators/prop.ts | 200 +++ .../property-translators/provide.ts | 167 ++ .../property-translators/regularProperty.ts | 128 ++ .../ui-plugins/property-translators/state.ts | 160 ++ .../property-translators/staticProperty.ts | 52 + .../property-translators/storageProp.ts | 190 +++ .../property-translators/storagelink.ts | 189 +++ .../ui-plugins/property-translators/types.ts | 40 + .../ui-plugins/property-translators/utils.ts | 629 ++++---- .../ui-plugins/struct-translators/factory.ts | 937 ++++++++++++ .../ui-plugins/struct-translators/utils.ts | 522 +++++++ .../mirror-replace/ui-plugins/test.ts | 10 + .../mirror-replace/ui-plugins/ui-factory.ts | 270 ++-- .../mirror-replace/ui-plugins/utils.ts | 57 +- 40 files changed, 8224 insertions(+), 2038 deletions(-) create mode 100644 arkui-plugins/mirror-replace/collectors/memo-collectors/factory.ts create mode 100644 arkui-plugins/mirror-replace/collectors/memo-collectors/function-collector.ts create mode 100644 arkui-plugins/mirror-replace/common/gensym-generator.ts create mode 100644 arkui-plugins/mirror-replace/common/log-collector.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/builder-lambda-translators/factory.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/builder-lambda-translators/utils.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/checked-transformer.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/base.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/builderParam.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/consume.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/index.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/link.ts create mode 100755 arkui-plugins/mirror-replace/ui-plugins/property-translators/localstoragelink.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/localstorageprop.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/objectlink.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/observedTrack.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/prop.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/provide.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/regularProperty.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/state.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/staticProperty.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/storageProp.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/storagelink.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/property-translators/types.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/struct-translators/factory.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/struct-translators/utils.ts create mode 100644 arkui-plugins/mirror-replace/ui-plugins/test.ts diff --git a/arkui-plugins/mirror-replace/collectors/memo-collectors/factory.ts b/arkui-plugins/mirror-replace/collectors/memo-collectors/factory.ts new file mode 100644 index 000000000..ec6aad61f --- /dev/null +++ b/arkui-plugins/mirror-replace/collectors/memo-collectors/factory.ts @@ -0,0 +1,95 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { + addMemoAnnotation, + collectMemoFromCallExpression, + findCanAddMemoFromArrowFunction, + findCanAddMemoFromClassProperty, + findCanAddMemoFromMethod, + findCanAddMemoFromParameter, + findCanAddMemoFromProperty, + findCanAddMemoFromTypeAlias, +} from './utils'; + +export function findAndCollectMemoableNode(node: mirrorArkts.AstNode): mirrorArkts.AstNode { + const type = mirrorArkts.nodeType(node); + if (collectByType.has(type)) { + return collectByType.get(type)!(node); + } + return node; +} + +export class factory { + static findAndCollectMemoableProperty(node: mirrorArkts.Property): mirrorArkts.Property { + if (findCanAddMemoFromProperty(node)) { + addMemoAnnotation(node.value! as mirrorArkts.ArrowFunctionExpression); + } + return node; + } + + static findAndCollectMemoableClassProperty(node: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + if (findCanAddMemoFromClassProperty(node)) { + addMemoAnnotation(node); + } + return node; + } + + static findAndCollectMemoableTypeAlias(node: mirrorArkts.TSTypeAliasDeclaration): mirrorArkts.TSTypeAliasDeclaration { + if (findCanAddMemoFromTypeAlias(node)) { + addMemoAnnotation(node); + } + return node; + } + + static findAndCollectMemoableParameter(node: mirrorArkts.ETSParameterExpression): mirrorArkts.ETSParameterExpression { + if (findCanAddMemoFromParameter(node)) { + addMemoAnnotation(node); + } + return node; + } + + static findAndCollectMemoableMethod(node: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + if (findCanAddMemoFromMethod(node)) { + addMemoAnnotation(node.function!); + } + return node; + } + + static findAndCollectMemoableArrowFunction(node: mirrorArkts.ArrowFunctionExpression): mirrorArkts.ArrowFunctionExpression { + if (findCanAddMemoFromArrowFunction(node)) { + addMemoAnnotation(node.function!); + } + return node; + } + + static findAndCollectMemoableCallExpression(node: mirrorArkts.CallExpression): mirrorArkts.CallExpression { + collectMemoFromCallExpression(node); + return node; + } +} + +type CollectFactoryFn = (node: any) => mirrorArkts.AstNode; + +const collectByType = new Map([ + [mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_PROPERTY, factory.findAndCollectMemoableProperty], + [mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_PROPERTY, factory.findAndCollectMemoableClassProperty], + [mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_ALIAS_DECLARATION, factory.findAndCollectMemoableTypeAlias], + [mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_PARAMETER_EXPRESSION, factory.findAndCollectMemoableParameter], + [mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_METHOD_DEFINITION, factory.findAndCollectMemoableMethod], + [mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_ARROW_FUNCTION_EXPRESSION, factory.findAndCollectMemoableArrowFunction], + [mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_CALL_EXPRESSION, factory.findAndCollectMemoableCallExpression], +]); diff --git a/arkui-plugins/mirror-replace/collectors/memo-collectors/function-collector.ts b/arkui-plugins/mirror-replace/collectors/memo-collectors/function-collector.ts new file mode 100644 index 000000000..b1d36261d --- /dev/null +++ b/arkui-plugins/mirror-replace/collectors/memo-collectors/function-collector.ts @@ -0,0 +1,218 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +// import { AbstractVisitor } from '../../common/abstract-visitor'; +// import { +// checkIsMemoFromMemoableInfo, +// collectMemoableInfoInFunctionReturnType, +// collectMemoableInfoInScriptFunction, +// collectMemoableInfoInVariableDeclarator, +// collectMemoableInfoMapInFunctionParams, +// collectMemoScriptFunctionBody, +// findIdentifierFromCallee, +// getDeclResolveAlias, +// MemoableInfo, +// } from './utils'; + +export class MemoFunctionCollector extends AbstractVisitor { + private returnMemoableInfo: MemoableInfo | undefined; + private paramMemoableInfoMap: Map | undefined; + private _disableCollectReturn: boolean = false; + private _shouldCollectReturn: boolean = true; + + private get shouldCollectReturn(): boolean { + if (this._disableCollectReturn) { + return false; + } + return this._shouldCollectReturn; + } + + private set shouldCollectReturn(newValue: boolean) { + if (this._disableCollectReturn) { + return; + } + this._shouldCollectReturn = newValue; + } + + private collectMemoAstNode(node: mirrorArkts.AstNode, info: MemoableInfo): void { + if (checkIsMemoFromMemoableInfo(info, false)) { + mirrorArkts.NodeCache.getInstance().collect(node); + } + } + + private collectCallWithDeclaredPeerInParamMap(node: mirrorArkts.CallExpression, peer: mirrorArkts.AstNode['peer']): void { + const memoableInfo = this.paramMemoableInfoMap!.get(peer)!; + if (checkIsMemoFromMemoableInfo(memoableInfo, true)) { + mirrorArkts.NodeCache.getInstance().collect(node); + } + } + + private collectCallWithDeclaredIdInVariableDeclarator( + node: mirrorArkts.CallExpression, + declarator: mirrorArkts.VariableDeclarator + ): void { + const shouldCollect = + mirrorArkts.NodeCache.getInstance().has(declarator) || + (!!declarator.initializer && mirrorArkts.NodeCache.getInstance().has(declarator.initializer)); + if (shouldCollect) { + mirrorArkts.NodeCache.getInstance().collect(node); + } + } + + private visitVariableDeclarator(node: mirrorArkts.VariableDeclarator): mirrorArkts.AstNode { + let memoableInfo: MemoableInfo; + if (this.paramMemoableInfoMap?.has(node.name.peer)) { + memoableInfo = this.paramMemoableInfoMap.get(node.name.peer)!; + } else { + memoableInfo = collectMemoableInfoInVariableDeclarator(node); + } + this.collectMemoAstNode(node, memoableInfo); + if (!node.initializer) { + return node; + } + if (mirrorArkts.isArrowFunctionExpression(node.initializer)) { + const localInfo = collectMemoableInfoInScriptFunction(node.initializer.scriptFunction); + const shouldCollectParameter = + (localInfo.hasBuilder || localInfo.hasMemo) && !localInfo.hasMemoEntry && !localInfo.hasMemoIntrinsic; + const shouldCollectReturn = + localInfo.hasBuilder || localInfo.hasMemo || memoableInfo.hasBuilder || memoableInfo.hasMemo; + const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(node.initializer.scriptFunction); + const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams( + node.initializer.scriptFunction, + shouldCollectParameter + ); + if ( + !!node.initializer.scriptFunction.body && + mirrorArkts.isBlockStatement(node.initializer.scriptFunction.body) + ) { + collectMemoScriptFunctionBody( + node.initializer.scriptFunction.body, + returnMemoableInfo, + paramMemoableInfoMap, + gensymCount, + !shouldCollectReturn + ); + } + return node; + } + this.shouldCollectReturn = !!memoableInfo.hasMemo || !!memoableInfo.hasBuilder; + this.visitor(node.initializer); + return node; + } + + private visitCallExpression(node: mirrorArkts.CallExpression): mirrorArkts.AstNode { + if (mirrorArkts.NodeCache.getInstance().has(node)) { + this.shouldCollectReturn = false; + this.visitEachChild(node); + this.shouldCollectReturn = true; + return node; + } + const expr = findIdentifierFromCallee(node.expression); + const decl = (expr && getDeclResolveAlias(expr)) ?? node.expression; + if (!decl) { + this.shouldCollectReturn = false; + this.visitEachChild(node); + this.shouldCollectReturn = true; + return node; + } + if (mirrorArkts.NodeCache.getInstance().has(decl)) { + mirrorArkts.NodeCache.getInstance().collect(node); + } + if (this.paramMemoableInfoMap?.has(decl.peer)) { + this.collectCallWithDeclaredPeerInParamMap(node, decl.peer); + } else if (mirrorArkts.isEtsParameterExpression(decl) && this.paramMemoableInfoMap?.has(decl.identifier.peer)) { + this.collectCallWithDeclaredPeerInParamMap(node, decl.identifier.peer); + } else if (mirrorArkts.isIdentifier(decl) && !!decl.parent && mirrorArkts.isVariableDeclarator(decl.parent)) { + this.collectCallWithDeclaredIdInVariableDeclarator(node, decl.parent); + } + this.shouldCollectReturn = false; + this.visitEachChild(node); + this.shouldCollectReturn = true; + return node; + } + + private visitIdentifier(node: mirrorArkts.Identifier): mirrorArkts.AstNode { + const decl = getDeclResolveAlias(node); + if (!decl) { + return node; + } + if (this.paramMemoableInfoMap?.has(decl.peer)) { + mirrorArkts.NodeCache.getInstance().collect(node); + } else if (mirrorArkts.isEtsParameterExpression(decl) && this.paramMemoableInfoMap?.has(decl.identifier.peer)) { + mirrorArkts.NodeCache.getInstance().collect(node); + } + return node; + } + + private visitReturnStatement(node: mirrorArkts.ReturnStatement): mirrorArkts.AstNode { + if (!!this.returnMemoableInfo && !!node.argument && mirrorArkts.isArrowFunctionExpression(node.argument)) { + this.collectMemoAstNode(node.argument, this.returnMemoableInfo); + } + mirrorArkts.NodeCache.getInstance().collect(node); + this.visitEachChild(node); + return node; + } + + registerReturnInfo(info: MemoableInfo): this { + this.returnMemoableInfo = info; + return this; + } + + registerParamInfoMap(infoMap: Map): this { + this.paramMemoableInfoMap = infoMap; + return this; + } + + disableCollectReturn(): this { + this._disableCollectReturn = true; + return this; + } + + enableCollectReturn(): this { + this._disableCollectReturn = false; + return this; + } + + reset(): void { + this.returnMemoableInfo = undefined; + this.paramMemoableInfoMap = undefined; + this._shouldCollectReturn = true; + this._disableCollectReturn = false; + } + + visitor(node: mirrorArkts.AstNode): mirrorArkts.AstNode { + if (mirrorArkts.isVariableDeclarator(node)) { + return this.visitVariableDeclarator(node); + } + if (mirrorArkts.isCallExpression(node)) { + return this.visitCallExpression(node); + } + if (!!this.paramMemoableInfoMap && mirrorArkts.isIdentifier(node)) { + return this.visitIdentifier(node); + } + if (mirrorArkts.isReturnStatement(node) && this.shouldCollectReturn) { + return this.visitReturnStatement(node); + } + if ( + mirrorArkts.isArrowFunctionExpression(node) && + !mirrorArkts.NodeCache.getInstance().has(node) && + !mirrorArkts.NodeCache.getInstance().has(node.scriptFunction) + ) { + this.shouldCollectReturn = false; + } + return this.visitEachChild(node); + } +} diff --git a/arkui-plugins/mirror-replace/collectors/memo-collectors/utils.ts b/arkui-plugins/mirror-replace/collectors/memo-collectors/utils.ts index 28a69320a..24335feb9 100644 --- a/arkui-plugins/mirror-replace/collectors/memo-collectors/utils.ts +++ b/arkui-plugins/mirror-replace/collectors/memo-collectors/utils.ts @@ -14,10 +14,10 @@ */ import * as mirrorArkts from '@koalaui/libarkts-mirror'; -// import { annotation, forEachArgWithParam, isDecoratorAnnotation } from '../../common/arkts-utils'; -// import { ImportCollector } from '../../common/import-collector'; -// import { DecoratorNames, GenSymPrefix, MEMO_IMPORT_SOURCE_NAME } from '../../common/predefines'; -// import { MemoFunctionCollector } from './function-collector'; +import { annotation, forEachArgWithParam, isDecoratorAnnotation } from '../../common/arkts-utils'; +import { ImportCollector } from '../../common/import-collector'; +import { DecoratorNames, GenSymPrefix, MEMO_IMPORT_SOURCE_NAME } from '../../common/predefines'; +import { MemoFunctionCollector } from './function-collector'; export enum MemoNames { MEMO = 'memo', @@ -26,41 +26,41 @@ export enum MemoNames { MEMO_ENTRY = 'memo_entry', } -// export type MemoAstNode = -// | mirrorArkts.ScriptFunction -// | mirrorArkts.ETSParameterExpression -// | mirrorArkts.ClassProperty -// | mirrorArkts.TSTypeAliasDeclaration -// | mirrorArkts.ETSFunctionType -// | mirrorArkts.ArrowFunctionExpression -// | mirrorArkts.ETSUnionType -// | mirrorArkts.VariableDeclaration; - -// interface MemoableAnnotationInfo { -// hasMemo?: boolean; -// hasMemoSkip?: boolean; -// hasMemoIntrinsic?: boolean; -// hasMemoEntry?: boolean; -// hasBuilder?: boolean; -// hasBuilderParam?: boolean; -// } +export type MemoAstNode = + | mirrorArkts.ScriptFunction + | mirrorArkts.ETSParameterExpression + | mirrorArkts.ClassProperty + | mirrorArkts.TSTypeAliasDeclaration + | mirrorArkts.ETSFunctionType + | mirrorArkts.ArrowFunctionExpression + | mirrorArkts.ETSUnionType + | mirrorArkts.VariableDeclaration; + +interface MemoableAnnotationInfo { + hasMemo?: boolean; + hasMemoSkip?: boolean; + hasMemoIntrinsic?: boolean; + hasMemoEntry?: boolean; + hasBuilder?: boolean; + hasBuilderParam?: boolean; +} -// export type MemoableInfo = MemoableAnnotationInfo & { -// hasProperType?: boolean; -// }; +export type MemoableInfo = MemoableAnnotationInfo & { + hasProperType?: boolean; +}; -// export function isMemoAnnotation(node: mirrorArkts.AnnotationUsage, memoName: MemoNames): boolean { -// return node.expr !== undefined && mirrorArkts.isIdentifier(node.expr) && node.expr.name === memoName; -// } +export function isMemoAnnotation(node: mirrorArkts.AnnotationUsage, memoName: MemoNames): boolean { + return node.expr !== undefined && mirrorArkts.isIdentifier(node.expr) && node.expr.name === memoName; +} -// export function hasMemoAnnotation(node: T): boolean { -// return node.annotations.some((it) => isMemoAnnotation(it, MemoNames.MEMO)); -// } +export function hasMemoAnnotation(node: T): boolean { + return node.annotations.some((it) => isMemoAnnotation(it, MemoNames.MEMO)); +} export function addMemoAnnotation(node: T, memoName: MemoNames = MemoNames.MEMO): T { collectMemoAnnotationSource(memoName); if (mirrorArkts.isETSUnionType(node)) { - return mirrorArkts.factory.updateUnionType( + return mirrorArkts.factory.updateETSUnionType( node, node.types.map((type) => { if (mirrorArkts.isETSFunctionType(type)) { @@ -75,152 +75,152 @@ export function addMemoAnnotation(node: T, memoName: Memo annotation(memoName), ]; collectMemoAnnotationImport(memoName); - if (mirrorArkts.isEtsParameterExpression(node)) { - node.annotations = newAnnotations; - mirrorArkts.NodeCache.getInstance().collect(node); + if (mirrorArkts.isETSParameterExpression(node)) { + node.setAnnotations(newAnnotations); + mirrorArkts.AstNodeCache.getInstance().collect(node); return node; } const newNode = node.setAnnotations(newAnnotations) as T; - mirrorArkts.NodeCache.getInstance().collect(newNode); + mirrorArkts.AstNodeCache.getInstance().collect(newNode); return newNode; } -// export function hasMemoableAnnotation(node: T): MemoableAnnotationInfo { -// let hasBuilder: boolean = false; -// let hasBuilderParam: boolean = false; -// let hasMemo: boolean = false; -// let hasMemoSkip: boolean = false; -// let hasMemoIntrinsic: boolean = false; -// let hasMemoEntry: boolean = false; -// node.annotations.forEach((it) => { -// hasBuilder ||= isDecoratorAnnotation(it, DecoratorNames.BUILDER); -// hasBuilderParam ||= isDecoratorAnnotation(it, DecoratorNames.BUILDER_PARAM); -// hasMemo ||= isMemoAnnotation(it, MemoNames.MEMO); -// hasMemoSkip ||= isMemoAnnotation(it, MemoNames.MEMO_SKIP); -// hasMemoIntrinsic ||= isMemoAnnotation(it, MemoNames.MEMO_INTRINSIC); -// hasMemoEntry ||= isMemoAnnotation(it, MemoNames.MEMO_ENTRY); -// }); -// return { -// ...(hasMemo ? { hasMemo } : {}), -// ...(hasMemoSkip ? { hasMemoSkip } : {}), -// ...(hasMemoIntrinsic ? { hasMemoIntrinsic } : {}), -// ...(hasMemoEntry ? { hasMemoEntry } : {}), -// ...(hasBuilder ? { hasBuilder } : {}), -// ...(hasBuilderParam ? { hasBuilderParam } : {}), -// }; -// } +export function hasMemoableAnnotation(node: T): MemoableAnnotationInfo { + let hasBuilder: boolean = false; + let hasBuilderParam: boolean = false; + let hasMemo: boolean = false; + let hasMemoSkip: boolean = false; + let hasMemoIntrinsic: boolean = false; + let hasMemoEntry: boolean = false; + node.annotations.forEach((it) => { + hasBuilder ||= isDecoratorAnnotation(it, DecoratorNames.BUILDER); + hasBuilderParam ||= isDecoratorAnnotation(it, DecoratorNames.BUILDER_PARAM); + hasMemo ||= isMemoAnnotation(it, MemoNames.MEMO); + hasMemoSkip ||= isMemoAnnotation(it, MemoNames.MEMO_SKIP); + hasMemoIntrinsic ||= isMemoAnnotation(it, MemoNames.MEMO_INTRINSIC); + hasMemoEntry ||= isMemoAnnotation(it, MemoNames.MEMO_ENTRY); + }); + return { + ...(hasMemo ? { hasMemo } : {}), + ...(hasMemoSkip ? { hasMemoSkip } : {}), + ...(hasMemoIntrinsic ? { hasMemoIntrinsic } : {}), + ...(hasMemoEntry ? { hasMemoEntry } : {}), + ...(hasBuilder ? { hasBuilder } : {}), + ...(hasBuilderParam ? { hasBuilderParam } : {}), + }; +} -// export function collectMemoAnnotationImport(memoName: MemoNames = MemoNames.MEMO): void { -// ImportCollector.getInstance().collectImport(memoName); -// } +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 collectMemoAnnotationSource(memoName: MemoNames = MemoNames.MEMO): void { + ImportCollector.getInstance().collectSource(memoName, MEMO_IMPORT_SOURCE_NAME); +} -// export function collectMemoableInfoInUnionType(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { -// let currInfo = info ?? {}; -// if (mirrorArkts.NodeCache.getInstance().has(node)) { -// return { ...currInfo, hasMemo: true, hasProperType: true }; -// } -// if (!mirrorArkts.isETSUnionType(node)) { -// return currInfo; -// } -// node.types.forEach((t) => { -// currInfo = { -// ...currInfo, -// ...collectMemoableInfoInTypeReference(t), -// ...collectMemoableInfoInFunctionType(t), -// ...collectMemoableInfoInUnionType(t), -// }; -// }); -// currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; -// return currInfo; -// } +export function collectMemoableInfoInUnionType(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (mirrorArkts.AstNodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!mirrorArkts.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: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { -// let currInfo = info ?? {}; -// if (mirrorArkts.NodeCache.getInstance().has(node)) { -// return { ...currInfo, hasMemo: true, hasProperType: true }; -// } -// if (!mirrorArkts.isETSTypeReference(node) || !node.part || !mirrorArkts.isETSTypeReferencePart(node.part)) { -// return currInfo; -// } -// const expr = node.part.name; -// let decl: mirrorArkts.AstNode | undefined; -// if (!expr || !(decl = mirrorArkts.getDecl(expr))) { -// return currInfo; -// } -// return { -// ...currInfo, -// ...collectMemoableInfoInTypeAlias(decl), -// }; -// } +export function collectMemoableInfoInTypeReference(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (mirrorArkts.AstNodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!mirrorArkts.isETSTypeReference(node) || !node.part || !mirrorArkts.isETSTypeReferencePart(node.part)) { + return currInfo; + } + const expr = node.part.name; + let decl: mirrorArkts.AstNode | undefined; + if (!expr || !(decl = mirrorArkts.getDecl(expr))) { + return currInfo; + } + return { + ...currInfo, + ...collectMemoableInfoInTypeAlias(decl), + }; +} -// export function collectMemoableInfoInFunctionType(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { -// let currInfo = info ?? {}; -// if (mirrorArkts.NodeCache.getInstance().has(node)) { -// return { ...currInfo, hasMemo: true, hasProperType: true }; -// } -// if (!mirrorArkts.isETSFunctionType(node)) { -// return currInfo; -// } -// currInfo.hasProperType = true; -// currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; -// return currInfo; -// } +export function collectMemoableInfoInFunctionType(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (mirrorArkts.AstNodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!mirrorArkts.isETSFunctionType(node)) { + return currInfo; + } + currInfo.hasProperType = true; + currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; + return currInfo; +} -// export function collectMemoableInfoInTypeAlias(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { -// let currInfo = info ?? {}; -// if (mirrorArkts.NodeCache.getInstance().has(node)) { -// return { ...currInfo, hasMemo: true, hasProperType: true }; -// } -// if (!mirrorArkts.isTSTypeAliasDeclaration(node)) { -// return currInfo; -// } -// currInfo = { -// ...currInfo, -// ...hasMemoableAnnotation(node), -// }; -// if (!!node.typeAnnotation) { -// return { -// ...currInfo, -// ...collectMemoableInfoInType(node.typeAnnotation), -// }; -// } -// return currInfo; -// } +export function collectMemoableInfoInTypeAlias(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (mirrorArkts.AstNodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!mirrorArkts.isTSTypeAliasDeclaration(node)) { + return currInfo; + } + currInfo = { + ...currInfo, + ...hasMemoableAnnotation(node), + }; + if (!!node.typeAnnotation) { + return { + ...currInfo, + ...collectMemoableInfoInType(node.typeAnnotation), + }; + } + return currInfo; +} -// export function collectMemoableInfoInParameter(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { -// let currInfo = info ?? {}; -// if (mirrorArkts.NodeCache.getInstance().has(node)) { -// return { ...currInfo, hasMemo: true, hasProperType: true }; -// } -// if (!mirrorArkts.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 collectMemoableInfoInParameter(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + // if (mirrorArkts.AstNodeCache.getInstance().has(node)) { + // return { ...currInfo, hasMemo: true, hasProperType: true }; + // } + if (!mirrorArkts.isETSParameterExpression(node)) { + return currInfo; + } + currInfo = { + ...currInfo, + ...hasMemoableAnnotation(node), + }; + if (!!node.typeAnnotation) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInType(node.typeAnnotation), + }; + } + if (!!node.initializer) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInArrowFunction(node.initializer), + }; + } + return currInfo; +} // export function collectMemoableInfoInVariableDeclarator(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { // let currInfo = info ?? {}; -// if (mirrorArkts.NodeCache.getInstance().has(node)) { +// if (mirrorArkts.AstNodeCache.getInstance().has(node)) { // return { ...currInfo, hasMemo: true, hasProperType: true }; // } // if (!mirrorArkts.isVariableDeclarator(node)) { @@ -262,245 +262,248 @@ export function addMemoAnnotation(node: T, memoName: Memo // return currInfo; // } -// export function collectMemoableInfoInProperty(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { -// let currInfo = info ?? {}; -// if (mirrorArkts.NodeCache.getInstance().has(node)) { -// const property = node as mirrorArkts.Property; -// const hasProperType = !!property.value && mirrorArkts.isArrowFunctionExpression(property.value); -// return { ...currInfo, hasMemo: true, hasProperType }; -// } -// if (!mirrorArkts.isProperty(node) || !node.key || !mirrorArkts.isIdentifier(node.key)) { -// return currInfo; -// } -// const decl = mirrorArkts.getDecl(node.key); -// if (!decl || !mirrorArkts.isMethodDefinition(decl)) { -// return currInfo; -// } -// const hasReceiver = decl.scriptFunction.hasReceiver; -// const isSetter = decl.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; -// const isGetter = decl.kind === mirrorArkts.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.hasProperType = false; -// if (!!node.value && mirrorArkts.isArrowFunctionExpression(node.value)) { -// currInfo = { -// ...currInfo, -// ...collectMemoableInfoInScriptFunction(node.value.scriptFunction), -// }; -// } -// return currInfo; -// } +export function collectMemoableInfoInProperty(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (mirrorArkts.AstNodeCache.getInstance().has(node)) { + const property = node as mirrorArkts.Property; + const hasProperType = !!property.value && mirrorArkts.isArrowFunctionExpression(property.value); + return { ...currInfo, hasMemo: true, hasProperType }; + } + if (!mirrorArkts.isProperty(node) || !node.key || !mirrorArkts.isIdentifier(node.key)) { + return currInfo; + } + const decl = mirrorArkts.getDecl(node.key); + if (!decl || !mirrorArkts.isMethodDefinition(decl)) { + return currInfo; + } + const hasReceiver = decl.function!.hasReceiver; + const isSetter = decl.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; + const isGetter = decl.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; + let newInfo: MemoableInfo = {}; + if (isSetter && decl.function!.params.length > 0) { + if (hasReceiver && decl.function!.params.length === 2) { + newInfo = collectMemoableInfoInParameter(decl.function!.params.at(1)!); + } else { + newInfo = collectMemoableInfoInParameter(decl.function!.params.at(0)!); + } + } else if (isGetter) { + newInfo = collectMemoableInfoInFunctionReturnType(decl.function!); + } + currInfo = { ...currInfo, ...collectMemoableInfoInScriptFunction(decl.function!), ...newInfo }; + currInfo.hasProperType = false; + if (!!node.value && mirrorArkts.isArrowFunctionExpression(node.value)) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInScriptFunction(node.value.function!), + }; + } + return currInfo; +} -// export function collectMemoableInfoInClassProperty(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { -// let currInfo = info ?? {}; -// if (mirrorArkts.NodeCache.getInstance().has(node)) { -// return { ...currInfo, hasMemo: true, hasProperType: true }; -// } -// if (!mirrorArkts.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 collectMemoableInfoInClassProperty(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (mirrorArkts.AstNodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!mirrorArkts.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: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { -// let currInfo = info ?? {}; -// if (mirrorArkts.NodeCache.getInstance().has(node)) { -// return { ...currInfo, hasMemo: true, hasProperType: true }; -// } -// if (!mirrorArkts.isArrowFunctionExpression(node)) { -// return currInfo; -// } -// currInfo.hasProperType = true; -// currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; -// if (!!node.scriptFunction) { -// currInfo = { -// ...currInfo, -// ...collectMemoableInfoInScriptFunction(node.scriptFunction), -// }; -// } -// if (!!node.parent && mirrorArkts.isAssignmentExpression(node.parent) && !!node.parent.left) { -// const expr = mirrorArkts.isMemberExpression(node.parent.left) ? node.parent.left.property : node.parent.left; -// const decl = mirrorArkts.getDecl(expr); -// if (!decl) { -// return currInfo; -// } -// if (mirrorArkts.isClassProperty(decl)) { -// currInfo = { -// ...currInfo, -// ...collectMemoableInfoInClassProperty(decl), -// }; -// } -// } -// return currInfo; -// } +export function collectMemoableInfoInArrowFunction(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (mirrorArkts.AstNodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!mirrorArkts.isArrowFunctionExpression(node)) { + return currInfo; + } + currInfo.hasProperType = true; + currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; + if (!!node.function) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInScriptFunction(node.function), + }; + } + if (!!node.parent && mirrorArkts.isAssignmentExpression(node.parent) && !!node.parent.left) { + const expr = mirrorArkts.isMemberExpression(node.parent.left) ? node.parent.left.property : node.parent.left; + if (!expr) { + return currInfo; + } + const decl = mirrorArkts.getDecl(expr); + if (!decl) { + return currInfo; + } + if (mirrorArkts.isClassProperty(decl)) { + currInfo = { + ...currInfo, + ...collectMemoableInfoInClassProperty(decl), + }; + } + } + return currInfo; +} -// export function collectMemoableInfoInScriptFunction(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { -// let currInfo = info ?? {}; -// if (mirrorArkts.NodeCache.getInstance().has(node)) { -// return { ...currInfo, hasMemo: true, hasProperType: true }; -// } -// if (!mirrorArkts.isScriptFunction(node)) { -// return currInfo; -// } -// currInfo.hasProperType = true; -// currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; -// return currInfo; -// } +export function collectMemoableInfoInScriptFunction(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + if (mirrorArkts.AstNodeCache.getInstance().has(node)) { + return { ...currInfo, hasMemo: true, hasProperType: true }; + } + if (!mirrorArkts.isScriptFunction(node)) { + return currInfo; + } + currInfo.hasProperType = true; + currInfo = { ...currInfo, ...hasMemoableAnnotation(node) }; + return currInfo; +} -// export function collectMemoableInfoInMethod(node: mirrorArkts.MethodDefinition): MemoableInfo { -// const hasReceiver = node.scriptFunction.hasReceiver; -// const isSetter = node.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; -// const isGetter = node.kind === mirrorArkts.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); -// } -// return collectMemoableInfoInScriptFunction(node.scriptFunction, info); -// } +export function collectMemoableInfoInMethod(node: mirrorArkts.MethodDefinition): MemoableInfo { + const hasReceiver = node.function!.hasReceiver; + const isSetter = node.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; + const isGetter = node.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; + let info: MemoableInfo = {}; + if (isSetter && node.function!.params.length > 0) { + if (hasReceiver && node.function!.params.length === 2) { + info = collectMemoableInfoInParameter(node.function!.params.at(1)!); + } else { + info = collectMemoableInfoInParameter(node.function!.params.at(0)!); + } + } else if (isGetter) { + info = collectMemoableInfoInFunctionReturnType(node.function!); + } + return collectMemoableInfoInScriptFunction(node.function!, info); +} -// export function collectMemoableInfoInType(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { -// let currInfo = info ?? {}; -// return { -// ...currInfo, -// ...collectMemoableInfoInFunctionType(node), -// ...collectMemoableInfoInUnionType(node), -// ...collectMemoableInfoInTypeReference(node), -// }; -// } +export function collectMemoableInfoInType(node: mirrorArkts.AstNode, info?: MemoableInfo): MemoableInfo { + let currInfo = info ?? {}; + return { + ...currInfo, + ...collectMemoableInfoInFunctionType(node), + ...collectMemoableInfoInUnionType(node), + ...collectMemoableInfoInTypeReference(node), + }; +} -// export function collectMemoableInfoInFunctionReturnType(node: mirrorArkts.ScriptFunction): MemoableInfo { -// if (!!node.returnTypeAnnotation) { -// let memoableInfo: MemoableInfo; -// if (mirrorArkts.NodeCache.getInstance().has(node.returnTypeAnnotation)) { -// memoableInfo = { hasMemo: true, hasProperType: true }; -// } else { -// memoableInfo = collectMemoableInfoInType(node.returnTypeAnnotation); -// } -// if ((memoableInfo.hasMemo || memoableInfo.hasBuilder) && memoableInfo.hasProperType) { -// mirrorArkts.NodeCache.getInstance().collect(node.returnTypeAnnotation); -// } -// return memoableInfo; -// } -// return {}; -// } +export function collectMemoableInfoInFunctionReturnType(node: mirrorArkts.ScriptFunction): MemoableInfo { + if (!!node.returnTypeAnnotation) { + let memoableInfo: MemoableInfo; + if (mirrorArkts.AstNodeCache.getInstance().has(node.returnTypeAnnotation)) { + memoableInfo = { hasMemo: true, hasProperType: true }; + } else { + memoableInfo = collectMemoableInfoInType(node.returnTypeAnnotation); + } + if ((memoableInfo.hasMemo || memoableInfo.hasBuilder) && memoableInfo.hasProperType) { + mirrorArkts.AstNodeCache.getInstance().collect(node.returnTypeAnnotation); + } + return memoableInfo; + } + return {}; +} -// export function collectGensymDeclarator(declarator: mirrorArkts.VariableDeclarator, info: MemoableInfo): void { -// if (!info.hasMemo && !info.hasBuilder) { -// return; -// } -// mirrorArkts.NodeCache.getInstance().collect(declarator); -// const initializer = declarator.initializer; -// if (!initializer || !mirrorArkts.isConditionalExpression(initializer)) { -// return; -// } -// const alternate = initializer.alternate; -// if (!alternate) { -// return; -// } -// let arrowFunc: mirrorArkts.ArrowFunctionExpression | undefined; -// if (mirrorArkts.isTSAsExpression(alternate) && !!alternate.expr && mirrorArkts.isArrowFunctionExpression(alternate.expr)) { -// arrowFunc = alternate.expr; -// } else if (mirrorArkts.isArrowFunctionExpression(alternate)) { -// arrowFunc = alternate; -// } -// if (!!arrowFunc) { -// const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(arrowFunc.scriptFunction); -// const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams(arrowFunc.scriptFunction); -// if (!!arrowFunc.scriptFunction.body && mirrorArkts.isBlockStatement(arrowFunc.scriptFunction.body)) { -// collectMemoScriptFunctionBody( -// arrowFunc.scriptFunction.body, -// returnMemoableInfo, -// paramMemoableInfoMap, -// gensymCount -// ); -// } -// } -// } +export function collectGensymDeclarator(declarator: mirrorArkts.VariableDeclarator, info: MemoableInfo): void { + if (!info.hasMemo && !info.hasBuilder) { + return; + } + mirrorArkts.AstNodeCache.getInstance().collect(declarator); + const initializer = declarator.init; + if (!initializer || !mirrorArkts.isConditionalExpression(initializer)) { + return; + } + const alternate = initializer.alternate; + if (!alternate) { + return; + } + let arrowFunc: mirrorArkts.ArrowFunctionExpression | undefined; + if (mirrorArkts.isTSAsExpression(alternate) && !!alternate.expr && mirrorArkts.isArrowFunctionExpression(alternate.expr)) { + arrowFunc = alternate.expr; + } else if (mirrorArkts.isArrowFunctionExpression(alternate)) { + arrowFunc = alternate; + } + if (!!arrowFunc && !!arrowFunc.function) { + const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(arrowFunc.function); + const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams(arrowFunc.function); + if (!!arrowFunc.function.body && mirrorArkts.isBlockStatement(arrowFunc.function.body)) { + collectMemoScriptFunctionBody( + arrowFunc.function.body, + returnMemoableInfo, + paramMemoableInfoMap, + gensymCount + ); + } + } +} -// export function collectMemoableInfoMapInFunctionParams( -// node: mirrorArkts.ScriptFunction, -// shouldCollectParameter: boolean = true -// ): [Map, number] { -// const hasReceiver = node.hasReceiver; -// const paramMap: Map = new Map(); -// let gensymCount: number = 0; -// node.params.slice(hasReceiver ? 1 : 0).forEach((p) => { -// const info = collectMemoableInfoInFunctionParam(node, p, gensymCount, shouldCollectParameter); -// gensymCount = info.gensymCount; -// info.peers.forEach((peer) => paramMap.set(peer, info.memoableInfo)); -// }); -// return [paramMap, gensymCount]; -// } +export function collectMemoableInfoMapInFunctionParams( + node: mirrorArkts.ScriptFunction, + shouldCollectParameter: boolean = true +): [Map, number] { + const hasReceiver = node.hasReceiver; + const paramMap: Map = new Map(); + let gensymCount: number = 0; + node.params.slice(hasReceiver ? 1 : 0).forEach((p) => { + const info = collectMemoableInfoInFunctionParam(node, p, gensymCount, shouldCollectParameter); + gensymCount = info.gensymCount; + info.peers.forEach((peer) => paramMap.set(peer, info.memoableInfo)); + }); + return [paramMap, gensymCount]; +} -// interface FunctionParamCollectInfo { -// peers: mirrorArkts.AstNode['peer'][]; -// gensymCount: number; -// memoableInfo: MemoableInfo; -// } +interface FunctionParamCollectInfo { + peers: mirrorArkts.AstNode['peer'][]; + gensymCount: number; + memoableInfo: MemoableInfo; +} -// function collectMemoableInfoInFunctionParam( -// node: mirrorArkts.ScriptFunction, -// param: mirrorArkts.Expression, -// gensymCount: number, -// shouldCollectParameter: boolean = true -// ): FunctionParamCollectInfo { -// const peers: mirrorArkts.AstNode['peer'][] = []; -// let memoableInfo: MemoableInfo; -// const _param = param as mirrorArkts.ETSParameterExpression; -// if (mirrorArkts.NodeCache.getInstance().has(_param)) { -// const metadata = mirrorArkts.NodeCache.getInstance().get(_param)!.metadata ?? {}; -// const { hasMemoSkip } = metadata; -// memoableInfo = { hasMemo: true, hasMemoSkip, hasProperType: true }; -// } else { -// memoableInfo = collectMemoableInfoInParameter(_param); -// } -// if (_param.identifier.name.startsWith(GenSymPrefix.INTRINSIC) && !!node.body && mirrorArkts.isBlockStatement(node.body)) { -// const declaration = node.body.statements.at(gensymCount); -// if (!!declaration && mirrorArkts.isVariableDeclaration(declaration) && declaration.declarators.length > 0) { -// const declarator = declaration.declarators[0]; -// collectGensymDeclarator(declarator, memoableInfo); -// if (!memoableInfo.hasMemoSkip && shouldCollectParameter) { -// peers.push(declarator.name.peer); -// } -// gensymCount++; -// } -// } -// if (checkIsMemoFromMemoableInfo(memoableInfo)) { -// mirrorArkts.NodeCache.getInstance().collect(_param, { hasMemoSkip: memoableInfo.hasMemoSkip }); -// } -// if (!memoableInfo.hasMemoSkip && shouldCollectParameter) { -// peers.push(_param.identifier.peer); -// } -// return { peers, memoableInfo, gensymCount }; -// } +function collectMemoableInfoInFunctionParam( + node: mirrorArkts.ScriptFunction, + param: mirrorArkts.Expression, + gensymCount: number, + shouldCollectParameter: boolean = true +): FunctionParamCollectInfo { + const peers: mirrorArkts.AstNode['peer'][] = []; + let memoableInfo: MemoableInfo; + const _param = param as mirrorArkts.ETSParameterExpression; + if (mirrorArkts.AstNodeCache.getInstance().has(_param)) { + const metadata = mirrorArkts.AstNodeCache.getInstance().get(_param)!.metadata ?? {}; + const { hasMemoSkip } = metadata; + memoableInfo = { hasMemo: true, hasMemoSkip, hasProperType: true }; + } else { + memoableInfo = collectMemoableInfoInParameter(_param); + } + if (_param.ident?.name.startsWith(GenSymPrefix.INTRINSIC) && !!node.body && mirrorArkts.isBlockStatement(node.body)) { + const declaration = node.body.statements.at(gensymCount); + if (!!declaration && mirrorArkts.isVariableDeclaration(declaration) && declaration.declarators.length > 0) { + const declarator = declaration.declarators[0]; + collectGensymDeclarator(declarator, memoableInfo); + if (!memoableInfo.hasMemoSkip && shouldCollectParameter) { + peers.push(declarator.id!.peer); + } + gensymCount++; + } + } + if (checkIsMemoFromMemoableInfo(memoableInfo)) { + mirrorArkts.AstNodeCache.getInstance().collect(_param, { hasMemoSkip: memoableInfo.hasMemoSkip }); + } + if (!memoableInfo.hasMemoSkip && shouldCollectParameter) { + peers.push(_param.ident!.peer); + } + return { peers, memoableInfo, gensymCount }; +} /** * Collect `@memo` annotated `mirrorArkts.TypeNode` node. And find whether it can be `@memo` annotated. @@ -516,311 +519,314 @@ export function findCanAddMemoFromTypeAnnotation( } const memoableInfo = collectMemoableInfoInType(typeAnnotation); if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { - mirrorArkts.NodeCache.getInstance().collect(typeAnnotation); + // mirrorArkts.AstNodeCache.getInstance().collect(typeAnnotation); } return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; } -// /** -// * Collect `@memo` annotated `mirrorArkts.Property` node. And find whether it can be `@memo` annotated. -// * -// * @param node `mirrorArkts.Property` node. -// * @returns true if it is not `@memo` annotated but can add `@memo` to it. -// */ -// export function findCanAddMemoFromProperty(property: mirrorArkts.AstNode): property is mirrorArkts.Property { -// const memoableInfo = collectMemoableInfoInProperty(property); -// if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { -// mirrorArkts.NodeCache.getInstance().collect(property); -// } -// return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; -// } +/** + * Collect `@memo` annotated `mirrorArkts.Property` node. And find whether it can be `@memo` annotated. + * + * @param node `mirrorArkts.Property` node. + * @returns true if it is not `@memo` annotated but can add `@memo` to it. + */ +export function findCanAddMemoFromProperty(property: mirrorArkts.AstNode): property is mirrorArkts.Property { + const memoableInfo = collectMemoableInfoInProperty(property); + if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { + mirrorArkts.AstNodeCache.getInstance().collect(property); + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +} -// /** -// * Collect `@memo` annotated `mirrorArkts.ClassProperty` node. And find whether it can be `@memo` annotated. -// * -// * @param node `mirrorArkts.ClassProperty` node. -// * @returns true if it is not `@memo` annotated but can add `@memo` to it. -// */ -// export function findCanAddMemoFromClassProperty(property: mirrorArkts.AstNode): property is mirrorArkts.ClassProperty { -// const memoableInfo = collectMemoableInfoInClassProperty(property); -// if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { -// mirrorArkts.NodeCache.getInstance().collect(property); -// } -// return ( -// (!!memoableInfo.hasBuilder || !!memoableInfo.hasBuilderParam) && -// !memoableInfo.hasMemo && -// !!memoableInfo.hasProperType -// ); -// } +/** + * Collect `@memo` annotated `mirrorArkts.ClassProperty` node. And find whether it can be `@memo` annotated. + * + * @param node `mirrorArkts.ClassProperty` node. + * @returns true if it is not `@memo` annotated but can add `@memo` to it. + */ +export function findCanAddMemoFromClassProperty(property: mirrorArkts.AstNode): property is mirrorArkts.ClassProperty { + const memoableInfo = collectMemoableInfoInClassProperty(property); + if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { + mirrorArkts.AstNodeCache.getInstance().collect(property); + } + return ( + (!!memoableInfo.hasBuilder || !!memoableInfo.hasBuilderParam) && + !memoableInfo.hasMemo && + !!memoableInfo.hasProperType + ); +} -// /** -// * Collect `@memo` annotated `mirrorArkts.ETSParameterExpression` node. And find whether it can be `@memo` annotated. -// * -// * @param node `mirrorArkts.ETSParameterExpression` node. -// * @returns true if it is not `@memo` annotated but can add `@memo` to it. -// */ -// export function findCanAddMemoFromParameter(param: mirrorArkts.AstNode | undefined): param is mirrorArkts.ETSParameterExpression { -// if (!param) { -// return false; -// } -// const memoableInfo = collectMemoableInfoInParameter(param); -// if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { -// mirrorArkts.NodeCache.getInstance().collect(param, { hasMemoSkip: memoableInfo.hasMemoSkip }); -// } -// return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; -// } +/** + * Collect `@memo` annotated `mirrorArkts.ETSParameterExpression` node. And find whether it can be `@memo` annotated. + * + * @param node `mirrorArkts.ETSParameterExpression` node. + * @returns true if it is not `@memo` annotated but can add `@memo` to it. + */ +export function findCanAddMemoFromParameter(param: mirrorArkts.AstNode | undefined): param is mirrorArkts.ETSParameterExpression { + if (!param) { + return false; + } + const memoableInfo = collectMemoableInfoInParameter(param); + if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { + mirrorArkts.AstNodeCache.getInstance().collect(param, { hasMemoSkip: memoableInfo.hasMemoSkip }); + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +} -// /** -// * Collect `@memo` annotated `mirrorArkts.ArrowFunctionExpression` node. And find whether it can be `@memo` annotated. -// * -// * @param node `mirrorArkts.ArrowFunctionExpression` node. -// * @returns true if it is not `@memo` annotated but can add `@memo` to it. -// */ -// export function findCanAddMemoFromArrowFunction(node: mirrorArkts.AstNode): node is mirrorArkts.ArrowFunctionExpression { -// if (!mirrorArkts.isArrowFunctionExpression(node)) { -// return false; -// } -// const memoableInfo = collectMemoableInfoInArrowFunction(node); -// const { hasMemoEntry, hasMemoIntrinsic } = memoableInfo; -// const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(node.scriptFunction); -// const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams( -// node.scriptFunction, -// !hasMemoEntry && !hasMemoIntrinsic -// ); -// const isMemoReturnType = checkIsMemoFromMemoableInfo(returnMemoableInfo); -// if (isMemoReturnType) { -// mirrorArkts.NodeCache.getInstance().collect(node.scriptFunction.returnTypeAnnotation!); -// } -// const isMemo = checkIsMemoFromMemoableInfo(memoableInfo); -// if (isMemo && !mirrorArkts.NodeCache.getInstance().has(node)) { -// mirrorArkts.NodeCache.getInstance().collect(node, { hasMemoEntry, hasMemoIntrinsic }); -// if (!!node.scriptFunction.body && mirrorArkts.isBlockStatement(node.scriptFunction.body)) { -// const disableCollectReturn = hasMemoEntry || hasMemoIntrinsic; -// collectMemoScriptFunctionBody( -// node.scriptFunction.body, -// returnMemoableInfo, -// paramMemoableInfoMap, -// gensymCount, -// disableCollectReturn -// ); -// } -// } -// return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; -// } +/** + * Collect `@memo` annotated `mirrorArkts.ArrowFunctionExpression` node. And find whether it can be `@memo` annotated. + * + * @param node `mirrorArkts.ArrowFunctionExpression` node. + * @returns true if it is not `@memo` annotated but can add `@memo` to it. + */ +export function findCanAddMemoFromArrowFunction(node: mirrorArkts.AstNode): node is mirrorArkts.ArrowFunctionExpression { + if (!mirrorArkts.isArrowFunctionExpression(node) || !node.function) { + return false; + } + const memoableInfo = collectMemoableInfoInArrowFunction(node); + const { hasMemoEntry, hasMemoIntrinsic } = memoableInfo; + const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(node.function); + const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams( + node.function, + !hasMemoEntry && !hasMemoIntrinsic + ); + const isMemoReturnType = checkIsMemoFromMemoableInfo(returnMemoableInfo); + if (isMemoReturnType) { + mirrorArkts.AstNodeCache.getInstance().collect(node.function.returnTypeAnnotation!); + } + const isMemo = checkIsMemoFromMemoableInfo(memoableInfo); + if (isMemo && !mirrorArkts.AstNodeCache.getInstance().has(node)) { + mirrorArkts.AstNodeCache.getInstance().collect(node, { hasMemoEntry, hasMemoIntrinsic }); + if (!!node.function.body && mirrorArkts.isBlockStatement(node.function.body)) { + const disableCollectReturn = hasMemoEntry || hasMemoIntrinsic; + collectMemoScriptFunctionBody( + node.function.body, + returnMemoableInfo, + paramMemoableInfoMap, + gensymCount, + disableCollectReturn + ); + } + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +} -// /** -// * Collect `@memo` annotated `mirrorArkts.TSTypeAliasDeclaration` node. And find whether it can be `@memo` annotated. -// * -// * @param node `mirrorArkts.TSTypeAliasDeclaration` node. -// * @returns true if it is not `@memo` annotated but can add `@memo` to it. -// */ -// export function findCanAddMemoFromTypeAlias(node: mirrorArkts.AstNode): node is mirrorArkts.TSTypeAliasDeclaration { -// const memoableInfo = collectMemoableInfoInTypeAlias(node); -// if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { -// mirrorArkts.NodeCache.getInstance().collect(node); -// } -// return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; -// } +/** + * Collect `@memo` annotated `mirrorArkts.TSTypeAliasDeclaration` node. And find whether it can be `@memo` annotated. + * + * @param node `mirrorArkts.TSTypeAliasDeclaration` node. + * @returns true if it is not `@memo` annotated but can add `@memo` to it. + */ +export function findCanAddMemoFromTypeAlias(node: mirrorArkts.AstNode): node is mirrorArkts.TSTypeAliasDeclaration { + const memoableInfo = collectMemoableInfoInTypeAlias(node); + if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { + mirrorArkts.AstNodeCache.getInstance().collect(node); + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +} -// /** -// * Collect `@memo` annotated `mirrorArkts.MethodDefinition` node. And find whether it can be `@memo` annotated. -// * -// * @param node `mirrorArkts.MethodDefinition` node. -// * @returns true if it is not `@memo` annotated but can add `@memo` to it. -// */ -// export function findCanAddMemoFromMethod(node: mirrorArkts.AstNode): node is mirrorArkts.MethodDefinition { -// if (!mirrorArkts.isMethodDefinition(node)) { -// return false; -// } -// const memoableInfo = collectMemoableInfoInMethod(node); -// const { hasMemoEntry, hasMemoIntrinsic } = memoableInfo; -// const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(node.scriptFunction); -// const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams( -// node.scriptFunction, -// !hasMemoEntry && !hasMemoIntrinsic -// ); -// const isMemo = checkIsMemoFromMemoableInfo(memoableInfo); -// const isMemoReturnType = checkIsMemoFromMemoableInfo(returnMemoableInfo); -// if (isMemoReturnType) { -// mirrorArkts.NodeCache.getInstance().collect(node.scriptFunction.returnTypeAnnotation!); -// } -// if (isMemo && !mirrorArkts.NodeCache.getInstance().has(node)) { -// const metadata = collectMetadataInMethod(node); -// mirrorArkts.NodeCache.getInstance().collect(node, { -// ...metadata, -// hasMemoEntry, -// hasMemoIntrinsic, -// }); -// if (!!node.scriptFunction.body && mirrorArkts.isBlockStatement(node.scriptFunction.body)) { -// const disableCollectReturn = hasMemoEntry || hasMemoIntrinsic; -// collectMemoScriptFunctionBody( -// node.scriptFunction.body, -// returnMemoableInfo, -// paramMemoableInfoMap, -// gensymCount, -// disableCollectReturn -// ); -// } -// } -// return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; -// } +/** + * Collect `@memo` annotated `mirrorArkts.MethodDefinition` node. And find whether it can be `@memo` annotated. + * + * @param node `mirrorArkts.MethodDefinition` node. + * @returns true if it is not `@memo` annotated but can add `@memo` to it. + */ +export function findCanAddMemoFromMethod(node: mirrorArkts.AstNode): node is mirrorArkts.MethodDefinition { + if (!mirrorArkts.isMethodDefinition(node) || !node.function) { + return false; + } + const memoableInfo = collectMemoableInfoInMethod(node); + const { hasMemoEntry, hasMemoIntrinsic } = memoableInfo; + const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(node.function); + const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams( + node.function, + !hasMemoEntry && !hasMemoIntrinsic + ); + const isMemo = checkIsMemoFromMemoableInfo(memoableInfo); + const isMemoReturnType = checkIsMemoFromMemoableInfo(returnMemoableInfo); + if (isMemoReturnType) { + mirrorArkts.AstNodeCache.getInstance().collect(node.function.returnTypeAnnotation!); + } + if (isMemo && !mirrorArkts.AstNodeCache.getInstance().has(node)) { + const metadata = collectMetadataInMethod(node); + mirrorArkts.AstNodeCache.getInstance().collect(node, { + ...metadata, + hasMemoEntry, + hasMemoIntrinsic, + }); + if (!!node.function.body && mirrorArkts.isBlockStatement(node.function.body)) { + const disableCollectReturn = hasMemoEntry || hasMemoIntrinsic; + collectMemoScriptFunctionBody( + node.function.body, + returnMemoableInfo, + paramMemoableInfoMap, + gensymCount, + disableCollectReturn + ); + } + } + return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; +} -// /** -// * Collect `@memo` annotated `mirrorArkts.CallExpression` node from corresponding declared method, -// * as well as collect each `@memo` annotated argument from corresponding declared method parameter. -// * -// * @param node `mirrorArkts.CallExpression` node. -// */ -// export function collectMemoFromCallExpression(node: mirrorArkts.CallExpression): void { -// if (mirrorArkts.NodeCache.getInstance().has(node)) { -// return; -// } -// const expr = findIdentifierFromCallee(node.expression); -// const decl = (expr && getDeclResolveAlias(expr)) ?? node.expression; -// if (!decl) { -// return; -// } -// let isCollected: boolean = false; -// if (mirrorArkts.NodeCache.getInstance().has(decl)) { -// mirrorArkts.NodeCache.getInstance().collect(node); -// isCollected = true; -// } else if (mirrorArkts.isMethodDefinition(decl)) { -// isCollected = collectCallWithDeclaredMethod(node, decl); -// } else if (mirrorArkts.isClassProperty(decl)) { -// isCollected = collectCallWithDeclaredClassProperty(node, decl); -// } -// if (isCollected && mirrorArkts.isTSAsExpression(node.expression) && node.expression.typeAnnotation) { -// mirrorArkts.NodeCache.getInstance().collect(node.expression.typeAnnotation); -// } -// } +/** + * Collect `@memo` annotated `mirrorArkts.CallExpression` node from corresponding declared method, + * as well as collect each `@memo` annotated argument from corresponding declared method parameter. + * + * @param node `mirrorArkts.CallExpression` node. + */ +export function collectMemoFromCallExpression(node: mirrorArkts.CallExpression): void { + if (mirrorArkts.AstNodeCache.getInstance().has(node)) { + return; + } + const expr = findIdentifierFromCallee(node.callee); + const decl = (expr && getDeclResolveAlias(expr)) ?? node.callee; + if (!decl) { + return; + } + let isCollected: boolean = false; + if (mirrorArkts.AstNodeCache.getInstance().has(decl)) { + mirrorArkts.AstNodeCache.getInstance().collect(node); + isCollected = true; + } else if (mirrorArkts.isMethodDefinition(decl)) { + isCollected = collectCallWithDeclaredMethod(node, decl); + } else if (mirrorArkts.isClassProperty(decl)) { + isCollected = collectCallWithDeclaredClassProperty(node, decl); + } + if (isCollected && mirrorArkts.isTSAsExpression(node.callee) && node.callee.typeAnnotation) { + mirrorArkts.AstNodeCache.getInstance().collect(node.callee.typeAnnotation); + } +} -// export function collectCallWithDeclaredClassProperty(node: mirrorArkts.CallExpression, decl: mirrorArkts.ClassProperty): boolean { -// if (mirrorArkts.NodeCache.getInstance().has(decl)) { -// mirrorArkts.NodeCache.getInstance().collect(node); -// return true; -// } -// const memoableInfo = collectMemoableInfoInClassProperty(decl); -// if (checkIsMemoFromMemoableInfo(memoableInfo, false) || memoableInfo.hasBuilder || memoableInfo.hasBuilderParam) { -// mirrorArkts.NodeCache.getInstance().collect(node); -// return true; -// } -// return false; -// } +export function collectCallWithDeclaredClassProperty(node: mirrorArkts.CallExpression, decl: mirrorArkts.ClassProperty): boolean { + if (mirrorArkts.AstNodeCache.getInstance().has(decl)) { + mirrorArkts.AstNodeCache.getInstance().collect(node); + return true; + } + const memoableInfo = collectMemoableInfoInClassProperty(decl); + if (checkIsMemoFromMemoableInfo(memoableInfo, false) || memoableInfo.hasBuilder || memoableInfo.hasBuilderParam) { + mirrorArkts.AstNodeCache.getInstance().collect(node); + return true; + } + return false; +} -// export function collectCallWithDeclaredMethod(node: mirrorArkts.CallExpression, decl: mirrorArkts.MethodDefinition): boolean { -// const hasReceiver = decl.scriptFunction.hasReceiver; -// const params = decl.scriptFunction.params; -// const args = node.arguments; -// const hasRestParameter = decl.scriptFunction.hasRestParameter; -// const isTrailingCall = node.isTrailingCall; -// const options = { hasRestParameter, isTrailingCall }; -// forEachArgWithParam(args, params, collectCallArgsWithMethodParams, options); -// if (mirrorArkts.NodeCache.getInstance().has(decl)) { -// const { hasMemoEntry, hasMemoIntrinsic } = mirrorArkts.NodeCache.getInstance().get(decl)!.metadata ?? {}; -// mirrorArkts.NodeCache.getInstance().collect(node, { hasReceiver, hasMemoEntry, hasMemoIntrinsic }); -// return true; -// } else { -// const memoableInfo = collectMemoableInfoInScriptFunction(decl.scriptFunction); -// if (checkIsMemoFromMemoableInfo(memoableInfo, true)) { -// const { hasMemoEntry, hasMemoIntrinsic } = memoableInfo; -// mirrorArkts.NodeCache.getInstance().collect(node, { hasReceiver, hasMemoEntry, hasMemoIntrinsic }); -// return true; -// } -// } -// return false; -// } +export function collectCallWithDeclaredMethod(node: mirrorArkts.CallExpression, decl: mirrorArkts.MethodDefinition): boolean { + if (!decl.function) { + return false; + } + const hasReceiver = decl.function.hasReceiver; + const params = decl.function.params; + const args = node.arguments; + const hasRestParameter = decl.function.hasRestParameter; + const isTrailingCall = node.isTrailingCall; + const options = { hasRestParameter, isTrailingCall }; + forEachArgWithParam(args, params, collectCallArgsWithMethodParams, options); + if (mirrorArkts.AstNodeCache.getInstance().has(decl)) { + const { hasMemoEntry, hasMemoIntrinsic } = mirrorArkts.AstNodeCache.getInstance().get(decl)!.metadata ?? {}; + mirrorArkts.AstNodeCache.getInstance().collect(node, { hasReceiver, hasMemoEntry, hasMemoIntrinsic }); + return true; + } else { + const memoableInfo = collectMemoableInfoInScriptFunction(decl.function); + if (checkIsMemoFromMemoableInfo(memoableInfo, true)) { + const { hasMemoEntry, hasMemoIntrinsic } = memoableInfo; + mirrorArkts.AstNodeCache.getInstance().collect(node, { hasReceiver, hasMemoEntry, hasMemoIntrinsic }); + return true; + } + } + return false; +} -// export function collectCallArgsWithMethodParams(arg: mirrorArkts.Expression | undefined, param: mirrorArkts.Expression): void { -// if (!arg) { -// return; -// } -// let info: MemoableInfo; -// if (mirrorArkts.NodeCache.getInstance().has(param)) { -// info = { hasMemo: true, hasProperType: true }; -// } else { -// info = collectMemoableInfoInParameter(param); -// } -// if (checkIsMemoFromMemoableInfo(info) && mirrorArkts.isArrowFunctionExpression(arg)) { -// mirrorArkts.NodeCache.getInstance().collect(arg); -// const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(arg.scriptFunction); -// const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams(arg.scriptFunction); -// if (!!arg.scriptFunction.body && mirrorArkts.isBlockStatement(arg.scriptFunction.body)) { -// collectMemoScriptFunctionBody( -// arg.scriptFunction.body, -// returnMemoableInfo, -// paramMemoableInfoMap, -// gensymCount -// ); -// } -// } -// } +export function collectCallArgsWithMethodParams(arg: mirrorArkts.Expression | undefined, param: mirrorArkts.Expression): void { + if (!arg) { + return; + } + let info: MemoableInfo; + if (mirrorArkts.AstNodeCache.getInstance().has(param)) { + info = { hasMemo: true, hasProperType: true }; + } else { + info = collectMemoableInfoInParameter(param); + } + if (checkIsMemoFromMemoableInfo(info) && mirrorArkts.isArrowFunctionExpression(arg) && !!arg.function) { + mirrorArkts.AstNodeCache.getInstance().collect(arg); + const returnMemoableInfo = collectMemoableInfoInFunctionReturnType(arg.function); + const [paramMemoableInfoMap, gensymCount] = collectMemoableInfoMapInFunctionParams(arg.function); + if (!!arg.function.body && mirrorArkts.isBlockStatement(arg.function.body)) { + collectMemoScriptFunctionBody( + arg.function.body, + returnMemoableInfo, + paramMemoableInfoMap, + gensymCount + ); + } + } +} -// export function findIdentifierFromCallee(callee: mirrorArkts.AstNode | undefined): mirrorArkts.Identifier | undefined { -// if (!callee) { -// return undefined; -// } -// if (mirrorArkts.isIdentifier(callee)) { -// return callee; -// } -// if (mirrorArkts.isMemberExpression(callee)) { -// return findIdentifierFromCallee(callee.property); -// } -// if (mirrorArkts.isTSAsExpression(callee)) { -// return findIdentifierFromCallee(callee.expr); -// } -// if (mirrorArkts.isTSNonNullExpression(callee)) { -// return findIdentifierFromCallee(callee.expr); -// } -// return undefined; -// } +export function findIdentifierFromCallee(callee: mirrorArkts.AstNode | undefined): mirrorArkts.Identifier | undefined { + if (!callee) { + return undefined; + } + if (mirrorArkts.isIdentifier(callee)) { + return callee; + } + if (mirrorArkts.isMemberExpression(callee)) { + return findIdentifierFromCallee(callee.property); + } + if (mirrorArkts.isTSAsExpression(callee)) { + return findIdentifierFromCallee(callee.expr); + } + if (mirrorArkts.isTSNonNullExpression(callee)) { + return findIdentifierFromCallee(callee.expr); + } + return undefined; +} -// export function collectMemoScriptFunctionBody( -// body: mirrorArkts.BlockStatement, -// returnMemoableInfo: MemoableInfo, -// paramMemoableInfoMap: Map, -// gensymCount: number, -// disableCollectReturn?: boolean -// ): void { -// const collector = new MemoFunctionCollector(); -// body.statements.forEach((st, index) => { -// if (index < gensymCount) { -// return; -// } -// if (disableCollectReturn) { -// collector.disableCollectReturn(); -// } -// collector.registerReturnInfo(returnMemoableInfo).registerParamInfoMap(paramMemoableInfoMap).visitor(st); -// collector.reset(); -// }); -// } +export function collectMemoScriptFunctionBody( + body: mirrorArkts.BlockStatement, + returnMemoableInfo: MemoableInfo, + paramMemoableInfoMap: Map, + gensymCount: number, + disableCollectReturn?: boolean +): void { + const collector = new MemoFunctionCollector(); + body.statements.forEach((st, index) => { + if (index < gensymCount) { + return; + } + if (disableCollectReturn) { + collector.disableCollectReturn(); + } + collector.registerReturnInfo(returnMemoableInfo).registerParamInfoMap(paramMemoableInfoMap).visitor(st); + collector.reset(); + }); +} -// export function collectMetadataInMethod(node: mirrorArkts.MethodDefinition): mirrorArkts.AstNodeCacheValue['metadata'] { -// const callName = node.name.name; -// const hasReceiver = node.scriptFunction.hasReceiver; -// const isSetter = node.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; -// const isGetter = node.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; -// return { callName, hasReceiver, isSetter, isGetter }; -// } +export function collectMetadataInMethod(node: mirrorArkts.MethodDefinition): mirrorArkts.AstNodeCacheValue['metadata'] { + const callName = node.id!.name; + const hasReceiver = node.function!.hasReceiver; + const isSetter = node.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; + const isGetter = node.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; + return { callName, hasReceiver, isSetter, isGetter }; +} -// export function checkIsMemoFromMemoableInfo(info: MemoableInfo, ignoreType: boolean = false): boolean { -// return ( -// (!!info.hasMemo || !!info.hasMemoIntrinsic || !!info.hasMemoEntry || !!info.hasBuilder) && -// (ignoreType || !!info.hasProperType) -// ); -// } +export function checkIsMemoFromMemoableInfo(info: MemoableInfo, ignoreType: boolean = false): boolean { + return ( + (!!info.hasMemo || !!info.hasMemoIntrinsic || !!info.hasMemoEntry || !!info.hasBuilder) && + (ignoreType || !!info.hasProperType) + ); +} -// export function getDeclResolveAlias(node: mirrorArkts.AstNode): mirrorArkts.AstNode | undefined { -// const decl = mirrorArkts.getDecl(node); -// if (!!decl && !!decl.parent && mirrorArkts.isIdentifier(decl) && mirrorArkts.isVariableDeclarator(decl.parent)) { -// if (!!decl.parent.initializer && mirrorArkts.isIdentifier(decl.parent.initializer)) { -// return getDeclResolveAlias(decl.parent.initializer); -// } -// if (!!decl.parent.initializer && mirrorArkts.isMemberExpression(decl.parent.initializer)) { -// return getDeclResolveAlias(decl.parent.initializer.property); -// } -// } -// return decl; -// } +export function getDeclResolveAlias(node: mirrorArkts.AstNode): mirrorArkts.AstNode | undefined { + const decl = mirrorArkts.getDecl(node); + if (!!decl && !!decl.parent && mirrorArkts.isIdentifier(decl) && mirrorArkts.isVariableDeclarator(decl.parent)) { + if (!!decl.parent.init && mirrorArkts.isIdentifier(decl.parent.init)) { + return getDeclResolveAlias(decl.parent.init); + } + if (!!decl.parent.init && mirrorArkts.isMemberExpression(decl.parent.init)) { + return getDeclResolveAlias(decl.parent.init.property!); + } + } + return decl; +} // export function parametersBlockHasReceiver(params: readonly mirrorArkts.Expression[]): boolean { // return params.length > 0 && mirrorArkts.isEtsParameterExpression(params[0]) && isThisParam(params[0]); diff --git a/arkui-plugins/mirror-replace/common/arkts-utils.ts b/arkui-plugins/mirror-replace/common/arkts-utils.ts index d27b87a3a..7ab79c711 100644 --- a/arkui-plugins/mirror-replace/common/arkts-utils.ts +++ b/arkui-plugins/mirror-replace/common/arkts-utils.ts @@ -124,4 +124,47 @@ export function mangle(value: string): string { export function backingField(originalName: string): string { return mangle(`backing_${originalName}`); +} + +export function removeAnnotationByName( + annotations: readonly mirrorArkts.AnnotationUsage[], + annoName: string +): mirrorArkts.AnnotationUsage[] { + return annotations.filter((it) => !isAnnotation(it, annoName)); +} + +/** + * Performs the specified action for each argument in a `mirrorArkts.CallExpression`'s arguments array + * paired with corresponding parameter from the function declaration node. + * + * @param args An arguments array from a `mirrorArkts.CallExpression` node. + * @param params A parameters array from a function declaration node. + * @param callbackFn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array. + * @param options Additional options field that accepts special conditions of calls and function, used for pairing arguments with parameters. + */ +export function forEachArgWithParam( + args: readonly mirrorArkts.Expression[], + params: readonly mirrorArkts.Expression[], + callbackFn: (arg: mirrorArkts.Expression | undefined, param: mirrorArkts.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; + } + 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); + callbackFn(argument, param, index); + index++; + } + const lastParam = params.at(paramLen - 1)!; + const lastIndex = isTrailingCall ? argLen - 1 : maxLen - 1; + const lastArg = args.at(lastIndex); + callbackFn(lastArg, lastParam, maxLen - 1); } \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/common/declaration-collector.ts b/arkui-plugins/mirror-replace/common/declaration-collector.ts index 4e858a19c..cc3faad2f 100644 --- a/arkui-plugins/mirror-replace/common/declaration-collector.ts +++ b/arkui-plugins/mirror-replace/common/declaration-collector.ts @@ -14,7 +14,7 @@ */ import * as mirrorArkts from '@koalaui/libarkts-mirror'; import { IMPORT_SOURCE_MAP_V2, INTERMEDIATE_IMPORT_SOURCE } from './predefines'; -// import { ImportCollector } from './import-collector'; +import { ImportCollector } from './import-collector'; export class DeclarationCollector { private fromExternalSourceNameMap: Map; @@ -40,35 +40,35 @@ export class DeclarationCollector { } else { sourceName = declSourceName; } - // ImportCollector.getInstance().collectSource(symbol, sourceName); + ImportCollector.getInstance().collectSource(symbol, sourceName); } collect(decl: mirrorArkts.AstNode | undefined): void { - // if (!decl) { - // return; - // } - // let declName: string | undefined; - // if (mirrorArkts.isAnnotationDeclaration(decl) && !!decl.expr && mirrorArkts.isIdentifier(decl.expr)) { - // declName = decl.expr.name; - // } else if (mirrorArkts.isMethodDefinition(decl)) { - // declName = decl.name.name; - // } else if (mirrorArkts.isIdentifier(decl)) { - // declName = decl.name; - // } else if (mirrorArkts.isClassProperty(decl) && !!decl.key && mirrorArkts.isIdentifier(decl.key)) { - // declName = decl.key.name; - // } else if (mirrorArkts.isEtsParameterExpression(decl)) { - // declName = decl.identifier.name; - // } - // if (!declName) { - // return; - // } - // let sourceName: string = mirrorArkts.getProgramFromAstNode(decl).moduleName; - // this.fromExternalSourceNameMap.set(declName, sourceName); - // this.fromExternalSourceNodePeerMap.set(decl.peer, sourceName); + if (!decl) { + return; + } + let declName: string | undefined; + if (mirrorArkts.isAnnotationDeclaration(decl) && !!decl.expr && mirrorArkts.isIdentifier(decl.expr)) { + declName = decl.expr.name; + } else if (mirrorArkts.isMethodDefinition(decl)) { + declName = decl.id?.name; + } else if (mirrorArkts.isIdentifier(decl)) { + declName = decl.name; + } else if (mirrorArkts.isClassProperty(decl) && !!decl.key && mirrorArkts.isIdentifier(decl.key)) { + declName = decl.key.name; + } else if (mirrorArkts.isETSParameterExpression(decl)) { + declName = decl.ident?.name; + } + if (!declName) { + return; + } + let sourceName: string = mirrorArkts.getProgramFromAstNode(decl).moduleName; + this.fromExternalSourceNameMap.set(declName, sourceName); + this.fromExternalSourceNodePeerMap.set(decl.peer, sourceName); - // INTERMEDIATE_IMPORT_SOURCE.get(declName)?.forEach((symbol) => { - // this.collectIntermediateImportSource(symbol, sourceName); - // }); + INTERMEDIATE_IMPORT_SOURCE.get(declName)?.forEach((symbol) => { + this.collectIntermediateImportSource(symbol, sourceName); + }); } findExternalSourceFromName(declName: string): string | undefined { diff --git a/arkui-plugins/mirror-replace/common/gensym-generator.ts b/arkui-plugins/mirror-replace/common/gensym-generator.ts new file mode 100644 index 000000000..6e05f109d --- /dev/null +++ b/arkui-plugins/mirror-replace/common/gensym-generator.ts @@ -0,0 +1,60 @@ +/* + * 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 { getCommonPath } from '../path'; +const common = require(getCommonPath()); +const UniqueId = common.UniqueId; + +export class GenSymGenerator { + // Global for the whole program. + private static callCount: number = 0; + static instance: GenSymGenerator; + + // Set `stable` to true if you want to have more predictable values. + // For example for tests. + // Don't use it in production! + private constructor(public stableForTests: boolean = false) { + if (stableForTests) GenSymGenerator.callCount = 0; + } + + static getInstance(stableForTests: boolean = false): GenSymGenerator { + if (!this.instance) { + this.instance = new GenSymGenerator(stableForTests); + } + + return this.instance; + } + + sha1Id(callName: string): string { + const uniqId = new UniqueId(); + uniqId.addString('gensym uniqid'); + uniqId.addString(callName); + uniqId.addI32(GenSymGenerator.callCount++); + return uniqId.compute().substring(0, 7); + } + + stringId(callName: string): string { + return `${GenSymGenerator.callCount++}_${callName}_id`; + } + + id(callName: string = ''): string { + const positionId = this.stableForTests ? this.stringId(callName) : this.sha1Id(callName); + + const coreceToStr = parseInt(positionId, 16).toString(); + + // compiler use gensym%%_ but % is illegal before after-check phase + return `gensym___${coreceToStr}`; + } +} diff --git a/arkui-plugins/mirror-replace/common/log-collector.ts b/arkui-plugins/mirror-replace/common/log-collector.ts new file mode 100644 index 000000000..99d4e54b5 --- /dev/null +++ b/arkui-plugins/mirror-replace/common/log-collector.ts @@ -0,0 +1,75 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { LogType } from './predefines'; + +interface LogInfo { + type: LogType; + message: string; + node: mirrorArkts.AstNode; + code: string; +} + +// export function generateDiagnosticKind(logItem: LogInfo): mirrorArkts.DiagnosticKind { +// return mirrorArkts.DiagnosticKind.create( +// `${logItem.code}: ${logItem.message}`, +// logItem.type === LogType.ERROR +// ? mirrorArkts.PluginDiagnosticType.ES2PANDA_PLUGIN_ERROR +// : mirrorArkts.PluginDiagnosticType.ES2PANDA_PLUGIN_WARNING +// ); +// } + +export class LogCollector { + public logInfos: LogInfo[]; + private static instance: LogCollector; + private ignoreError: boolean; + + private constructor() { + this.logInfos = []; + this.ignoreError = false; + } + + static getInstance(): LogCollector { + if (!this.instance) { + this.instance = new LogCollector(); + } + return this.instance; + } + + reset(): void { + this.logInfos = []; + this.ignoreError = false; + } + + collectLogInfo(logItem: LogInfo): void { + this.logInfos.push(logItem); + } + + emitLogInfo(): void { + // if (this.ignoreError) { + // return; + // } + // this.logInfos.forEach((logItem: LogInfo) => { + // mirrorArkts.Diagnostic.logDiagnostic(generateDiagnosticKind(logItem), mirrorArkts.getStartPosition(logItem.node)); + // }); + } + + shouldIgnoreError(ignoreError: boolean | undefined): void { + if (!!ignoreError) { + this.ignoreError = true; + } + } +} diff --git a/arkui-plugins/mirror-replace/common/predefines.ts b/arkui-plugins/mirror-replace/common/predefines.ts index d1416d9d5..87bdd9413 100644 --- a/arkui-plugins/mirror-replace/common/predefines.ts +++ b/arkui-plugins/mirror-replace/common/predefines.ts @@ -24,16 +24,22 @@ export const EXTERNAL_SOURCE_PREFIX_NAMES: (string | RegExp)[] = [ /@arkts\..*/, /@ohos\.(?!arkui).*/, /@system\..*/, - /arkui\.(?!Ark|[Uu]serView$)[A-Z]/, // temporary solution /ability\..*/, ]; +export const EXTERNAL_SOURCE_PREFIX_NAMES_FOR_FRAMEWORK: (string | RegExp)[] = [ + 'std', + 'escompat', + /@arkts\..*/ +]; + export const ARKUI_IMPORT_PREFIX_NAMES: (string | RegExp)[] = [/arkui\..*/, /@ohos\..*/, /@kit\..*/]; export const MEMO_IMPORT_SOURCE_NAME: string = 'arkui.stateManagement.runtime'; export const CUSTOM_COMPONENT_IMPORT_SOURCE_NAME: string = 'arkui.component.customComponent'; export const ENTRY_POINT_IMPORT_SOURCE_NAME: string = 'arkui.UserView'; export const ARKUI_COMPONENT_COMMON_SOURCE_NAME: string = 'arkui.component.common'; +export const ARKUI_FOREACH_SOURCE_NAME: string = 'arkui.component.forEach'; export enum ModuleType { HAR = 'har', @@ -76,6 +82,27 @@ export enum StructDecoratorNames { CUSTOMDIALOG = 'CustomDialog', } +export enum EntryWrapperNames { + ENTRY_FUNC = 'entry', + WRAPPER_CLASS_NAME = '__EntryWrapper', + ENTRY_STORAGE_LOCAL_STORAGE_PROPERTY_NAME = '_entry_local_storage_', + ENTRY_POINT_CLASS_NAME = 'EntryPoint', + REGISTER_NAMED_ROUTER = 'RegisterNamedRouter', + ROUTER_NAME = 'routerName', + INSTANCE = 'instance', + PARAM = 'param' +} + +export enum EntryParamNames { + ENTRY_STORAGE = 'storage', + ENTRY_USE_SHARED_STORAGE = 'useSharedStorage', + ENTRY_ROUTE_NAME = 'routeName' +} + +export enum InnerComponentNames { + FOR_EACH = 'ForEach', +} + export enum DecoratorNames { STATE = 'State', STORAGE_LINK = 'StorageLink', @@ -109,9 +136,9 @@ export enum StateManagementTypes { LINK_DECORATED = 'ILinkDecoratedVariable', LINK_SOURCE_TYPE = 'LinkSourceType', STORAGE_LINK_DECORATED = 'IStorageLinkDecoratedVariable', - STORAGE_PROP_DECORATED = 'IStoragePropDecoratedVariable', + STORAGE_PROP_REF_DECORATED = 'IStoragePropRefDecoratedVariable', + LOCAL_STORAGE_LINK_DECORATED = 'ILocalStorageLinkDecoratedVariable', PROP_DECORATED = 'IPropDecoratedVariable', - MUTABLE_STATE = 'MutableState', SYNCED_PROPERTY = 'SyncedProperty', PROVIDE_DECORATED = 'IProvideDecoratedVariable', CONSUME_DECORATED = 'IConsumeDecoratedVariable', @@ -130,8 +157,9 @@ export enum StateManagementTypes { MAKE_STATE = 'makeState', MAKE_LINK = 'makeLink', MAKE_PROP = 'makeProp', - MAKE_STORAGE_PROP = 'makeStorageProp', + MAKE_STORAGE_PROP_REF = 'makeStoragePropRef', MAKE_STORAGE_LINK = 'makeStorageLink', + MAKE_LOCAL_STORAGE_LINK = 'makeLocalStorageLink', MAKE_PROVIDE = 'makeProvide', MAKE_CONSUME = 'makeConsume', MAKE_OBJECT_LINK = 'makeObjectLink', @@ -147,6 +175,15 @@ export enum AnimationNames { ANIMATION_STOP = 'animationStop', } +export enum NavigationNames { + NAVINTERFACE = 'NavInterface', + BUNDLE_NAME = 'bundleName', + MODULE_NAME = 'moduleName', + PAGE_PATH = 'pagePath', + PAGE_FULL_PATH = 'pageFullPath', + INTEGRATED_HSP = 'integratedHsp', +} + export const RESOURCE_TYPE: Record = { color: 10001, float: 10002, @@ -167,9 +204,9 @@ export const DECORATOR_TYPE_MAP = new Map( [DecoratorNames.LINK, StateManagementTypes.LINK_SOURCE_TYPE], [DecoratorNames.PROP, StateManagementTypes.PROP_DECORATED], [DecoratorNames.STORAGE_LINK, StateManagementTypes.STORAGE_LINK_DECORATED], - [DecoratorNames.STORAGE_PROP, StateManagementTypes.STORAGE_PROP_DECORATED], + [DecoratorNames.STORAGE_PROP, StateManagementTypes.STORAGE_PROP_REF_DECORATED], [DecoratorNames.LOCAL_STORAGE_PROP, StateManagementTypes.SYNCED_PROPERTY], - [DecoratorNames.LOCAL_STORAGE_LINK, StateManagementTypes.MUTABLE_STATE], + [DecoratorNames.LOCAL_STORAGE_LINK, StateManagementTypes.LOCAL_STORAGE_LINK_DECORATED], [DecoratorNames.OBJECT_LINK, StateManagementTypes.OBJECT_LINK_DECORATED], [DecoratorNames.PROVIDE, StateManagementTypes.PROVIDE_DECORATED], [DecoratorNames.CONSUME, StateManagementTypes.CONSUME_DECORATED], @@ -184,17 +221,10 @@ export const INTERMEDIATE_IMPORT_SOURCE: Map = new Map = new Map = new Map([ [Dollars.TRANSFORM_DOLLAR_RESOURCE, 'arkui.component.resources'], [Dollars.TRANSFORM_DOLLAR_RAWFILE, 'arkui.component.resources'], - [StateManagementTypes.MUTABLE_STATE, 'arkui.stateManagement.runtime'], [StateManagementTypes.SYNCED_PROPERTY, 'arkui.stateManagement.runtime'], [StateManagementTypes.STORAGE_LINK_STATE, 'arkui.stateManagement.runtime'], [StateManagementTypes.OBSERVABLE_PROXY, 'arkui.stateManagement.runtime'], @@ -250,28 +279,7 @@ export enum GetSetTypes { SET = 'set', } -export enum NavigationNames { - NAVINTERFACE = 'NavInterface', - BUNDLE_NAME = 'bundleName', - MODULE_NAME = 'moduleName', - PAGE_PATH = 'pagePath', - PAGE_FULL_PATH = 'pageFullPath', - INTEGRATED_HSP = 'integratedHsp', -} - -export enum EntryWrapperNames { - ENTRY_FUNC = 'entry', - WRAPPER_CLASS_NAME = '__EntryWrapper', - ENTRY_STORAGE_LOCAL_STORAGE_PROPERTY_NAME = '_entry_local_storage_', - ENTRY_POINT_CLASS_NAME = 'EntryPoint', - REGISTER_NAMED_ROUTER = 'RegisterNamedRouter', - ROUTER_NAME = 'routerName', - INSTANCE = 'instance', - PARAM = 'param' -} - -export enum EntryParamNames { - ENTRY_STORAGE = 'storage', - ENTRY_USE_SHARED_STORAGE = 'useSharedStorage', - ENTRY_ROUTE_NAME = 'routeName' +export enum GenSymPrefix { + INTRINSIC = 'gensym%%', + UI = 'gensym__' } \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/ui-plugins/builder-lambda-translators/factory.ts b/arkui-plugins/mirror-replace/ui-plugins/builder-lambda-translators/factory.ts new file mode 100644 index 000000000..7f4e818ab --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/builder-lambda-translators/factory.ts @@ -0,0 +1,707 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { BuilderLambdaNames } from '../utils'; +import { + backingField, + filterDefined, + isDecoratorAnnotation, + removeAnnotationByName, + forEachArgWithParam +} from '../../common/arkts-utils'; +import { + BuilderLambdaDeclInfo, + builderLambdaFunctionName, + builderLambdaMethodDeclType, + callIsGoodForBuilderLambda, + collectComponentAttributeImport, + findBuilderLambdaDecl, + findBuilderLambdaDeclInfo, + isBuilderLambda, + isBuilderLambdaFunctionCall, + isSafeType, + replaceBuilderLambdaDeclMethodName, + getDecalTypeFromValue, + hasBindableProperty, + isDoubleDollarCall, + InstanceCallInfo, + isStyleChainedCall, + isStyleWithReceiverCall, + builderLambdaType, + BuilderLambdaSecondLastArgInfo, + buildSecondLastArgInfo, + checkIsSpecialComponentAttributeFromType, +} from './utils'; +import { isDecoratorIntrinsicAnnotation } from '../property-translators/utils'; +import { factory as PropertyFactory } from '../property-translators/factory'; +import { AnimationNames, BindableDecl, DecoratorIntrinsicNames, DecoratorNames } from '../../common/predefines'; +import { ImportCollector } from '../../common/import-collector'; +import { devNull } from 'os'; +import { addMemoAnnotation, collectMemoableInfoInParameter } from '../../collectors/memo-collectors/utils'; + +export class factory { + /** + * update `@ComponentBuilder` decorated method. + */ + static updateBuilderLambdaMethodDecl( + node: mirrorArkts.MethodDefinition, + prefixArgs: mirrorArkts.ETSParameterExpression[], + newAnno: mirrorArkts.AnnotationUsage[], + newName: string | undefined, + externalSourceName?: string + ): mirrorArkts.MethodDefinition { + const func: mirrorArkts.ScriptFunction = node.function!; + let newParams: mirrorArkts.Expression[] = []; + if (func.params.length > 0) { + newParams.push(...prefixArgs, ...func.params); + } + let updateFunc = mirrorArkts.factory + .updateScriptFunction( + func, + func.body, + func.typeParams, + newParams, + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + func.flags, + func.modifierFlags, + undefined, + undefined + ) + .setAnnotations(newAnno); + + updateFunc = node.id!.name === BuilderLambdaNames.ORIGIN_METHOD_NAME ? addMemoAnnotation(updateFunc) : updateFunc; + + return mirrorArkts.factory.updateMethodDefinition( + node, + node.kind, + mirrorArkts.factory.updateIdentifier(node.id!, newName ?? node.id!.name), + mirrorArkts.factory.createFunctionExpression(updateFunc.id, updateFunc), + node.modifierFlags, + false + ); + } + + /* + * transform arguments in style node. + */ + static getTransformedStyle(call: mirrorArkts.CallExpression): mirrorArkts.Expression[] { + if (!call.callee) { + return [...call.arguments]; + } + const decl = mirrorArkts.getDecl(call.callee); + if (!decl || !mirrorArkts.isMethodDefinition(decl)) { + return [...call.arguments]; + } + const type: mirrorArkts.AstNode | undefined = mirrorArkts.isETSParameterExpression(decl.function?.params[0]) + ? decl.function.params[0].typeAnnotation?.clone() + : undefined; + if ( + type && + mirrorArkts.isTypeNode(type) && + hasBindableProperty(type, BindableDecl.BINDABLE) && + isDoubleDollarCall(call.arguments[0]) + ) { + const bindableArg: mirrorArkts.Expression = (call.arguments[0] as mirrorArkts.CallExpression).arguments[0]; + return [factory.updateBindableStyleArguments(bindableArg), ...call.arguments.slice(1)]; + } + return [...call.arguments]; + } + + /* + * transform bundable arguments in style node, e.g. `Radio().checked($$(this.checked))` => `Radio().checked({value: xxx, onChange: xxx})`. + */ + static updateBindableStyleArguments(bindableArg: mirrorArkts.Expression): mirrorArkts.Expression { + const valueType: mirrorArkts.TypeNode = getDecalTypeFromValue(bindableArg); + const objExp: mirrorArkts.ObjectExpression = mirrorArkts.factory.createObjectExpression( + mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, + [factory.generateValueProperty(bindableArg), factory.generateOnChangeArrowFunc(bindableArg, valueType)], + false + ); + return mirrorArkts.factory.createTSAsExpression(objExp, factory.createBindableType(valueType), false); + } + + /* + * create style instance call, e.g. `instance.margin(10)`. + */ + static createStyleLambdaBody(lambdaBody: mirrorArkts.AstNode, callInfo: InstanceCallInfo): mirrorArkts.CallExpression { + if (!callInfo.isReceiver) { + const newArgs: mirrorArkts.Expression[] = factory.getTransformedStyle(callInfo.call); + return mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + lambdaBody as mirrorArkts.Identifier, + callInfo.call.callee, + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + newArgs.map((arg) => { + if (mirrorArkts.isArrowFunctionExpression(arg)) { + return this.processArgArrowFunction(arg); + } + return arg; + }), + undefined, + ); + } else { + return mirrorArkts.factory.createCallExpression(callInfo.call.callee, [ + lambdaBody as mirrorArkts.Identifier, + ...callInfo.call.arguments.slice(1), + ], callInfo.call.typeParams); + } + } + + /* + * update parameter passing, e.g. `: __backing_`. + */ + static updateBackingMember(val: mirrorArkts.MemberExpression, originName: string): mirrorArkts.MemberExpression { + return mirrorArkts.factory.updateMemberExpression( + val, + val.object, + mirrorArkts.factory.createIdentifier(backingField(originName)), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); + } + + /* + * create style arguments in builder lambda. + */ + static createStyleArgInBuilderLambda( + lambdaBody: mirrorArkts.Expression | undefined, + typeNode: mirrorArkts.TypeNode | undefined, + moduleName: string, + typeArgument?: mirrorArkts.TypeNode + ): mirrorArkts.UndefinedLiteral | mirrorArkts.ArrowFunctionExpression { + if (!lambdaBody) { + return mirrorArkts.factory.createUndefinedLiteral(); + } + collectComponentAttributeImport(typeNode, moduleName); + let safeType: mirrorArkts.TypeNode | undefined; + if (checkIsSpecialComponentAttributeFromType(typeNode, typeArgument)) { + safeType = mirrorArkts.factory.updateETSTypeReference( + typeNode, + mirrorArkts.factory.updateETSTypeReferencePart( + typeNode.part!, + typeNode.part!.name!, + mirrorArkts.factory.createTSTypeParameterInstantiation([typeArgument!.clone()]) + ) + ); + } else { + safeType = isSafeType(typeNode) ? typeNode : undefined; + } + + const styleLambdaParam: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier(BuilderLambdaNames.STYLE_ARROW_PARAM_NAME, safeType), + false + ); + + const returnStatement = mirrorArkts.factory.createReturnStatement(); + // mirrorArkts.NodeCache.getInstance().collect(returnStatement); + const body: mirrorArkts.BlockStatement = mirrorArkts.factory.createBlockStatement([ + mirrorArkts.factory.createExpressionStatement(lambdaBody), + returnStatement, + ]); + + const func = mirrorArkts.factory.createScriptFunction( + body, + undefined, + [styleLambdaParam], + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ); + + return addMemoAnnotation(mirrorArkts.factory.createArrowFunctionExpression(func)); + } + + /* + * create style arguments in builder lambda declaration. + */ + static createStyleArgInBuilderLambdaDecl( + typeNode: mirrorArkts.TypeNode | undefined, + isFunctionCall: boolean + ): mirrorArkts.ETSParameterExpression { + const styleLambdaParam: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier(BuilderLambdaNames.STYLE_ARROW_PARAM_NAME, typeNode), + false + ); + const funcType = mirrorArkts.factory.createETSFunctionType( + undefined, + [styleLambdaParam], + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW + ); + addMemoAnnotation(funcType); + + let parameter: mirrorArkts.ETSParameterExpression; + const optionalFuncType = mirrorArkts.factory.createETSUnionType([funcType, mirrorArkts.factory.createETSUndefinedType()]); + parameter = mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier(BuilderLambdaNames.STYLE_PARAM_NAME, optionalFuncType), + false + ); + mirrorArkts.AstNodeCache.getInstance().collect(parameter); + return parameter; + } + + /** + * If a builder lambda's argument is an arrow function, + * then transform any builder lambda in the function body. + */ + static processArgArrowFunction(arg: mirrorArkts.ArrowFunctionExpression): mirrorArkts.ArrowFunctionExpression { + const func: mirrorArkts.ScriptFunction = arg.function!; + const updateFunc = mirrorArkts.factory.updateScriptFunction( + func, + !!func.body && mirrorArkts.isBlockStatement(func.body) + ? mirrorArkts.factory.updateBlockStatement( + func.body, + func.body.statements.map((st) => this.updateContentBodyInBuilderLambda(st)) + ) + : undefined, + + func.typeParams, + func.params, + func.returnTypeAnnotation, + false + , + func.flags, + func.modifierFlags, + undefined, + undefined + ); + return mirrorArkts.factory.updateArrowFunctionExpression(arg, updateFunc); + } + + /** + * transform options argument in a builder lambda call. + */ + static processOptionsArg(arg: T, typeName: string): T { + let expr: mirrorArkts.ObjectExpression | undefined; + if (mirrorArkts.isTSAsExpression(arg) && !!arg.expr && mirrorArkts.isObjectExpression(arg.expr)) { + expr = arg.expr; + } else if (mirrorArkts.isObjectExpression(arg)) { + expr = arg; + } + if (!expr) { + return arg; + } + const properties = (expr.properties as mirrorArkts.Property[]).map((p) => factory.updatePropertiesInOptions(p)); + const updatedExpr: mirrorArkts.ObjectExpression = mirrorArkts.ObjectExpression.updateObjectExpression( + expr, + mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, + properties, + false + ); + if (mirrorArkts.isTSAsExpression(arg)) { + return mirrorArkts.TSAsExpression.updateTSAsExpression(arg, updatedExpr, arg.typeAnnotation, arg.isConst) as T; + } + return updatedExpr as T; + } + + static updatePropertiesInOptions(prop: mirrorArkts.Property): mirrorArkts.Property { + let decl: mirrorArkts.AstNode | undefined; + if (!prop.key || !prop.value || !(decl = mirrorArkts.getDecl(prop.key)) || !mirrorArkts.isMethodDefinition(decl) || !decl.function) { + return prop; + } + const returnType: mirrorArkts.TypeNode | undefined = decl.function.returnTypeAnnotation; + const isBindable: boolean = !!returnType && hasBindableProperty(returnType, BindableDecl.BINDABLE); + let isBuilderParam: boolean = false; + let isLinkIntrinsic: boolean = false; + decl.function.annotations.forEach((anno) => { + isBuilderParam ||= isDecoratorAnnotation(anno, DecoratorNames.BUILDER_PARAM); + isLinkIntrinsic ||= isDecoratorIntrinsicAnnotation(anno, DecoratorIntrinsicNames.LINK); + }); + + if (isBindable && isDoubleDollarCall(prop.value)) { + return factory.updateBindableProperty(prop); + } else if (isBuilderParam && mirrorArkts.isArrowFunctionExpression(prop.value)) { + addMemoAnnotation(prop.value); + return prop; + } else if ( + isLinkIntrinsic && + mirrorArkts.isIdentifier(prop.key) && + mirrorArkts.isMemberExpression(prop.value) && + mirrorArkts.isThisExpression(prop.value.object) && + mirrorArkts.isIdentifier(prop.value.property) + ) { + return mirrorArkts.Property.updateProperty( + prop, + mirrorArkts.factory.createIdentifier(backingField(prop.key.name)), + factory.updateBackingMember(prop.value, prop.value.property.name) + ); + } + return prop; + } + + /** + * create or update arguments in a builder lambda call. + * If the corresponding argument is not provided, fill-in an `undefined` to it. + */ + static createOrUpdateArgInBuilderLambda( + fallback: mirrorArkts.AstNode | undefined, + arg: mirrorArkts.Expression | undefined, + typeName?: string, + canAddMemo?: boolean + ): mirrorArkts.AstNode | undefined { + if (!arg) { + return fallback; + } + if (mirrorArkts.isArrowFunctionExpression(arg)) { + const newNode = this.processArgArrowFunction(arg); + if (canAddMemo) { + addMemoAnnotation(newNode); + } + return newNode; + } + // this is too optimistic to check if this is an options argument... + if (mirrorArkts.isTSAsExpression(arg) || mirrorArkts.isObjectExpression(arg)) { + return this.processOptionsArg(arg, typeName!); + } + return arg; + } + + static createSecondLastArgInBuilderLambda(argInfo: BuilderLambdaSecondLastArgInfo): mirrorArkts.AstNode | undefined { + if (!!argInfo.isReusable && !!argInfo.reuseId) { + const reuseIdNode = mirrorArkts.factory.createStringLiteral(argInfo.reuseId); + return this.createOrUpdateArgInBuilderLambda(reuseIdNode, undefined, undefined); + } else if (!argInfo.isFunctionCall) { + return this.createOrUpdateArgInBuilderLambda(mirrorArkts.factory.createUndefinedLiteral(), undefined, undefined); + } + return undefined; + } + + /** + * transform arguments in a builder lambda call. + */ + static generateArgsInBuilderLambda( + leaf: mirrorArkts.CallExpression, + lambdaBody: mirrorArkts.Identifier | mirrorArkts.CallExpression, + declInfo: BuilderLambdaDeclInfo + ): (mirrorArkts.AstNode | undefined)[] { + const { isFunctionCall, params, returnType, moduleName } = declInfo; + const type: mirrorArkts.Identifier | undefined = builderLambdaType(leaf); + const args: (mirrorArkts.AstNode | undefined)[] = [ + this.createStyleArgInBuilderLambda(lambdaBody, returnType, moduleName, leaf.typeParams?.params.at(0)), + ]; + const secondLastArgInfo = buildSecondLastArgInfo(type, isFunctionCall); + const isTrailingCall = leaf.isTrailingCall; + forEachArgWithParam( + leaf.arguments, + params, + (arg, param, index) => { + let modifiedArg: mirrorArkts.AstNode | undefined; + if (index === params.length - 2 && !arg) { + modifiedArg = this.createSecondLastArgInBuilderLambda(secondLastArgInfo); + } + if (!modifiedArg) { + const memoableInfo = collectMemoableInfoInParameter(param); + const canAddMemo = + (!!memoableInfo.hasBuilder || !!memoableInfo.hasMemo) && !!memoableInfo.hasProperType; + modifiedArg = this.createOrUpdateArgInBuilderLambda( + mirrorArkts.factory.createUndefinedLiteral(), + arg, + type?.name, + canAddMemo + ); + } + args.push(modifiedArg); + }, + { isTrailingCall } + ); + return filterDefined(args); + } + + /** + * update if-else in trailing lambda contents in a builder lambda call. + */ + static updateIfElseContentBodyInBuilderLambda(statement: mirrorArkts.Statement): mirrorArkts.Statement { + if (mirrorArkts.isIfStatement(statement) && !!statement.consequent) { + const alternate = !!statement.alternate + ? this.updateIfElseContentBodyInBuilderLambda(statement.alternate) + : statement.alternate; + const consequence = this.updateIfElseContentBodyInBuilderLambda(statement.consequent); + return mirrorArkts.factory.updateIfStatement(statement, statement.test, consequence!, alternate); + } + if (mirrorArkts.isBlockStatement(statement)) { + return mirrorArkts.factory.updateBlockStatement( + statement, + statement.statements.map((st) => this.updateContentBodyInBuilderLambda(st)) + ); + } + return statement; + } + + /** + * update trailing lambda contents in a builder lambda call. + */ + static updateContentBodyInBuilderLambda(statement: mirrorArkts.Statement): mirrorArkts.Statement { + if ( + mirrorArkts.isExpressionStatement(statement) && + mirrorArkts.isCallExpression(statement.expression) && + isBuilderLambda(statement.expression) + ) { + return mirrorArkts.factory.updateExpressionStatement( + statement, + this.transformBuilderLambda(statement.expression) as mirrorArkts.Expression + ); + } + if (mirrorArkts.isIfStatement(statement)) { + return this.updateIfElseContentBodyInBuilderLambda(statement); + } + + return statement; + } + + /** + * replace function call's name to the corresponding transformed name. + */ + static builderLambdaReplace(leaf: mirrorArkts.CallExpression): mirrorArkts.Identifier | mirrorArkts.MemberExpression | undefined { + if (!callIsGoodForBuilderLambda(leaf)) { + return undefined; + } + const node = leaf.callee; + const funcName = builderLambdaFunctionName(leaf); + if (!funcName) { + return undefined; + } + if (mirrorArkts.isIdentifier(node)) { + return mirrorArkts.factory.createIdentifier(funcName); + } + if (mirrorArkts.isMemberExpression(node)) { + return mirrorArkts.factory.createMemberExpression( + node.object, + mirrorArkts.factory.createIdentifier(funcName), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + node.isComputed, + node.isOptional + ); + } + return undefined; + } + + /** + * transform `@ComponentBuilder` in declared methods. + */ + static transformBuilderLambdaMethodDecl( + node: mirrorArkts.MethodDefinition, + externalSourceName?: string + ): mirrorArkts.MethodDefinition { + const func: mirrorArkts.ScriptFunction = node.function!; + const isFunctionCall: boolean = isBuilderLambdaFunctionCall(node); + const typeNode: mirrorArkts.TypeNode | undefined = builderLambdaMethodDeclType(node); + const newOverloads: mirrorArkts.MethodDefinition[] = node.overloads.map((method) => + factory.transformBuilderLambdaMethodDecl(method) + ); + + const newNode = this.updateBuilderLambdaMethodDecl( + node, + [this.createStyleArgInBuilderLambdaDecl(typeNode, isFunctionCall)], + removeAnnotationByName(func.annotations, BuilderLambdaNames.ANNOTATION_NAME), + replaceBuilderLambdaDeclMethodName(node.id?.name), + externalSourceName + ).setOverloads(newOverloads); + mirrorArkts.AstNodeCache.getInstance().collect(newNode); + return newNode; + } + + /** + * transform `.animation(...)` to `.animationStart(...) and .animationStop(...)` + */ + static updateAnimation(instanceCalls: InstanceCallInfo[]): void { + let lastAniIdx = 0; + let curIdx = 0; + + while (curIdx < instanceCalls.length) { + if (instanceCalls[curIdx].isReceiver) { + curIdx++; + continue; + } + const property: mirrorArkts.Identifier = instanceCalls[curIdx].call.callee as mirrorArkts.Identifier; + if (property.name === AnimationNames.ANIMATION) { + const aniStart: mirrorArkts.CallExpression = mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createIdentifier(AnimationNames.ANIMATION_START), + instanceCalls[curIdx].call.arguments, + undefined + ); + const aniStop: mirrorArkts.CallExpression = mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createIdentifier(AnimationNames.ANIMATION_STOP), + instanceCalls[curIdx].call.arguments.map((arg) => arg.clone()), + undefined + ); + instanceCalls.splice(lastAniIdx, 0, { isReceiver: false, call: aniStart }); + instanceCalls[curIdx + 1] = { isReceiver: false, call: aniStop }; + curIdx += 2; + lastAniIdx = curIdx; + } else { + curIdx++; + } + } + } + + /** + * transform `@ComponentBuilder` in non-declared calls. + */ + static transformBuilderLambda(node: mirrorArkts.CallExpression): mirrorArkts.AstNode { + let instanceCalls: InstanceCallInfo[] = []; + let leaf: mirrorArkts.CallExpression = node; + + while (isStyleChainedCall(leaf) || isStyleWithReceiverCall(leaf)) { + if (isStyleChainedCall(leaf)) { + instanceCalls.push({ + isReceiver: false, + call: mirrorArkts.factory.createCallExpression( + (leaf.callee as mirrorArkts.MemberExpression).property, + leaf.arguments, + leaf.typeParams + ), + }); + leaf = (leaf.callee as mirrorArkts.MemberExpression).object as mirrorArkts.CallExpression; + } + + if (isStyleWithReceiverCall(leaf)) { + instanceCalls.push({ + isReceiver: true, + call: mirrorArkts.factory.createCallExpression(leaf.callee, leaf.arguments, leaf.typeParams), + }); + leaf = leaf.arguments[0] as mirrorArkts.CallExpression; + } + } + + const decl: mirrorArkts.AstNode | undefined = findBuilderLambdaDecl(leaf); + if (!decl) { + return node; + } + + const replace: mirrorArkts.Identifier | mirrorArkts.MemberExpression | undefined = this.builderLambdaReplace(leaf); + const declInfo: BuilderLambdaDeclInfo | undefined = findBuilderLambdaDeclInfo(decl); + if (!replace || !declInfo) { + return node; + } + + let lambdaBody: mirrorArkts.Identifier | mirrorArkts.CallExpression | undefined; + if (instanceCalls.length > 0) { + instanceCalls = instanceCalls.reverse(); + this.updateAnimation(instanceCalls); + lambdaBody = mirrorArkts.factory.createIdentifier(BuilderLambdaNames.STYLE_ARROW_PARAM_NAME); + mirrorArkts.AstNodeCache.getInstance().collect(lambdaBody); + instanceCalls.forEach((callInfo) => { + lambdaBody = this.createStyleLambdaBody(lambdaBody!, callInfo); + }); + } + + const args: (mirrorArkts.AstNode | undefined)[] = this.generateArgsInBuilderLambda(leaf, lambdaBody!, declInfo); + const newNode = mirrorArkts.factory.updateCallExpression(node, replace, filterDefined(args) as mirrorArkts.Expression[], leaf.typeParams); + mirrorArkts.AstNodeCache.getInstance().collect(newNode); + return newNode; + } + + /* + * update bindableProperty, e.g. `text: $$(this.text)` => `text: { value: xxx , onChange: xxx }`. + */ + static updateBindableProperty(prop: mirrorArkts.Property, type?: mirrorArkts.TypeNode): mirrorArkts.Property { + let res: mirrorArkts.Property[] = []; + let valueType: mirrorArkts.TypeNode; + if ( + prop.value && + mirrorArkts.isCallExpression(prop.value) && + prop.value.arguments && + prop.value.arguments.length === 1 + ) { + let bindableArg = prop.value.arguments[0]; + valueType = getDecalTypeFromValue(bindableArg); + res.push( + factory.generateValueProperty(bindableArg), + factory.generateOnChangeArrowFunc(bindableArg, valueType) + ); + } else { + return prop; + } + const asObjProp: mirrorArkts.TSAsExpression = mirrorArkts.factory.createTSAsExpression( + mirrorArkts.ObjectExpression.createObjectExpression( + mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, + res, + false + ), + factory.createBindableType(valueType), + false + ); + return mirrorArkts.factory.updateProperty(prop, prop.kind, prop.key, asObjProp, prop.isMethod, prop.isComputed); + } + + /* + * generate `value: ` in object. + */ + static generateValueProperty(bindableArg: mirrorArkts.Expression): mirrorArkts.Property { + return mirrorArkts.factory.createProperty( + mirrorArkts.Es2pandaPropertyKind.PROPERTY_KIND_INIT, + mirrorArkts.factory.createIdentifier('value'), + bindableArg.clone(), + false, + false + ); + } + + /* + * generate `onChange: (value) => = value` in object. + */ + static generateOnChangeArrowFunc(bindableArg: mirrorArkts.Expression, valueType: mirrorArkts.TypeNode): mirrorArkts.Property { + return mirrorArkts.factory.createProperty( + mirrorArkts.Es2pandaPropertyKind.PROPERTY_KIND_INIT, + mirrorArkts.factory.createIdentifier('onChange'), + PropertyFactory.createArrowFunctionWithParamsAndBody( + undefined, + [ + mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier('value', valueType.clone()), + false + ), + ], + undefined, + false, + [ + mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createAssignmentExpression( + bindableArg.clone(), + mirrorArkts.factory.createIdentifier('value'), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION + ) + ), + ] + ), + false, + false + ); + } + + /* + * generate `Bindable`. + */ + static createBindableType(valueType: mirrorArkts.TypeNode): mirrorArkts.ETSTypeReference { + const transformedKey = BindableDecl.BINDABLE; + ImportCollector.getInstance().collectImport(transformedKey); + return mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(transformedKey), + mirrorArkts.factory.createTSTypeParameterInstantiation([valueType.clone()]) + ) + ); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/builder-lambda-translators/utils.ts b/arkui-plugins/mirror-replace/ui-plugins/builder-lambda-translators/utils.ts new file mode 100644 index 000000000..602048c7f --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/builder-lambda-translators/utils.ts @@ -0,0 +1,562 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { isAnnotation, matchPrefix } from '../../common/arkts-utils'; +import { BuilderLambdaNames, isCustomComponentAnnotation } from '../utils'; +import { DeclarationCollector } from '../../common/declaration-collector'; +import { ARKUI_IMPORT_PREFIX_NAMES, BindableDecl, Dollars, StructDecoratorNames } from '../../common/predefines'; +import { ImportCollector } from '../../common/import-collector'; + +export type BuilderLambdaDeclInfo = { + isFunctionCall: boolean; // isFunctionCall means it is from $_instantiate. + params: readonly mirrorArkts.Expression[]; + returnType: mirrorArkts.TypeNode | undefined; + moduleName: string; +}; + +export type BuilderLambdaAstNode = mirrorArkts.ScriptFunction | mirrorArkts.ETSParameterExpression | mirrorArkts.FunctionDeclaration; + +export type InstanceCallInfo = { + isReceiver: boolean; + call: mirrorArkts.CallExpression; +}; + +export type BuilderLambdaArgInfo = { + isFunctionCall: boolean; +}; + +export type BuilderLambdaReusableArgInfo = { + isReusable?: boolean; + reuseId?: string; +}; + +export type BuilderLambdaSecondLastArgInfo = BuilderLambdaArgInfo & BuilderLambdaReusableArgInfo; + +export function buildSecondLastArgInfo( + type: mirrorArkts.Identifier | undefined, + isFunctionCall: boolean +): BuilderLambdaSecondLastArgInfo { + let isReusable: boolean | undefined; + let reuseId: string | undefined; + if (!isFunctionCall && !!type) { + const customComponentDecl = mirrorArkts.getDecl(type); + isReusable = + !!customComponentDecl && + mirrorArkts.isClassDefinition(customComponentDecl) && + customComponentDecl.annotations.some((anno) => + isCustomComponentAnnotation(anno, StructDecoratorNames.RESUABLE) + ); + reuseId = isReusable ? type.name : undefined; + } + return { isFunctionCall, isReusable, reuseId }; +} + +/** + * Used in finding "XXX" in BuilderLambda("XXX") + * @deprecated + */ +export function builderLambdaArgumentName(annotation: mirrorArkts.AnnotationUsage): string | undefined { + if (!isBuilderLambdaAnnotation(annotation)) { + return undefined; + } + + const property = annotation.properties.at(0); + if (!property || !mirrorArkts.isClassProperty(property)) { + return undefined; + } + if (!property.value || !mirrorArkts.isStringLiteral(property.value)) { + return undefined; + } + + return property.value.str; +} + +export function isBuilderLambda(node: mirrorArkts.AstNode): boolean { + const builderLambdaCall: mirrorArkts.AstNode | undefined = getDeclForBuilderLambda(node); + if (!builderLambdaCall) { + return mirrorArkts.isCallExpression(node) && node.arguments.length > 0 && isBuilderLambda(node.arguments[0]); + } + return !!builderLambdaCall; +} + +/** + * Determine whether it is a function with receiver method definition. + * + * @param node method definition node + */ +export function isFunctionWithReceiver(node: mirrorArkts.MethodDefinition): boolean { + if (node.function && mirrorArkts.isScriptFunction(node.function)) { + return node.function.hasReceiver; + } + return false; +} + +/** + * Determine whether it is a function with receiver call. + * + * @param node identifier node + */ +export function isFunctionWithReceiverCall(node: mirrorArkts.Identifier): boolean { + const decl: mirrorArkts.AstNode | undefined = mirrorArkts.getDecl(node); + if (decl && mirrorArkts.isMethodDefinition(decl)) { + return isFunctionWithReceiver(decl); + } + return false; +} + +/** + * Determine whether it is a style chained call. + * + * @param node call expression node + */ +export function isStyleChainedCall(node: mirrorArkts.CallExpression): boolean { + return ( + mirrorArkts.isMemberExpression(node.callee) && + mirrorArkts.isIdentifier(node.callee.property) && + mirrorArkts.isCallExpression(node.callee.object) + ); +} + +/** + * Determine whether it is a style function with receiver call. + * + * @param node call expression node + */ +export function isStyleWithReceiverCall(node: mirrorArkts.CallExpression): boolean { + return ( + mirrorArkts.isIdentifier(node.callee) && + isFunctionWithReceiverCall(node.callee) && + !!node.arguments.length && + mirrorArkts.isCallExpression(node.arguments[0]) + ); +} + +/** + * replace $_instantiate with _instantiateImpl. + * + * @param name origin name + */ +export function replaceBuilderLambdaDeclMethodName(name: string | undefined): string | undefined { + if (!!name && name === BuilderLambdaNames.ORIGIN_METHOD_NAME) { + return BuilderLambdaNames.TRANSFORM_METHOD_NAME; + } + return undefined; +} + +export function isBuilderLambdaMethodDecl(node: mirrorArkts.AstNode): boolean { + const builderLambdaMethodDecl: mirrorArkts.AstNode | undefined = getDeclForBuilderLambdaMethodDecl(node); + return !!builderLambdaMethodDecl; +} + +export function getDeclForBuilderLambdaMethodDecl(node: mirrorArkts.AstNode): mirrorArkts.AstNode | undefined { + if (!node || !mirrorArkts.isMethodDefinition(node)) { + return undefined; + } + + const isBuilderLambda: boolean = !!node.id && isBuilderLambdaCall(node.id); + const isMethodDecl: boolean = + !!node.function && + mirrorArkts.hasModifierFlag(node.function, mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE); + if (isBuilderLambda && isMethodDecl) { + return node; + } + return undefined; +} + +export function getDeclForBuilderLambda(node: mirrorArkts.AstNode): mirrorArkts.AstNode | undefined { + if (!node || !mirrorArkts.isCallExpression(node)) { + return undefined; + } + + let currNode: mirrorArkts.AstNode | undefined = node; + while ( + !!currNode && + mirrorArkts.isCallExpression(currNode) && + !!currNode.callee && + mirrorArkts.isMemberExpression(currNode.callee) + ) { + const _node: mirrorArkts.MemberExpression = currNode.callee; + if (!!_node.property && mirrorArkts.isIdentifier(_node.property) && isBuilderLambdaCall(_node.property)) { + return node; + } + if (!!_node.object && mirrorArkts.isCallExpression(_node.object) && isBuilderLambdaCall(_node.object)) { + return node; + } + currNode = _node.object; + } + + if (isBuilderLambdaCall(node)) { + return node; + } + return undefined; +} + +export function isBuilderLambdaCall(node: mirrorArkts.CallExpression | mirrorArkts.Identifier): boolean { + const expr = mirrorArkts.isIdentifier(node) ? node : node.callee; + if (!expr) { + return false; + } + const decl = mirrorArkts.getDecl(expr); + if (!decl) { + return false; + } + + if (mirrorArkts.isMethodDefinition(decl)) { + if (isFunctionWithReceiver(decl)) { + return ( + mirrorArkts.isCallExpression(node) && + node.arguments.length > 0 && + !!getDeclForBuilderLambda(node.arguments[0]) + ); + } + return isBuilderLambdaMethod(decl); + } + if (mirrorArkts.isFunctionExpression(decl) && !!decl.function) { + return hasBuilderLambdaAnnotation(decl.function); + } + return false; +} + +export function isBuilderLambdaMethod(node: mirrorArkts.MethodDefinition): boolean { + if (!node || !mirrorArkts.isMethodDefinition(node) || !node.function) { + return false; + } + + const result = hasBuilderLambdaAnnotation(node.function); + if (result) { + return true; + } + if (node.overloads.length > 0) { + return node.overloads.some(isBuilderLambdaMethod); + } + return false; +} + +export function hasBuilderLambdaAnnotation(node: BuilderLambdaAstNode): boolean { + return node.annotations.some(isBuilderLambdaAnnotation); +} + +export function isBuilderLambdaAnnotation(node: mirrorArkts.AnnotationUsage): boolean { + return isAnnotation(node, BuilderLambdaNames.ANNOTATION_NAME); +} + +export function findBuilderLambdaAnnotation( + node: mirrorArkts.ScriptFunction | mirrorArkts.ETSParameterExpression +): mirrorArkts.AnnotationUsage | undefined { + return node.annotations.find(isBuilderLambdaAnnotation); +} + +export function findBuilderLambdaInMethod(node: mirrorArkts.MethodDefinition): mirrorArkts.AnnotationUsage | undefined { + if (!node || !mirrorArkts.isMethodDefinition(node) || !node.function) { + return undefined; + } + const result = findBuilderLambdaAnnotation(node.function); + if (!!result) { + return result; + } + node.overloads.forEach((overload) => { + const anno: mirrorArkts.AnnotationUsage | undefined = findBuilderLambdaInMethod(overload); + if (!!anno) { + return anno; + } + }); + return undefined; +} + +export function findBuilderLambdaInCall( + node: mirrorArkts.CallExpression | mirrorArkts.Identifier +): mirrorArkts.AnnotationUsage | undefined { + const decl = findBuilderLambdaDecl(node); + if (!decl) { + return undefined; + } + + if (mirrorArkts.isMethodDefinition(decl)) { + return findBuilderLambdaInMethod(decl); + } + if (mirrorArkts.isFunctionExpression(decl) && !!decl.function) { + return findBuilderLambdaAnnotation(decl.function); + } + return undefined; +} + +export function findBuilderLambdaDecl(node: mirrorArkts.CallExpression | mirrorArkts.Identifier): mirrorArkts.AstNode | undefined { + const expr = mirrorArkts.isIdentifier(node) ? node : node.callee; + if (!expr) { + return undefined; + } + const decl = mirrorArkts.getDecl(expr); + if (!decl) { + return undefined; + } + const moduleName: string = mirrorArkts.getProgramFromAstNode(decl).moduleName; + if (!moduleName) { + return undefined; + } + DeclarationCollector.getInstance().collect(decl); + return decl; +} + +/** + * check whether `` is the passing parameter. + * + * @param name origin name + */ +export function isParameterPassing(prop: mirrorArkts.Property): boolean | undefined { + return ( + prop.key && + prop.value && + mirrorArkts.isIdentifier(prop.key) && + mirrorArkts.isMemberExpression(prop.value) && + mirrorArkts.isThisExpression(prop.value.object) && + mirrorArkts.isIdentifier(prop.value.property) + ); +} + +export function findBuilderLambdaDeclInfo(decl: mirrorArkts.AstNode | undefined): BuilderLambdaDeclInfo | undefined { + if (!decl) { + return undefined; + } + const moduleName: string = mirrorArkts.getProgramFromAstNode(decl).moduleName; + if (!moduleName) { + return undefined; + } + if (mirrorArkts.isMethodDefinition(decl) && !!decl.function) { + const params = decl.function.params.map((p) => p.clone()); + const returnType = decl.function.returnTypeAnnotation?.clone(); + const isFunctionCall = isBuilderLambdaFunctionCall(decl); + return { isFunctionCall, params, returnType, moduleName }; + } + + return undefined; +} + +export function isBuilderLambdaFunctionCall(decl: mirrorArkts.AstNode | undefined): boolean { + if (!decl) { + return false; + } + if (mirrorArkts.isMethodDefinition(decl)) { + return ( + decl.id?.name !== BuilderLambdaNames.ORIGIN_METHOD_NAME && + decl.id?.name !== BuilderLambdaNames.TRANSFORM_METHOD_NAME + ); + } + return false; +} + +export function callIsGoodForBuilderLambda(leaf: mirrorArkts.CallExpression): boolean { + const node = leaf.callee; + return mirrorArkts.isIdentifier(node) || mirrorArkts.isMemberExpression(node); +} + +// quick-fix: make `Repeat` component as special return type check. +// This is a temporary solution +export function checkIsSpecialComponentAttributeFromType( + typeNode: mirrorArkts.TypeNode | undefined, + typeArgument?: mirrorArkts.TypeNode +): typeNode is mirrorArkts.ETSTypeReference { + if (!typeNode || !typeArgument) { + return false; + } + if (!mirrorArkts.isETSTypeReference(typeNode) || !typeNode.part || !typeNode.part.name) { + return false; + } + if (!mirrorArkts.isIdentifier(typeNode.part.name)) { + return false; + } + return typeNode.part.name.name === 'RepeatAttribute'; +} + +export function isSafeType(type: mirrorArkts.TypeNode | undefined): boolean { + if (!type) { + return false; + } + // type can be generic (not safe) if includes any type params in a type reference. + if (mirrorArkts.isETSTypeReference(type) && !!type.part && !!type.part.typeParams) { + return false; + } + return true; +} + +export function builderLambdaMethodDeclType(method: mirrorArkts.MethodDefinition): mirrorArkts.TypeNode | undefined { + if (!method || !method.function) { + return undefined; + } + return method.function.returnTypeAnnotation; +} + +export function builderLambdaType(leaf: mirrorArkts.CallExpression): mirrorArkts.Identifier | undefined { + if (!callIsGoodForBuilderLambda(leaf)) { + return undefined; + } + const node = leaf.callee; + let name: mirrorArkts.Identifier | undefined; + if (mirrorArkts.isIdentifier(node)) { + name = node; + } + if (mirrorArkts.isMemberExpression(node) && mirrorArkts.isIdentifier(node.object)) { + name = node.object; + } + return name; +} + +export function builderLambdaFunctionName(node: mirrorArkts.CallExpression): string | undefined { + const annotation = findBuilderLambdaInCall(node); + if (!annotation) { + return undefined; + } + if (mirrorArkts.isIdentifier(node.callee)) { + return node.callee.name; + } + if ( + mirrorArkts.isMemberExpression(node.callee) && + mirrorArkts.isIdentifier(node.callee.property) && + node.callee.property.name === BuilderLambdaNames.ORIGIN_METHOD_NAME + ) { + return BuilderLambdaNames.TRANSFORM_METHOD_NAME; + } + return undefined; +} + +/** + * Determine whether the node `` is `` bindable property. + * + * @param type type node + * @param bindableDecl bindable decalaration name + */ +export function hasBindableProperty(type: mirrorArkts.AstNode, bindableDecl: BindableDecl): boolean { + let res: boolean = false; + if (mirrorArkts.isETSUnionType(type)) { + type.types.forEach((item: mirrorArkts.TypeNode) => { + res = res || hasBindableProperty(item, bindableDecl); + }); + } + if (mirrorArkts.isETSTypeReference(type)) { + res = + res || + (!!type.part && + !!type.part.name && + mirrorArkts.isIdentifier(type.part.name) && + type.part.name.name === bindableDecl); + } + + return res; +} + +/** + * Determine whether `` is `$$()` call expression node. + * + * @param value expression node + */ +export function isDoubleDollarCall( + value: mirrorArkts.Expression, + ignoreDecl: boolean = false +): value is mirrorArkts.CallExpression { + if (!mirrorArkts.isCallExpression(value)) { + return false; + } + if ( + !(!!value.callee && mirrorArkts.isIdentifier(value.callee) && value.callee.name === Dollars.DOLLAR_DOLLAR) + ) { + return false; + } + if (!ignoreDecl) { + const decl = mirrorArkts.getDecl(value.callee); + if (!decl) { + return false; + } + const moduleName: string = mirrorArkts.getProgramFromAstNode(decl).moduleName; + if (!moduleName || !matchPrefix(ARKUI_IMPORT_PREFIX_NAMES, moduleName)) { + return false; + } + DeclarationCollector.getInstance().collect(decl); + } + return true; +} + +/** + * get declaration type from `{xxx: }` or `fun()`. + * + * @param value type node + */ +export function getDecalTypeFromValue(value: mirrorArkts.Expression): mirrorArkts.TypeNode { + const decl: mirrorArkts.AstNode | undefined = mirrorArkts.getDecl(value); + if (!decl || !mirrorArkts.isClassProperty(decl)) { + throw new Error('cannot get declaration'); + } + if (isArrayType(decl.typeAnnotation!)) { + return getElementTypeFromArray(decl.typeAnnotation!)!; + } + return decl.typeAnnotation!; +} + +/** + * Determine whether `` is array type, e.g. `xxx[]` or `Array`. + * + * @param type type node + */ +export function isArrayType(type: mirrorArkts.TypeNode): boolean { + return ( + mirrorArkts.isTSArrayType(type) || + (mirrorArkts.isETSTypeReference(type) && + !!type.part && + mirrorArkts.isETSTypeReferencePart(type.part) && + !!type.part.name && + mirrorArkts.isIdentifier(type.part.name) && + type.part.name.name === 'Array') + ); +} + +/** + * get element type from array type node ``. + * + * @param arrayType array type node + */ +export function getElementTypeFromArray(arrayType: mirrorArkts.TypeNode): mirrorArkts.TypeNode | undefined { + if (mirrorArkts.isTSArrayType(arrayType)) { + return arrayType.elementType?.clone(); + } else if ( + mirrorArkts.isETSTypeReference(arrayType) && + !!arrayType.part && + mirrorArkts.isETSTypeReferencePart(arrayType.part) && + !!arrayType.part.typeParams && + arrayType.part.typeParams.params.length + ) { + return arrayType.part.typeParams.params[0].clone(); + } + return undefined; +} + +export function collectComponentAttributeImport(type: mirrorArkts.TypeNode | undefined, moduleName: string): void { + if ( + !type || + !mirrorArkts.isETSTypeReference(type) || + !type.part || + !type.part.name || + !mirrorArkts.isIdentifier(type.part.name) + ) { + return; + } + + const regex: RegExp = /(?\w+Attribute)(?:<.*>)?$/; + const name: string = type.part.name.name; + const match: RegExpExecArray | null = regex.exec(name); + const attributeName: string | undefined = match?.groups?.source; + if (!!attributeName) { + ImportCollector.getInstance().collectSource(attributeName, moduleName); + ImportCollector.getInstance().collectImport(attributeName); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/checked-transformer.ts b/arkui-plugins/mirror-replace/ui-plugins/checked-transformer.ts new file mode 100644 index 000000000..7feebcf46 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/checked-transformer.ts @@ -0,0 +1,165 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror' +import { ProjectConfig } from '../common/plugin-context'; +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 { 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 { checkCustomDialogController, insertImportDeclaration, transformDeclaration } from './customdialog'; +import { LogCollector } from '../common/log-collector'; +import { + CustomComponentScopeInfo, + initResourceInfo, + loadBuildJson, + LoaderJson, + ResourceInfo, + ScopeInfoCollection, + isForEachDecl +} from './struct-translators/utils'; +import { collectCustomComponentScopeInfo, CustomComponentNames, isCustomComponentClass } from './utils'; +import { findAndCollectMemoableNode } from '../collectors/memo-collectors/factory'; + +export class CheckedTransformer extends AbstractVisitor { + private scope: ScopeInfoCollection; + projectConfig: ProjectConfig | undefined; + aceBuildJson: LoaderJson; + resourceInfo: ResourceInfo; + + constructor(projectConfig: ProjectConfig | undefined) { + super(); + this.projectConfig = projectConfig; + this.scope = { customComponents: [] }; + this.aceBuildJson = loadBuildJson(this.projectConfig); + this.resourceInfo = initResourceInfo(this.projectConfig, this.aceBuildJson); + } + + reset(): void { + super.reset(); + this.scope = { customComponents: [] }; + PropertyCache.getInstance().reset(); + ImportCollector.getInstance().reset(); + DeclarationCollector.getInstance().reset(); + LogCollector.getInstance().reset(); + } + + enter(node: mirrorArkts.AstNode): void { + if (mirrorArkts.isClassDeclaration(node) && !!node.definition && node.definition.body.length > 0) { + const customComponentInfo = collectCustomComponentScopeInfo(node); + if (!!customComponentInfo) { + this.scope.customComponents.push(customComponentInfo); + } + } + if (mirrorArkts.isMethodDefinition(node) && this.scope.customComponents.length > 0) { + const name = node.id?.name; + const scopeInfo = this.scope.customComponents.pop()!; + scopeInfo.hasInitializeStruct ||= name === CustomComponentNames.COMPONENT_INITIALIZE_STRUCT; + scopeInfo.hasUpdateStruct ||= name === CustomComponentNames.COMPONENT_UPDATE_STRUCT; + this.scope.customComponents.push(scopeInfo); + } + } + + exit(node: mirrorArkts.AstNode): void { + if ( + mirrorArkts.isClassDeclaration(node) && + this.scope.customComponents.length > 0 && + isCustomComponentClass(node, this.scope.customComponents[this.scope.customComponents.length - 1]) + ) { + this.scope.customComponents.pop(); + } + } + + isCustomDialogController(): boolean { + if (this.isExternal && this.externalSourceName === 'arkui.component.customDialogController') { + return true; + } + return false; + } + + processCustomDialogController(node: mirrorArkts.AstNode): mirrorArkts.AstNode { + // if (mirrorArkts.isETSModule(node)) { + // insertImportDeclaration(this.program); + // } + // if (mirrorArkts.isClassDeclaration(node) && node.definition && node.definition.ident && + // node.definition.ident.name === 'CustomDialogController') { + // return transformDeclaration(node as mirrorArkts.ClassDeclaration); + // } + return node; + } + + visitor(beforeChildren: mirrorArkts.AstNode): mirrorArkts.AstNode { + this.enter(beforeChildren); + if (mirrorArkts.isCallExpression(beforeChildren) && isBuilderLambda(beforeChildren)) { + /** + * replace style chained calls with instance calls + */ + console.log('transformBuilderLambda berfore: ', beforeChildren.dumpSrc()) + const lambda = builderLambdaFactory.transformBuilderLambda(beforeChildren); + console.log('transformBuilderLambda after: ', lambda.dumpSrc()) + return this.visitEachChild(lambda); + } else if (mirrorArkts.isMethodDefinition(beforeChildren) && isBuilderLambdaMethodDecl(beforeChildren)) { + const lambda = builderLambdaFactory.transformBuilderLambdaMethodDecl( + beforeChildren, + this.externalSourceName + ); + return this.visitEachChild(lambda); + } + const node = this.visitEachChild(beforeChildren); + if (mirrorArkts.isETSModule(node)) { + /** + * @notice visitEachChild returns a new ETSModule node now, so that we must update the progrom.ast pointer + */ + this.program?.setAst(node); + } + findAndCollectMemoableNode(node); + if (this.isCustomDialogController()) { + return this.processCustomDialogController(node); + } else if ( + mirrorArkts.isClassDeclaration(node) && + this.scope.customComponents.length > 0 && + isCustomComponentClass(node, this.scope.customComponents[this.scope.customComponents.length - 1]) + ) { + const scope: CustomComponentScopeInfo = this.scope.customComponents[this.scope.customComponents.length - 1]; + const newClass: mirrorArkts.ClassDeclaration = structFactory.tranformClassMembers(node, scope); + this.exit(beforeChildren); + return newClass; + } else if (isEntryWrapperClass(node)) { + // entryFactory.addMemoToEntryWrapperClassMethods(node); + return node; + } else if (mirrorArkts.isClassDeclaration(node)) { + return structFactory.transformNormalClass(node); + } else if (mirrorArkts.isCallExpression(node)) { + return structFactory.transformCallExpression(node, this.projectConfig, this.resourceInfo); + } else if (mirrorArkts.isMethodDefinition(node) && isForEachDecl(node, this.externalSourceName)) { + return structFactory.AddArrowTypeForParameter(node); + } else if (mirrorArkts.isTSInterfaceDeclaration(node)) { + return structFactory.tranformInterfaceMembers(node, this.externalSourceName); + } else if (mirrorArkts.isBlockStatement(node)) { + return checkCustomDialogController(node); + } + if (mirrorArkts.isETSModule(node) && ImportCollector.getInstance().importInfos.length > 0) { + ImportCollector.getInstance().insertCurrentImports(this.program); + LogCollector.getInstance().shouldIgnoreError(this.projectConfig?.ignoreError); + LogCollector.getInstance().emitLogInfo(); + } + return node; + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/component-transformer.ts b/arkui-plugins/mirror-replace/ui-plugins/component-transformer.ts index ecba1b3b1..82b77f800 100644 --- a/arkui-plugins/mirror-replace/ui-plugins/component-transformer.ts +++ b/arkui-plugins/mirror-replace/ui-plugins/component-transformer.ts @@ -468,7 +468,7 @@ export class ComponentTransformer extends AbstractVisitor { return mirrorArkts.factory.createMethodDefinition( mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, mirrorArkts.factory.createIdentifier(CustomComponentNames.BUILDCOMPATIBLENODE), - mirrorArkts.factory.createFunctionExpression(undefined, script), + mirrorArkts.factory.createFunctionExpression(script.id, script), mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC | mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC, false ); @@ -593,8 +593,12 @@ export class ComponentTransformer extends AbstractVisitor { /** * @notice visitEachChild returns a new ETSModule node now, so that we must update the progrom.ast pointer */ - this.program?.setAst(newNode) + this.program?.setAst(newNode); + let res = this.processETSModule(newNode); + if (this.program?.fileNameWithExtension === 'new.ets') { + console.log(res.dumpSrc()) + } return res; } if (isNewCustomDialogController(newNode)) { diff --git a/arkui-plugins/mirror-replace/ui-plugins/customdialog.ts b/arkui-plugins/mirror-replace/ui-plugins/customdialog.ts index 488d18f97..bf3eb9a0e 100644 --- a/arkui-plugins/mirror-replace/ui-plugins/customdialog.ts +++ b/arkui-plugins/mirror-replace/ui-plugins/customdialog.ts @@ -68,54 +68,54 @@ export function createCustomDialogMethod(controller: string): mirrorArkts.Method return mirrorArkts.factory.createMethodDefinition( mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, mirrorArkts.factory.createIdentifier(CustomComponentNames.SETDIALOGCONTROLLER_METHOD), - mirrorArkts.factory.createFunctionExpression(undefined, script), + mirrorArkts.factory.createFunctionExpression(script.id, script), mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, false ); } -// export function transformCallToArrow(value: mirrorArkts.CallExpression): mirrorArkts.ArrowFunctionExpression { -// const className = value.expression.name; -// const args = value.arguments; -// const as_value = mirrorArkts.factory.createExpressionStatement( -// mirrorArkts.factory.updateCallExpression( -// value, -// value.expression, -// value.typeArguments, -// args.length === 0 ? [] : [ -// mirrorArkts.factory.createTSAsExpression( -// args[0], -// mirrorArkts.factory.createTypeReference( -// mirrorArkts.factory.createTypeReferencePart( -// mirrorArkts.factory.createIdentifier(getCustomComponentOptionsName(className)) -// ) -// ), -// false -// ) -// ] -// ) -// ); -// const newValue = mirrorArkts.factory.createArrowFunction( -// factory.createScriptFunction( -// { -// body: mirrorArkts.factory.createBlock([as_value]), -// flags: mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, -// modifiers: mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, -// } -// ) -// ); -// return newValue; -// } +export function transformCallToArrow(value: mirrorArkts.CallExpression): mirrorArkts.ArrowFunctionExpression { + const className = value.callee!.name; + const args = value.arguments; + const as_value = mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.updateCallExpression( + value, + value.callee, + args.length === 0 ? [] : [ + mirrorArkts.factory.createTSAsExpression( + args[0], + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(getCustomComponentOptionsName(className)) + ) + ), + false + ) + ], + value.typeParams, + ) + ); + const newValue = mirrorArkts.factory.createArrowFunctionExpression( + factory.createScriptFunction( + { + body: mirrorArkts.factory.createBlockStatement([as_value]), + flags: mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + modifiers: mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + } + ) + ); + return newValue; +} export function transformController(newInstance: mirrorArkts.ETSNewClassInstanceExpression): mirrorArkts.ETSNewClassInstanceExpression { - const arg = newInstance.getArguments[0]; + const arg = newInstance.arguments[0]; if (!mirrorArkts.isObjectExpression(arg)) { throw new Error('Error CustomDialogOptions'); } const properties = arg.properties as mirrorArkts.Property[]; const property = properties[0]; const value = property?.value; - if (!(value && mirrorArkts.isCallExpression(value) && mirrorArkts.isIdentifier(value.expression))) { + if (!(value && mirrorArkts.isCallExpression(value) && mirrorArkts.isIdentifier(value.callee))) { return newInstance; } @@ -440,7 +440,7 @@ export function isNewCustomDialogController(node: mirrorArkts.AstNode | undefine // for (let i = 0; i < statements.length; i++) { // const statement = statements[i]; // if (mirrorArkts.isVariableDeclaration(statement) && statement.declarators.length > 0 && -// isNewCustomDialogController(statement.declarators[0].initializer)) { +// isNewCustomDialogController(statement.declarators[0].init)) { // const varDeclare = statement.declarators[0]; // const varName = varDeclare.name.name; // const classInstance = varDeclare.initializer; diff --git a/arkui-plugins/mirror-replace/ui-plugins/entry-translators/factory.ts b/arkui-plugins/mirror-replace/ui-plugins/entry-translators/factory.ts index fc87dfc03..f059c2826 100644 --- a/arkui-plugins/mirror-replace/ui-plugins/entry-translators/factory.ts +++ b/arkui-plugins/mirror-replace/ui-plugins/entry-translators/factory.ts @@ -104,7 +104,7 @@ export class factory { const def = mirrorArkts.factory.createMethodDefinition( mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, key, - mirrorArkts.factory.createFunctionExpression(undefined, entryScript), + mirrorArkts.factory.createFunctionExpression(entryScript.id, entryScript), mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, false ); @@ -132,7 +132,7 @@ export class factory { const def = mirrorArkts.factory.createMethodDefinition( mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_CONSTRUCTOR, key, - mirrorArkts.factory.createFunctionExpression(key, entryScript), + mirrorArkts.factory.createFunctionExpression(entryScript.id, entryScript), mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, false ); diff --git a/arkui-plugins/mirror-replace/ui-plugins/index.ts b/arkui-plugins/mirror-replace/ui-plugins/index.ts index afc1ab7ef..a215e9b0c 100644 --- a/arkui-plugins/mirror-replace/ui-plugins/index.ts +++ b/arkui-plugins/mirror-replace/ui-plugins/index.ts @@ -15,6 +15,7 @@ import * as mirrorArkts from '@koalaui/libarkts-mirror'; import { ComponentTransformer } from './component-transformer'; +import { CheckedTransformer } from './checked-transformer'; import { Plugins, PluginContext, ProjectConfig } from '../common/plugin-context'; import { ProgramVisitor } from '../common/program-visitor'; import { EXTERNAL_SOURCE_PREFIX_NAMES } from '../common/predefines'; @@ -158,12 +159,11 @@ function checkedProgramVisit( if (projectConfig && !projectConfig.appResource) { projectConfig.ignoreError = true; } - // const checkedTransformer = new CheckedTransformer(projectConfig); + const checkedTransformer = new CheckedTransformer(projectConfig); const programVisitor = new ProgramVisitor({ pluginName: uiTransform.name, state: mirrorArkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, - // visitors: [checkedTransformer], - visitors: [], + visitors: [checkedTransformer], skipPrefixNames: EXTERNAL_SOURCE_PREFIX_NAMES, pluginContext: context, }); diff --git a/arkui-plugins/mirror-replace/ui-plugins/interop/interop.ts b/arkui-plugins/mirror-replace/ui-plugins/interop/interop.ts index 2490c575f..a1e05b2e2 100644 --- a/arkui-plugins/mirror-replace/ui-plugins/interop/interop.ts +++ b/arkui-plugins/mirror-replace/ui-plugins/interop/interop.ts @@ -235,52 +235,52 @@ import { InteropContext } from '../component-transformer'; // } -// function createWrapperBlock(context: InteropContext, varMap: Map, -// updateProp: mirrorArkts.Property[]): mirrorArkts.BlockStatement { -// const enableStateManagementInterop = false; -// const className: string = context.className; -// const path: string = context.path; -// const args: mirrorArkts.ObjectExpression | undefined = context.arguments; -// const index: number = path.indexOf('/'); -// if (index === -1) { -// throw new Error('Error path of Legacy Component.'); -// } -// const initial = [ -// createGlobal(), -// createEmptyESValue(InteroperAbilityNames.PARAM), -// ...(enableStateManagementInterop ? createProvideInterop() : []) -// ]; -// const initialArgsStatement = args ? initialArgs(args, varMap, updateProp) : []; -// return mirrorArkts.factory.createBlock( -// [ -// ...initial, -// ...initialArgsStatement, -// ...createExtraInfo(['page'], [path]), -// ...createELMTID(), -// ...createComponent(className), -// ...(enableStateManagementInterop ? setAndResetFindProvide() : []), -// createInitReturn(className) -// ] -// ); -// } +function createWrapperBlock(context: InteropContext, varMap: Map, + updateProp: mirrorArkts.Property[]): mirrorArkts.BlockStatement { + const enableStateManagementInterop = false; + const className: string = context.className; + const path: string = context.path; + const args: mirrorArkts.ObjectExpression | undefined = context.arguments; + const index: number = path.indexOf('/'); + if (index === -1) { + throw new Error('Error path of Legacy Component.'); + } + const initial = [ + createGlobal(), + createEmptyESValue(InteroperAbilityNames.PARAM), + ...(enableStateManagementInterop ? createProvideInterop() : []) + ]; + const initialArgsStatement = args ? initialArgs(args, varMap, updateProp) : []; + return mirrorArkts.factory.createBlock( + [ + ...initial, + ...initialArgsStatement, + ...createExtraInfo(['page'], [path]), + ...createELMTID(), + ...createComponent(className), + ...(enableStateManagementInterop ? setAndResetFindProvide() : []), + createInitReturn(className) + ] + ); +} -// function createInitializer(context: InteropContext, varMap: Map, -// updateProp: mirrorArkts.Property[]): mirrorArkts.ArrowFunctionExpression { -// const block = createWrapperBlock(context, varMap, updateProp); -// return mirrorArkts.factory.createArrowFunction( -// mirrorArkts.factory.createScriptFunction( -// block, -// mirrorArkts.factory.createFunctionSignature( -// undefined, -// [], -// undefined, -// false, -// ), -// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, -// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, -// ) -// ); -// } +function createInitializer(context: InteropContext, varMap: Map, + updateProp: mirrorArkts.Property[]): mirrorArkts.ArrowFunctionExpression { + const block = createWrapperBlock(context, varMap, updateProp); + return mirrorArkts.factory.createArrowFunction( + mirrorArkts.factory.createScriptFunction( + block, + mirrorArkts.factory.createFunctionSignature( + undefined, + [], + undefined, + false, + ), + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + ) + ); +} // function createUpdateProp(updateProp: mirrorArkts.Property[]): mirrorArkts.Statement[] { // const result: mirrorArkts.Statement[] = []; @@ -316,54 +316,54 @@ import { InteropContext } from '../component-transformer'; // ]; // } -// function createUpdater(updateProp: mirrorArkts.Property[]): mirrorArkts.ArrowFunctionExpression { -// const updateState = (updateProp.length !== 0) ? updateStateVars(updateProp) : []; -// return mirrorArkts.factory.createArrowFunction( -// mirrorArkts.factory.createScriptFunction( -// mirrorArkts.factory.createBlock( -// [ -// ...updateState -// ] -// ), -// mirrorArkts.factory.createFunctionSignature( -// undefined, -// [ -// mirrorArkts.factory.createParameterDeclaration( -// mirrorArkts.factory.createIdentifier(InteroperAbilityNames.INSTANCE, -// mirrorArkts.factory.createTypeReference( -// mirrorArkts.factory.createTypeReferencePart( -// mirrorArkts.factory.createIdentifier(ESValueMethodNames.ESVALUE) -// ) -// ) -// ), -// undefined, -// ), -// ], -// undefined, -// false, -// ), -// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, -// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, -// ) -// ); -// } +function createUpdater(updateProp: mirrorArkts.Property[]): mirrorArkts.ArrowFunctionExpression { + const updateState = (updateProp.length !== 0) ? updateStateVars(updateProp) : []; + return mirrorArkts.factory.createArrowFunction( + mirrorArkts.factory.createScriptFunction( + mirrorArkts.factory.createBlock( + [ + ...updateState + ] + ), + mirrorArkts.factory.createFunctionSignature( + undefined, + [ + mirrorArkts.factory.createParameterDeclaration( + mirrorArkts.factory.createIdentifier(InteroperAbilityNames.INSTANCE, + mirrorArkts.factory.createTypeReference( + mirrorArkts.factory.createTypeReferencePart( + mirrorArkts.factory.createIdentifier(ESValueMethodNames.ESVALUE) + ) + ) + ), + undefined, + ), + ], + undefined, + false, + ), + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + ) + ); +} -// function generateVarMap(node: mirrorArkts.Identifier): Map { -// const decl = mirrorArkts.getDecl(node); -// if (!(decl instanceof mirrorArkts.ClassDefinition)) { -// throw Error("can't find legacy class declaration"); -// } -// const result = new Map(); -// const definition = decl; -// const body = definition.body; -// body.forEach(node => { -// if (node instanceof mirrorArkts.ClassProperty && node.key instanceof mirrorArkts.Identifier) { -// const key = node.key.name; -// result.set(key, node); -// } -// }); -// return result; -// } +function generateVarMap(node: mirrorArkts.Identifier): Map { + const decl = mirrorArkts.getDecl(node); + if (!(decl instanceof mirrorArkts.ClassDefinition)) { + throw Error("can't find legacy class declaration"); + } + const result = new Map(); + const definition = decl; + const body = definition.body; + body.forEach(node => { + if (node instanceof mirrorArkts.ClassProperty && node.key instanceof mirrorArkts.Identifier) { + const key = node.key.name; + result.set(key, node); + } + }); + return result; +} function generateStructInfo(context: InteropContext): mirrorArkts.AstNode[] { const result: mirrorArkts.AstNode[] = [ @@ -420,40 +420,40 @@ export function generateInstantiateInterop(context: InteropContext): mirrorArkts // } -// /** -// * -// * @param node -// * @returns After Checked, transform instantiate_Interop -> ArkUICompatible -// */ -// export function generateArkUICompatible(node: mirrorArkts.CallExpression): mirrorArkts.CallExpression { -// const classInterop = (node.expression as mirrorArkts.MemberExpression).object as mirrorArkts.Identifier; -// const className = classInterop.name; -// const args = node.arguments; -// const path = (args[0] as mirrorArkts.StringLiteral).str; -// const line = args[1] instanceof mirrorArkts.UndefinedLiteral ? undefined : (args[1] as mirrorArkts.NumberLiteral).value; -// const col = args[2] instanceof mirrorArkts.UndefinedLiteral ? undefined : (args[2] as mirrorArkts.NumberLiteral).value; -// const options = args[3] instanceof mirrorArkts.UndefinedLiteral ? undefined : args[3] as mirrorArkts.ObjectExpression; -// const context: InteropContext = { -// className: className, -// path: path, -// line: line, -// col: col, -// arguments: options -// }; +/** + * + * @param node + * @returns After Checked, transform instantiate_Interop -> ArkUICompatible + */ +export function generateArkUICompatible(node: mirrorArkts.CallExpression): mirrorArkts.CallExpression { + const classInterop = (node.callee as mirrorArkts.MemberExpression).object as mirrorArkts.Identifier; + const className = classInterop.name; + const args = node.arguments; + const path = (args[0] as mirrorArkts.StringLiteral).str; + const line = args[1] instanceof mirrorArkts.UndefinedLiteral ? undefined : (args[1] as mirrorArkts.NumberLiteral).value; + const col = args[2] instanceof mirrorArkts.UndefinedLiteral ? undefined : (args[2] as mirrorArkts.NumberLiteral).value; + const options = args[3] instanceof mirrorArkts.UndefinedLiteral ? undefined : args[3] as mirrorArkts.ObjectExpression; + const context: InteropContext = { + className: className, + path: path, + line: line, + col: col, + arguments: options + }; -// const varMap: Map = generateVarMap(classInterop); -// const updateProp:mirrorArkts.Property[] = []; -// const initializer = createInitializer(context, varMap, updateProp); -// const updater = createUpdater(updateProp); -// const result = mirrorArkts.factory.updateCallExpression( -// node, -// mirrorArkts.factory.createIdentifier(InteroperAbilityNames.ARKUICOMPATIBLE), -// undefined, -// [ -// initializer, -// updater, -// ] -// ); -// mirrorArkts.NodeCache.getInstance().collect(result); -// return result; -// } \ No newline at end of file + const varMap: Map = generateVarMap(classInterop); + const updateProp:mirrorArkts.Property[] = []; + const initializer = createInitializer(context, varMap, updateProp); + const updater = createUpdater(updateProp); + const result = mirrorArkts.factory.updateCallExpression( + node, + mirrorArkts.factory.createIdentifier(InteroperAbilityNames.ARKUICOMPATIBLE), + undefined, + [ + initializer, + updater, + ] + ); + mirrorArkts.NodeCache.getInstance().collect(result); + return result; +} \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/base.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/base.ts new file mode 100644 index 000000000..6baf8b8db --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/base.ts @@ -0,0 +1,87 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { + collectStateManagementTypeImport, + createGetter, + createSetter, +} from './utils'; +import { CustomComponentInfo } from '../utils'; +import { StateManagementTypes } from '../../common/predefines'; + +export interface PropertyTranslatorOptions { + property: mirrorArkts.ClassProperty; + structInfo: CustomComponentInfo; +} + +export abstract class PropertyTranslator { + protected property: mirrorArkts.ClassProperty; + protected structInfo: CustomComponentInfo; + + constructor(options: PropertyTranslatorOptions) { + this.property = options.property; + this.structInfo = options.structInfo; + } + + abstract translateMember(): mirrorArkts.AstNode[]; + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.MemberExpression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + left: mirrorArkts.MemberExpression + ): mirrorArkts.MethodDefinition { + const right: mirrorArkts.CallExpression = mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createIdentifier(StateManagementTypes.OBSERVABLE_PROXY), + [mirrorArkts.factory.createIdentifier('value')], + undefined, + ); + collectStateManagementTypeImport(StateManagementTypes.OBSERVABLE_PROXY); + return createSetter(originalName, typeAnnotation, left, right); + } +} + +export type InterfacePropertyTypes = mirrorArkts.MethodDefinition | mirrorArkts.ClassProperty; + +export interface InterfacePropertyTranslatorOptions { + property: T; +} + +export abstract class InterfacePropertyTranslator + implements InterfacePropertyTranslatorOptions +{ + property: T; + + modified: boolean; + + constructor(options: InterfacePropertyTranslatorOptions) { + this.property = options.property; + this.modified = false; + } + + abstract translateProperty(): T; + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + return false; + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/builderParam.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/builderParam.ts new file mode 100644 index 000000000..a4c081f14 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/builderParam.ts @@ -0,0 +1,171 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames } from '../../common/predefines'; +import { createGetter, createSetter, generateThisBacking, hasDecorator, removeDecorator, PropertyCache } from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; +import { addMemoAnnotation, findCanAddMemoFromTypeAnnotation } from '../../collectors/memo-collectors/utils'; + +export class BuilderParamTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const mutableThis: mirrorArkts.Expression = generateThisBacking(newName); + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(mutableThis, originalName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const propertyType = this.property.typeAnnotation; + if (!!propertyType && (mirrorArkts.isETSFunctionType(propertyType) || mirrorArkts.isETSUnionType(propertyType))) { + addMemoAnnotation(propertyType); + } + const field: mirrorArkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + undefined, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + true + ); + mirrorArkts.AstNodeCache.getInstance().collect(field); + const thisSetValue: mirrorArkts.Expression = generateThisBacking(newName, false, false); + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + propertyType?.clone(), + mirrorArkts.hasModifierFlag(this.property, mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OPTIONAL) + ? generateThisBacking(newName, false, false) + : generateThisBacking(newName, false, true) + ); + mirrorArkts.AstNodeCache.getInstance().collect(getter); + const setter: mirrorArkts.MethodDefinition = this.translateSetter(originalName, propertyType?.clone(), thisSetValue); + mirrorArkts.AstNodeCache.getInstance().collect(setter); + + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue, true); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + left: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + const right: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier('value'); + return createSetter(originalName, typeAnnotation, left, right, true); + } + + generateInitializeStruct(mutableThis: mirrorArkts.Expression, originalName: string): mirrorArkts.AstNode { + return mirrorArkts.factory.createAssignmentExpression( + mutableThis, + mirrorArkts.factory.createBinaryExpression( + mirrorArkts.factory.createBinaryExpression( + factory.createBlockStatementForOptionalExpression( + mirrorArkts.factory.createIdentifier('initializers'), + originalName + ), + mirrorArkts.factory.createIdentifier('content'), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING + ), + this.property.value ?? mirrorArkts.factory.createUndefinedLiteral(), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING + ), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + ); + } +} + +export class BuilderParamInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (mirrorArkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateBuilderParamMethodInInterface(this.property) as T; + } else if (mirrorArkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateBuilderParamPropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + if (mirrorArkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.BUILDER_PARAM)) { + return true; + } else if (mirrorArkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.BUILDER_PARAM)) { + return true; + } + return false; + } + + /** + * Add `@memo` to getter's return type and setter's param type (expecting a function type or a function type within a union type). + * + * @param method expecting getter with `@BuilderParam` and a setter with `@BuilderParam` in the overloads. + */ + private updateBuilderParamMethodInInterface(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + if (method.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET) { + const type: mirrorArkts.TypeNode | undefined = method.function!.returnTypeAnnotation; + if (!!type && (mirrorArkts.isETSFunctionType(type) || mirrorArkts.isETSUnionType(type))) { + addMemoAnnotation(type); + } + const newOverLoads = method.overloads.map((overload) => { + if (mirrorArkts.isMethodDefinition(overload)) { + return this.updateBuilderParamMethodInInterface(overload); + } + return overload; + }); + method.setOverloads(newOverLoads); + removeDecorator(method, DecoratorNames.BUILDER_PARAM); + mirrorArkts.AstNodeCache.getInstance().collect(method, { isGetter: true }); + } else if (method.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET) { + const param = method.function!.params.at(0)! as mirrorArkts.ETSParameterExpression; + const type = param.typeAnnotation; + if (!!type && (mirrorArkts.isETSFunctionType(type) || mirrorArkts.isETSUnionType(type))) { + addMemoAnnotation(type); + } + removeDecorator(method, DecoratorNames.BUILDER_PARAM); + mirrorArkts.AstNodeCache.getInstance().collect(method, { isSetter: true }); + } + return method; + } + + /** + * Add `@memo` to the type of the property (expecting a function type or a function type within a union type). + * + * @param property expecting property with `@BuilderParam`. + */ + private updateBuilderParamPropertyInInterface(property: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + const type: mirrorArkts.TypeNode | undefined = property.typeAnnotation; + if (findCanAddMemoFromTypeAnnotation(type)) { + addMemoAnnotation(type); + } + removeDecorator(property, DecoratorNames.BUILDER_PARAM); + return property; + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/consume.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/consume.ts new file mode 100644 index 000000000..32484be58 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/consume.ts @@ -0,0 +1,155 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { + generateToRecord, + createGetter, + createSetter2, + generateThisBacking, + generateGetOrSetCall, + getValueInAnnotation, + hasDecorator, + PropertyCache, +} from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; + +export class ConsumeTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(originalName, newName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + if (!!this.structInfo.annotations?.reusable) { + const toRecord = generateToRecord(newName, originalName); + PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); + } + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const field: mirrorArkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.CONSUME_DECORATED, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + const thisValue: mirrorArkts.Expression = generateThisBacking(newName, false, true); + const thisGet: mirrorArkts.CallExpression = generateGetOrSetCall(thisValue, GetSetTypes.GET); + const thisSet: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( + generateGetOrSetCall(thisValue, GetSetTypes.SET) + ); + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + const setter: mirrorArkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + statement: mirrorArkts.AstNode + ): mirrorArkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } + + generateInitializeStruct(originalName: string, newName: string): mirrorArkts.AstNode { + const args: mirrorArkts.Expression[] = [ + mirrorArkts.factory.createStringLiteral(originalName), + mirrorArkts.factory.createStringLiteral( + getValueInAnnotation(this.property, DecoratorNames.CONSUME) ?? originalName + ), + ]; + factory.judgeIfAddWatchFunc(args, this.property); + const assign: mirrorArkts.AssignmentExpression = mirrorArkts.factory.createAssignmentExpression( + generateThisBacking(newName), + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_CONSUME, + this.property.typeAnnotation, + args, + true + ), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + ); + return mirrorArkts.factory.createExpressionStatement(assign); + } +} + +export class ConsumeInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (mirrorArkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (mirrorArkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + if (mirrorArkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.CONSUME)) { + return true; + } else if (mirrorArkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.CONSUME)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `ConsumeDecoratedVariable | undefined`. + * + * @param method expecting getter with `@Consume` and a setter with `@Consume` in the overloads. + */ + private updateStateMethodInInterface(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.CONSUME); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `ConsumeDecoratedVariable | undefined`. + * + * @param property expecting property with `@Consume`. + */ + private updateStatePropertyInInterface(property: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.CONSUME); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/factory.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/factory.ts index f5c75220c..d5cf40e11 100644 --- a/arkui-plugins/mirror-replace/ui-plugins/property-translators/factory.ts +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/factory.ts @@ -14,82 +14,85 @@ */ import * as mirrorArkts from '@koalaui/libarkts-mirror'; +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 { CustomComponentNames } from '../utils'; import { addMemoAnnotation, findCanAddMemoFromTypeAnnotation } from '../../collectors/memo-collectors/utils'; -import { collectStateManagementTypeImport } from './utils'; +import { annotation } from '../../common/arkts-utils'; export class factory { - // /** - // * generate an substitution for optional expression ?., e.g. `{let _tmp = xxx; _tmp == null ? undefined : xxx}`. - // * - // * @param object item before ?.. - // * @param key item after ?.. - // */ - // static createBlockStatementForOptionalExpression( - // object: mirrorArkts.AstNode, - // key: string, - // isCall: boolean = false - // ): mirrorArkts.Expression { - // let id = GenSymGenerator.getInstance().id(key); - // const statements: mirrorArkts.Statement[] = [ - // factory.generateLetVariableDecl(mirrorArkts.factory.createIdentifier(id), object), - // factory.generateTernaryExpression(id, key, isCall), - // ]; - // return mirrorArkts.factory.createBlockExpression(statements); - // } + /** + * generate an substitution for optional expression ?., e.g. `{let _tmp = xxx; _tmp == null ? undefined : xxx}`. + * + * @param object item before ?.. + * @param key item after ?.. + */ + static createBlockStatementForOptionalExpression( + object: mirrorArkts.AstNode, + key: string, + isCall: boolean = false + ): mirrorArkts.Expression { + let id = GenSymGenerator.getInstance().id(key); + const statements: mirrorArkts.Statement[] = [ + factory.generateLetVariableDecl(mirrorArkts.factory.createIdentifier(id), object), + factory.generateTernaryExpression(id, key, isCall), + ]; + return mirrorArkts.factory.createBlockExpression(statements); + } - // /** - // * generate a variable declaration, e.g. `let = `; - // * - // * @param left left expression. - // * @param right right expression. - // */ - // static generateLetVariableDecl(left: mirrorArkts.Identifier, right: mirrorArkts.AstNode): mirrorArkts.VariableDeclaration { - // return mirrorArkts.factory.createVariableDeclaration( - // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, - // mirrorArkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET, - // [ - // mirrorArkts.factory.createVariableDeclarator( - // mirrorArkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET, - // left, - // right - // ), - // ] - // ); - // } + /** + * generate a variable declaration, e.g. `let = `; + * + * @param left left expression. + * @param right right expression. + */ + static generateLetVariableDecl(left: mirrorArkts.Identifier, right: mirrorArkts.AstNode): mirrorArkts.VariableDeclaration { + return mirrorArkts.factory.createVariableDeclaration( + mirrorArkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET, + [ + mirrorArkts.factory.createVariableDeclarator( + mirrorArkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET, + left, + right as mirrorArkts.Expression + ), + ] + ); + } - // /** - // * generate a ternary expression, e.g. ` ? : `; - // * - // * @param testLeft the left hand of the test condition. - // * @param key item after ?. - // */ - // static generateTernaryExpression( - // testLeft: string, - // key: string, - // isCall: boolean = false - // ): mirrorArkts.ExpressionStatement { - // const test = mirrorArkts.factory.createBinaryExpression( - // mirrorArkts.factory.createIdentifier(testLeft), - // mirrorArkts.factory.createNullLiteral(), - // mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_EQUAL - // ); - // const consequent: mirrorArkts.Expression = mirrorArkts.factory.createUndefinedLiteral(); - // const alternate: mirrorArkts.MemberExpression = mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createIdentifier(testLeft), - // mirrorArkts.factory.createIdentifier(key), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // false - // ); - // return mirrorArkts.factory.createExpressionStatement( - // mirrorArkts.factory.createConditionalExpression( - // test, - // consequent, - // isCall ? mirrorArkts.factory.createCallExpression(alternate, undefined, undefined) : alternate - // ) - // ); - // } + /** + * generate a ternary expression, e.g. ` ? : `; + * + * @param testLeft the left hand of the test condition. + * @param key item after ?. + */ + static generateTernaryExpression( + testLeft: string, + key: string, + isCall: boolean = false + ): mirrorArkts.ExpressionStatement { + const test = mirrorArkts.factory.createBinaryExpression( + mirrorArkts.factory.createIdentifier(testLeft), + mirrorArkts.factory.createNullLiteral(), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_EQUAL + ); + const consequent: mirrorArkts.Expression = mirrorArkts.factory.createUndefinedLiteral(); + const alternate: mirrorArkts.MemberExpression = mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createIdentifier(testLeft), + mirrorArkts.factory.createIdentifier(key), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); + return mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createConditionalExpression( + test, + consequent, + isCall ? mirrorArkts.factory.createCallExpression(alternate, [], undefined) : alternate + ) + ); + } // /** // * generate an substitution for two optional expression ?., e.g. a?.b?.c. @@ -110,85 +113,91 @@ export class factory { // return mirrorArkts.factory.createBlockExpression(statements); // } - // /** - // * generate an memberExpression with nonNull or optional, e.g. object.property, object?.property or object!.property - // * - // * @param object item before point. - // * @param property item after point. - // */ - // static createNonNullOrOptionalMemberExpression( - // object: string, - // property: string, - // optional: boolean, - // nonNull: boolean - // ): mirrorArkts.Expression { - // const objectNode: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier(object); - // return mirrorArkts.factory.createMemberExpression( - // nonNull ? mirrorArkts.factory.createTSNonNullExpression(objectNode) : objectNode, - // mirrorArkts.factory.createIdentifier(property), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // optional - // ); - // } + /** + * generate an memberExpression with nonNull or optional, e.g. object.property, object?.property or object!.property + * + * @param object item before point. + * @param property item after point. + */ + static createNonNullOrOptionalMemberExpression( + object: string, + property: string, + optional: boolean, + nonNull: boolean + ): mirrorArkts.Expression { + const objectNode: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier(object); + return mirrorArkts.factory.createMemberExpression( + nonNull ? mirrorArkts.factory.createTSNonNullExpression(objectNode) : objectNode, + mirrorArkts.factory.createIdentifier(property), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + optional + ); + } - // /* - // * create `(): => { }`. - // */ - // static createArrowFunctionWithParamsAndBody( - // typeParams: mirrorArkts.TSTypeParameterDeclaration | undefined, - // params: mirrorArkts.Expression[] | undefined, - // returnType: mirrorArkts.TypeNode | undefined, - // hasReceiver: boolean, - // bodyStatementsList: mirrorArkts.Statement[] - // ): mirrorArkts.ArrowFunctionExpression { - // return mirrorArkts.factory.createArrowFunction( - // mirrorArkts.factory.createScriptFunction( - // mirrorArkts.BlockStatement.createBlockStatement(bodyStatementsList), - // mirrorArkts.factory.createFunctionSignature(typeParams, params ? params : [], returnType, hasReceiver), - // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, - // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE - // ) - // ); - // } + /* + * create `(): => { }`. + */ + static createArrowFunctionWithParamsAndBody( + typeParams: mirrorArkts.TSTypeParameterDeclaration | undefined, + params: mirrorArkts.Expression[] | undefined, + returnType: mirrorArkts.TypeNode | undefined, + hasReceiver: boolean, + bodyStatementsList: mirrorArkts.Statement[] + ): mirrorArkts.ArrowFunctionExpression { + return mirrorArkts.factory.createArrowFunctionExpression( + mirrorArkts.factory.createScriptFunction( + mirrorArkts.BlockStatement.createBlockStatement(bodyStatementsList), + typeParams, + params ? params : [], + returnType, + hasReceiver, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + undefined, + undefined + ) + ); + } - // /* - // * create @Watch callback, e.g. (propertyName: string): void => {this.(propertyName)}. - // */ - // static createWatchCallback(callbackName: string): mirrorArkts.ArrowFunctionExpression { - // return factory.createArrowFunctionWithParamsAndBody( - // undefined, - // [ - // mirrorArkts.factory.createParameterDeclaration( - // mirrorArkts.factory.createIdentifier('_', UIFactory.createTypeReferenceFromString('string')), - // undefined - // ), - // ], - // mirrorArkts.factory.createPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), - // false, - // [ - // mirrorArkts.factory.createExpressionStatement( - // mirrorArkts.factory.createCallExpression(factory.generateThisCall(callbackName), undefined, [ - // mirrorArkts.factory.createIdentifier('_'), - // ]) - // ), - // ] - // ); - // } + /* + * create @Watch callback, e.g. (propertyName: string): void => {this.(propertyName)}. + */ + static createWatchCallback(callbackName: string): mirrorArkts.ArrowFunctionExpression { + return factory.createArrowFunctionWithParamsAndBody( + undefined, + [ + mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier('_', UIFactory.createTypeReferenceFromString('string')), + false + ), + ], + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + [ + mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createCallExpression( + factory.generateThisCall(callbackName), + [mirrorArkts.factory.createIdentifier('_')], + undefined) + ), + ] + ); + } - // /* - // * create this. with optional or nonNullable. - // */ - // static generateThisCall(name: string, optional: boolean = false, nonNull: boolean = false): mirrorArkts.Expression { - // const member: mirrorArkts.Expression = mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createThisExpression(), - // mirrorArkts.factory.createIdentifier(`${name}`), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // optional - // ); - // return nonNull ? mirrorArkts.factory.createTSNonNullExpression(member) : member; - // } + /* + * create this. with optional or nonNullable. + */ + static generateThisCall(name: string, optional: boolean = false, nonNull: boolean = false): mirrorArkts.Expression { + const member: mirrorArkts.Expression = mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier(`${name}`), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + optional + ); + return nonNull ? mirrorArkts.factory.createTSNonNullExpression(member) : member; + } // /* // * create `initializers!.!.()`. @@ -232,61 +241,63 @@ export class factory { // ); // } - // /* - // * create `StateMgmtFactory.(this, ...);`. - // */ - // static generateStateMgmtFactoryCall( - // makeType: StateManagementTypes, - // typeArguments: mirrorArkts.TypeNode | undefined, - // args: mirrorArkts.AstNode[], - // argsContainsThis: boolean - // ): mirrorArkts.CallExpression { - // collectStateManagementTypeImport(StateManagementTypes.STATE_MANAGEMENT_FACTORY); - // return mirrorArkts.factory.createCallExpression( - // mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createIdentifier(StateManagementTypes.STATE_MANAGEMENT_FACTORY), - // mirrorArkts.factory.createIdentifier(makeType), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // false - // ), - // typeArguments ? [typeArguments] : undefined, - // [...(argsContainsThis ? [mirrorArkts.factory.createThisExpression()] : []), ...args] - // ); - // } + /* + * create `StateMgmtFactory.(this, ...);`. + */ + static generateStateMgmtFactoryCall( + makeType: StateManagementTypes, + typeArguments: mirrorArkts.TypeNode | undefined, + args: mirrorArkts.AstNode[], + argsContainsThis: boolean + ): mirrorArkts.CallExpression { + collectStateManagementTypeImport(StateManagementTypes.STATE_MANAGEMENT_FACTORY); + let parameterInstantiation = typeArguments ? mirrorArkts.TSTypeParameterInstantiation.createTSTypeParameterInstantiation([typeArguments]) + : undefined + return mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createIdentifier(StateManagementTypes.STATE_MANAGEMENT_FACTORY), + mirrorArkts.factory.createIdentifier(makeType), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + [...(argsContainsThis ? [mirrorArkts.factory.createThisExpression()] : []), ...args as mirrorArkts.Expression[]], + parameterInstantiation, + ); + } - // /* - // * create if statement in __updateStruct method. - // */ - // static createIfInUpdateStruct( - // originalName: string, - // member: mirrorArkts.Expression, - // args: mirrorArkts.AstNode[] - // ): mirrorArkts.IfStatement { - // const binaryItem = mirrorArkts.factory.createBinaryExpression( - // factory.createBlockStatementForOptionalExpression( - // mirrorArkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME), - // originalName - // ), - // mirrorArkts.factory.createUndefinedLiteral(), - // mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_STRICT_EQUAL - // ); - // return mirrorArkts.factory.createIfStatement( - // binaryItem, - // mirrorArkts.factory.createBlock([ - // mirrorArkts.factory.createExpressionStatement(mirrorArkts.factory.createCallExpression(member, undefined, args)), - // ]) - // ); - // } + /* + * create if statement in __updateStruct method. + */ + static createIfInUpdateStruct( + originalName: string, + member: mirrorArkts.Expression, + args: mirrorArkts.AstNode[] + ): mirrorArkts.IfStatement { + const binaryItem = mirrorArkts.factory.createBinaryExpression( + factory.createBlockStatementForOptionalExpression( + mirrorArkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME), + originalName + ), + mirrorArkts.factory.createUndefinedLiteral(), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_STRICT_EQUAL + ); + return mirrorArkts.factory.createIfStatement( + binaryItem, + mirrorArkts.factory.createBlockStatement([ + mirrorArkts.factory.createExpressionStatement(mirrorArkts.factory.createCallExpression(member, args as mirrorArkts.Expression[], undefined)), + ]) + ); + } - // static judgeIfAddWatchFunc(args: mirrorArkts.Expression[], property: mirrorArkts.ClassProperty): void { - // if (hasDecorator(property, DecoratorNames.WATCH)) { - // const watchStr: string | undefined = getValueInAnnotation(property, DecoratorNames.WATCH); - // if (watchStr) { - // args.push(factory.createWatchCallback(watchStr)); - // } - // } - // } + static judgeIfAddWatchFunc(args: mirrorArkts.Expression[], property: mirrorArkts.ClassProperty): void { + if (hasDecorator(property, DecoratorNames.WATCH)) { + const watchStr: string | undefined = getValueInAnnotation(property, DecoratorNames.WATCH); + if (watchStr) { + args.push(factory.createWatchCallback(watchStr)); + } + } + } static createOptionalClassProperty( name: string, @@ -324,476 +335,478 @@ export class factory { ); } - // /* - // * create watch related members in Observed/Track classes - // */ - // static createWatchMembers(): mirrorArkts.AstNode[] { - // const subscribedWatches: mirrorArkts.ClassProperty = mirrorArkts.factory.createClassProperty( - // mirrorArkts.factory.createIdentifier('subscribedWatches'), - // factory.generateStateMgmtFactoryCall(StateManagementTypes.MAKE_SUBSCRIBED_WATCHES, undefined, [], false), - // mirrorArkts.factory.createTypeReference( - // mirrorArkts.factory.createTypeReferencePart( - // mirrorArkts.factory.createIdentifier(StateManagementTypes.SUBSCRIBED_WATCHES) - // ) - // ), - // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, - // false - // ); - // subscribedWatches.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); - // collectStateManagementTypeImport(StateManagementTypes.SUBSCRIBED_WATCHES); - - // const addWatchSubscriber = factory.createWatchMethod( - // 'addWatchSubscriber', - // mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID, - // 'watchId', - // StateManagementTypes.WATCH_ID_TYPE, - // false - // ); - // const removeWatchSubscriber = factory.createWatchMethod( - // 'removeWatchSubscriber', - // mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_BOOLEAN, - // 'watchId', - // StateManagementTypes.WATCH_ID_TYPE, - // true - // ); - // collectStateManagementTypeImport(StateManagementTypes.WATCH_ID_TYPE); - - // const executeOnSubscribingWatches = factory.createWatchMethod( - // 'executeOnSubscribingWatches', - // mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID, - // 'propertyName', - // 'string', - // false - // ); + /* + * create watch related members in Observed/Track classes + */ + static createWatchMembers(): mirrorArkts.AstNode[] { + const subscribedWatches: mirrorArkts.ClassProperty = mirrorArkts.factory.createClassProperty( + mirrorArkts.factory.createIdentifier('subscribedWatches'), + factory.generateStateMgmtFactoryCall(StateManagementTypes.MAKE_SUBSCRIBED_WATCHES, undefined, [], false), + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(StateManagementTypes.SUBSCRIBED_WATCHES) + ) + ), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + false + ); + subscribedWatches.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); + collectStateManagementTypeImport(StateManagementTypes.SUBSCRIBED_WATCHES); + + const addWatchSubscriber = factory.createWatchMethod( + 'addWatchSubscriber', + mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID, + 'watchId', + StateManagementTypes.WATCH_ID_TYPE, + false + ); + const removeWatchSubscriber = factory.createWatchMethod( + 'removeWatchSubscriber', + mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_BOOLEAN, + 'watchId', + StateManagementTypes.WATCH_ID_TYPE, + true + ); + collectStateManagementTypeImport(StateManagementTypes.WATCH_ID_TYPE); - // return [subscribedWatches, addWatchSubscriber, removeWatchSubscriber, executeOnSubscribingWatches]; - // } + const executeOnSubscribingWatches = factory.createWatchMethod( + 'executeOnSubscribingWatches', + mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID, + 'propertyName', + 'string', + false + ); - // /* - // * helper for createWatchMembers to create watch methods - // */ - // static createWatchMethod( - // methodName: string, - // returnType: mirrorArkts.Es2pandaPrimitiveType, - // paramName: string, - // paramType: string, - // isReturnStatement: boolean - // ): mirrorArkts.MethodDefinition { - // return mirrorArkts.factory.createMethodDefinition( - // mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, - // mirrorArkts.factory.createIdentifier(methodName), - // mirrorArkts.factory.createScriptFunction( - // mirrorArkts.factory.createBlock([ - // isReturnStatement - // ? mirrorArkts.factory.createReturnStatement( - // mirrorArkts.factory.createCallExpression( - // factory.thisSubscribedWatchesMember(methodName), - // undefined, - // [mirrorArkts.factory.createIdentifier(paramName)] - // ) - // ) - // : mirrorArkts.factory.createExpressionStatement( - // mirrorArkts.factory.createCallExpression( - // factory.thisSubscribedWatchesMember(methodName), - // undefined, - // [mirrorArkts.factory.createIdentifier(paramName)] - // ) - // ), - // ]), - // mirrorArkts.factory.createFunctionSignature( - // undefined, - // [ - // mirrorArkts.factory.createParameterDeclaration( - // mirrorArkts.factory.createIdentifier( - // paramName, - // mirrorArkts.factory.createTypeReference( - // mirrorArkts.factory.createTypeReferencePart(mirrorArkts.factory.createIdentifier(paramType)) - // ) - // ), - // undefined - // ), - // ], - // mirrorArkts.factory.createPrimitiveType(returnType), - // false - // ), - // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, - // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC - // ), - // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, - // false - // ); - // } + return [subscribedWatches, addWatchSubscriber, removeWatchSubscriber, executeOnSubscribingWatches]; + } - // /* - // * helper for createWatchMethod, generates this.subscribedWatches.xxx - // */ - // static thisSubscribedWatchesMember(member: string): mirrorArkts.MemberExpression { - // return mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createThisExpression(), - // mirrorArkts.factory.createIdentifier('subscribedWatches'), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // false - // ), - // mirrorArkts.factory.createIdentifier(member), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // false - // ); - // } + /* + * helper for createWatchMembers to create watch methods + */ + static createWatchMethod( + methodName: string, + returnType: mirrorArkts.Es2pandaPrimitiveType, + paramName: string, + paramType: string, + isReturnStatement: boolean + ): mirrorArkts.MethodDefinition { + const script = mirrorArkts.factory.createScriptFunction( + mirrorArkts.factory.createBlockStatement([ + isReturnStatement + ? mirrorArkts.factory.createReturnStatement( + mirrorArkts.factory.createCallExpression( + factory.thisSubscribedWatchesMember(methodName), + [mirrorArkts.factory.createIdentifier(paramName)], + undefined + ) + ) + : mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createCallExpression( + factory.thisSubscribedWatchesMember(methodName), + [mirrorArkts.factory.createIdentifier(paramName)], + undefined, + ) + ), + ]), + undefined, + [ + mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier( + paramName, + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart(mirrorArkts.factory.createIdentifier(paramType)) + ) + ), + false + ), + ], + mirrorArkts.factory.createETSPrimitiveType(returnType), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ) + + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + mirrorArkts.factory.createIdentifier(methodName), + mirrorArkts.factory.createFunctionExpression(script.id, script), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); + } - // /* - // * create ____V1RenderId related members in Observed/Track classes - // */ - // static createV1RenderIdMembers(): mirrorArkts.AstNode[] { - // const v1RenderId: mirrorArkts.ClassProperty = mirrorArkts.factory.createClassProperty( - // mirrorArkts.factory.createIdentifier('____V1RenderId'), - // mirrorArkts.factory.createNumericLiteral(0), - // mirrorArkts.factory.createTypeReference( - // mirrorArkts.factory.createTypeReferencePart( - // mirrorArkts.factory.createIdentifier(StateManagementTypes.RENDER_ID_TYPE) - // ) - // ), - // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, - // false - // ); - // v1RenderId.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); - // collectStateManagementTypeImport(StateManagementTypes.RENDER_ID_TYPE); - // const setV1RenderId: mirrorArkts.MethodDefinition = factory.setV1RenderId(); - // return [v1RenderId, setV1RenderId]; - // } + /* + * helper for createWatchMethod, generates this.subscribedWatches.xxx + */ + static thisSubscribedWatchesMember(member: string): mirrorArkts.MemberExpression { + return mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier('subscribedWatches'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + mirrorArkts.factory.createIdentifier(member), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); + } - // /* - // * helper for createV1RenderIdMembers to generate setV1RenderId method - // */ - // static setV1RenderId(): mirrorArkts.MethodDefinition { - // const assignRenderId: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( - // mirrorArkts.factory.createAssignmentExpression( - // mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createThisExpression(), - // mirrorArkts.factory.createIdentifier('____V1RenderId'), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // false - // ), - // mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, - // mirrorArkts.factory.createIdentifier('renderId') - // ) - // ); - // const funcSig: mirrorArkts.FunctionSignature = mirrorArkts.factory.createFunctionSignature( - // undefined, - // [ - // mirrorArkts.factory.createParameterDeclaration( - // mirrorArkts.factory.createIdentifier( - // 'renderId', - // mirrorArkts.factory.createTypeReference( - // mirrorArkts.factory.createTypeReferencePart( - // mirrorArkts.factory.createIdentifier(StateManagementTypes.RENDER_ID_TYPE) - // ) - // ) - // ), - // undefined - // ), - // ], - // mirrorArkts.factory.createPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), - // false - // ); - // return mirrorArkts.factory.createMethodDefinition( - // mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, - // mirrorArkts.factory.createIdentifier('setV1RenderId'), - // mirrorArkts.factory.createScriptFunction( - // mirrorArkts.factory.createBlock([assignRenderId]), - // funcSig, - // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, - // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC - // ), - // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, - // false - // ); - // } + /* + * create ____V1RenderId related members in Observed/Track classes + */ + static createV1RenderIdMembers(): mirrorArkts.AstNode[] { + const v1RenderId: mirrorArkts.ClassProperty = mirrorArkts.factory.createClassProperty( + mirrorArkts.factory.createIdentifier('____V1RenderId'), + mirrorArkts.factory.createNumberLiteral(0), + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(StateManagementTypes.RENDER_ID_TYPE) + ) + ), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + false + ); + v1RenderId.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); + collectStateManagementTypeImport(StateManagementTypes.RENDER_ID_TYPE); + const setV1RenderId: mirrorArkts.MethodDefinition = factory.setV1RenderId(); + return [v1RenderId, setV1RenderId]; + } - // /* - // * create conditionalAddRef method in Observed/Track classes - // */ - // static conditionalAddRef(): mirrorArkts.MethodDefinition { - // const funcSig: mirrorArkts.FunctionSignature = mirrorArkts.factory.createFunctionSignature( - // undefined, - // [ - // mirrorArkts.factory.createParameterDeclaration( - // mirrorArkts.factory.createIdentifier( - // 'meta', - // mirrorArkts.factory.createTypeReference( - // mirrorArkts.factory.createTypeReferencePart( - // mirrorArkts.factory.createIdentifier(StateManagementTypes.MUTABLE_STATE_META) - // ) - // ) - // ), - // undefined - // ), - // ], - // mirrorArkts.factory.createPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), - // false - // ); - // collectStateManagementTypeImport(StateManagementTypes.MUTABLE_STATE_META); - // const shouldAddRef: mirrorArkts.IfStatement = factory.shouldAddRef(); - // return mirrorArkts.factory.createMethodDefinition( - // mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, - // mirrorArkts.factory.createIdentifier('conditionalAddRef'), - // mirrorArkts.factory.createScriptFunction( - // mirrorArkts.factory.createBlock([shouldAddRef]), - // funcSig, - // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, - // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PROTECTED - // ), - // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PROTECTED, - // false - // ); - // } + /* + * helper for createV1RenderIdMembers to generate setV1RenderId method + */ + static setV1RenderId(): mirrorArkts.MethodDefinition { + const assignRenderId: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createAssignmentExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier('____V1RenderId'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + mirrorArkts.factory.createIdentifier('renderId'), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION + ) + ); + const script = mirrorArkts.factory.createScriptFunction( + mirrorArkts.factory.createBlockStatement([assignRenderId]), + undefined, + [ + mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier( + 'renderId', + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(StateManagementTypes.RENDER_ID_TYPE) + ) + ) + ), + false + ), + ], + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ) + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + mirrorArkts.factory.createIdentifier('setV1RenderId'), + mirrorArkts.factory.createFunctionExpression(script.id, script), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); + } - // /* - // * helper for conditionalAddRef to generate shouldAddRef method - // */ - // static shouldAddRef(): mirrorArkts.IfStatement { - // const test: mirrorArkts.CallExpression = mirrorArkts.factory.createCallExpression( - // mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createIdentifier(StateManagementTypes.OBSERVE), - // mirrorArkts.factory.createIdentifier('shouldAddRef'), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // false - // ), - // undefined, - // [ - // mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createThisExpression(), - // mirrorArkts.factory.createIdentifier('____V1RenderId'), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // false - // ), - // ] - // ); - // collectStateManagementTypeImport(StateManagementTypes.OBSERVE); - // const consequent: mirrorArkts.BlockStatement = mirrorArkts.factory.createBlock([ - // mirrorArkts.factory.createExpressionStatement( - // mirrorArkts.factory.createCallExpression( - // mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createIdentifier('meta'), - // mirrorArkts.factory.createIdentifier('addRef'), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // false - // ), - // undefined, - // undefined - // ) - // ), - // ]); - // return mirrorArkts.factory.createIfStatement(test, consequent); - // } + /* + * create conditionalAddRef method in Observed/Track classes + */ + static conditionalAddRef(): mirrorArkts.MethodDefinition { + collectStateManagementTypeImport(StateManagementTypes.MUTABLE_STATE_META); + const shouldAddRef: mirrorArkts.IfStatement = factory.shouldAddRef(); + const script = mirrorArkts.factory.createScriptFunction(mirrorArkts.factory.createBlockStatement([shouldAddRef]), + undefined, + [ + mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier( + 'meta', + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(StateManagementTypes.MUTABLE_STATE_META) + ) + ) + ), + false + ), + ], + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PROTECTED, + undefined, + undefined + ) + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + mirrorArkts.factory.createIdentifier('conditionalAddRef'), + mirrorArkts.factory.createFunctionExpression(script.id, script), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PROTECTED, + false + ); + } - // /* - // * helper to create meta field in classes with only @Observe and no @Track - // */ - // static createMetaInObservedClass(): mirrorArkts.ClassProperty { - // collectStateManagementTypeImport(StateManagementTypes.MUTABLE_STATE_META); - // const meta = mirrorArkts.factory.createClassProperty( - // mirrorArkts.factory.createIdentifier(StateManagementTypes.META), - // factory.generateStateMgmtFactoryCall(StateManagementTypes.MAKE_MUTABLESTATE_META, undefined, [], false), - // mirrorArkts.factory.createTypeReference( - // mirrorArkts.factory.createTypeReferencePart( - // mirrorArkts.factory.createIdentifier(StateManagementTypes.MUTABLE_STATE_META) - // ) - // ), - // mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, - // false - // ); - // meta.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); - // return meta; - // } + /* + * helper for conditionalAddRef to generate shouldAddRef method + */ + static shouldAddRef(): mirrorArkts.IfStatement { + const test: mirrorArkts.CallExpression = mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createIdentifier(StateManagementTypes.OBSERVE), + mirrorArkts.factory.createIdentifier('shouldAddRef'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + [ + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier('____V1RenderId'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + ], + undefined + ); + collectStateManagementTypeImport(StateManagementTypes.OBSERVE); + const consequent: mirrorArkts.BlockStatement = mirrorArkts.factory.createBlockStatement([ + mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createIdentifier('meta'), + mirrorArkts.factory.createIdentifier('addRef'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + [], + undefined + ) + ), + ]); + return mirrorArkts.factory.createIfStatement(test, consequent); + } - // /** - // * add `@memo` to the `@Builder` methods in class. - // */ - // static addMemoToBuilderClassMethod(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { - // if (hasDecorator(method, DecoratorNames.BUILDER)) { - // removeDecorator(method, DecoratorNames.BUILDER); - // addMemoAnnotation(method.scriptFunction); - // } - // return method; - // } + /* + * helper to create meta field in classes with only @Observe and no @Track + */ + static createMetaInObservedClass(): mirrorArkts.ClassProperty { + collectStateManagementTypeImport(StateManagementTypes.MUTABLE_STATE_META); + const meta = mirrorArkts.factory.createClassProperty( + mirrorArkts.factory.createIdentifier(StateManagementTypes.META), + factory.generateStateMgmtFactoryCall(StateManagementTypes.MAKE_MUTABLESTATE_META, undefined, [], false), + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(StateManagementTypes.MUTABLE_STATE_META) + ) + ), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + false + ); + meta.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); + return meta; + } - // static createStorageLinkStateValue( - // property: mirrorArkts.ClassProperty, - // localStorageporpValueStr: string - // ): mirrorArkts.MemberExpression { - // return mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createCallExpression( - // mirrorArkts.factory.createIdentifier(StateManagementTypes.STORAGE_LINK_STATE), - // property.typeAnnotation ? [property.typeAnnotation] : [], - // [ - // mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createThisExpression(), - // mirrorArkts.factory.createIdentifier('_entry_local_storage_'), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // false - // ), - // mirrorArkts.factory.createStringLiteral(localStorageporpValueStr), - // property.value ?? mirrorArkts.factory.createUndefinedLiteral(), - // ] - // ), - // mirrorArkts.factory.createIdentifier('value'), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // false - // ); - // } + /** + * add `@memo` to the `@Builder` methods in class. + */ + static addMemoToBuilderClassMethod(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + if (hasDecorator(method, DecoratorNames.BUILDER)) { + removeDecorator(method, DecoratorNames.BUILDER); + addMemoAnnotation(method.function!); + } + return method; + } - // /** - // * wrap interface non-undefined property type `T` to ``. - // */ - // static wrapInterfacePropertyType(type: mirrorArkts.TypeNode, wrapTypeName: StateManagementTypes): mirrorArkts.TypeNode { - // if (mirrorArkts.isETSUnionType(type)) { - // return mirrorArkts.factory.updateUnionType(type, [ - // mirrorArkts.factory.createTypeReference( - // mirrorArkts.factory.createTypeReferencePart( - // mirrorArkts.factory.createIdentifier(wrapTypeName), - // mirrorArkts.factory.createTSTypeParameterInstantiation([type.types[0]]) - // ) - // ), - // type.types[1], - // ]); - // } - // return type; - // } + static createStorageLinkStateValue( + property: mirrorArkts.ClassProperty, + localStorageporpValueStr: string + ): mirrorArkts.MemberExpression { + return mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createIdentifier(StateManagementTypes.STORAGE_LINK_STATE), + [ + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier('_entry_local_storage_'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + mirrorArkts.factory.createStringLiteral(localStorageporpValueStr), + property.value ?? mirrorArkts.factory.createUndefinedLiteral(), + ], + property.typeAnnotation ? mirrorArkts.TSTypeParameterInstantiation.createTSTypeParameterInstantiation([property.typeAnnotation]) : undefined + ), + mirrorArkts.factory.createIdentifier('value'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); + } - // /** - // * wrap interface property parameter that has non-undefined type `T` to ``. - // */ - // static wrapInterfacePropertyParamExpr( - // param: mirrorArkts.Expression, - // wrapTypeName: StateManagementTypes - // ): mirrorArkts.Expression { - // if (!mirrorArkts.isEtsParameterExpression(param)) { - // return param; - // } - // if (!param.type || !mirrorArkts.isETSUnionType(param.type)) { - // return param; - // } - // return mirrorArkts.factory.updateParameterDeclaration( - // param, - // mirrorArkts.factory.createIdentifier( - // param.identifier.name, - // factory.wrapInterfacePropertyType(param.type, wrapTypeName) - // ), - // param.initializer - // ); - // } + /** + * wrap interface non-undefined property type `T` to ``. + */ + static wrapInterfacePropertyType(type: mirrorArkts.TypeNode, wrapTypeName: StateManagementTypes): mirrorArkts.TypeNode { + if (mirrorArkts.isETSUnionType(type)) { + return mirrorArkts.factory.updateETSUnionType(type, [ + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(wrapTypeName), + mirrorArkts.factory.createTSTypeParameterInstantiation([type.types[0]]) + ) + ), + type.types[1], + ]); + } + return type; + } - // static wrapStateManagementTypeToType( - // type: mirrorArkts.TypeNode | undefined, - // decoratorName: DecoratorNames - // ): mirrorArkts.TypeNode | undefined { - // let newType: mirrorArkts.TypeNode | undefined; - // let wrapTypeName: StateManagementTypes | undefined; - // if (!!type && !!(wrapTypeName = DECORATOR_TYPE_MAP.get(decoratorName))) { - // newType = factory.wrapInterfacePropertyType(type, wrapTypeName); - // collectStateManagementTypeImport(wrapTypeName); - // } - // return newType; - // } + /** + * wrap interface property parameter that has non-undefined type `T` to ``. + */ + static wrapInterfacePropertyParamExpr( + param: mirrorArkts.Expression, + wrapTypeName: StateManagementTypes + ): mirrorArkts.Expression { + if (!mirrorArkts.isETSParameterExpression(param)) { + return param; + } + if (!param.typeAnnotation || !mirrorArkts.isETSUnionType(param.typeAnnotation)) { + return param; + } + return mirrorArkts.factory.updateETSParameterExpression( + param, + mirrorArkts.factory.createIdentifier( + param.ident!.name, + factory.wrapInterfacePropertyType(param.typeAnnotation, wrapTypeName) + ), + false, + param.initializer + ); + } - // static wrapStateManagementTypeToParam( - // param: mirrorArkts.Expression | undefined, - // decoratorName: DecoratorNames - // ): mirrorArkts.Expression | undefined { - // let newParam: mirrorArkts.Expression | undefined; - // let wrapTypeName: StateManagementTypes | undefined; - // if (!!param && !!(wrapTypeName = DECORATOR_TYPE_MAP.get(decoratorName))) { - // newParam = factory.wrapInterfacePropertyParamExpr(param, wrapTypeName); - // collectStateManagementTypeImport(wrapTypeName); - // } - // return newParam; - // } + static wrapStateManagementTypeToType( + type: mirrorArkts.TypeNode | undefined, + decoratorName: DecoratorNames + ): mirrorArkts.TypeNode | undefined { + let newType: mirrorArkts.TypeNode | undefined; + let wrapTypeName: StateManagementTypes | undefined; + if (!!type && !!(wrapTypeName = DECORATOR_TYPE_MAP.get(decoratorName))) { + newType = factory.wrapInterfacePropertyType(type, wrapTypeName); + collectStateManagementTypeImport(wrapTypeName); + } + return newType; + } - // /** - // * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) - // * to ` | undefined`, where `` is getting from `DecoratorName`; - // * - // * @param method expecting getter with decorator annotation and a setter with decorator annotation in the overloads. - // */ - // static wrapStateManagementTypeToMethodInInterface( - // method: mirrorArkts.MethodDefinition, - // decorator: DecoratorNames - // ): mirrorArkts.MethodDefinition { - // if (method.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET) { - // const newType: mirrorArkts.TypeNode | undefined = factory.wrapStateManagementTypeToType( - // method.scriptFunction.returnTypeAnnotation, - // decorator - // ); - // const newOverLoads = method.overloads.map((overload) => { - // if (mirrorArkts.isMethodDefinition(overload)) { - // return factory.wrapStateManagementTypeToMethodInInterface(overload, decorator); - // } - // return overload; - // }); - // method.setOverloads(newOverLoads); - // removeDecorator(method, decorator); - // if (!!newType) { - // method.scriptFunction.setReturnTypeAnnotation(newType); - // } - // } else if (method.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET) { - // const newParam: mirrorArkts.Expression | undefined = factory.wrapStateManagementTypeToParam( - // method.scriptFunction.params.at(0), - // decorator - // ); - // removeDecorator(method, decorator); - // if (!!newParam) { - // return UIFactory.updateMethodDefinition(method, { function: { params: [newParam] } }); - // } - // } - // return method; - // } + static wrapStateManagementTypeToParam( + param: mirrorArkts.Expression | undefined, + decoratorName: DecoratorNames + ): mirrorArkts.Expression | undefined { + let newParam: mirrorArkts.Expression | undefined; + let wrapTypeName: StateManagementTypes | undefined; + if (!!param && !!(wrapTypeName = DECORATOR_TYPE_MAP.get(decoratorName))) { + newParam = factory.wrapInterfacePropertyParamExpr(param, wrapTypeName); + collectStateManagementTypeImport(wrapTypeName); + } + return newParam; + } - // /** - // * Wrap to the type of the property (expecting an union type with `T` and `undefined`) - // * to ` | undefined`, where `` is getting from `DecoratorName`; - // * - // * @param property expecting property with decorator annotation. - // */ - // static wrapStateManagementTypeToPropertyInInterface( - // property: mirrorArkts.ClassProperty, - // decorator: DecoratorNames - // ): mirrorArkts.ClassProperty { - // const newType: mirrorArkts.TypeNode | undefined = factory.wrapStateManagementTypeToType( - // property.typeAnnotation, - // decorator - // ); - // removeDecorator(property, decorator); - // if (!!newType) { - // property.setTypeAnnotation(newType); - // } - // return property; - // } + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to ` | undefined`, where `` is getting from `DecoratorName`; + * + * @param method expecting getter with decorator annotation and a setter with decorator annotation in the overloads. + */ + static wrapStateManagementTypeToMethodInInterface( + method: mirrorArkts.MethodDefinition, + decorator: DecoratorNames + ): mirrorArkts.MethodDefinition { + if (method.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET) { + const newType: mirrorArkts.TypeNode | undefined = factory.wrapStateManagementTypeToType( + method.function!.returnTypeAnnotation, + decorator + ); + const newOverLoads = method.overloads.map((overload) => { + if (mirrorArkts.isMethodDefinition(overload)) { + return factory.wrapStateManagementTypeToMethodInInterface(overload, decorator); + } + return overload; + }); + method.setOverloads(newOverLoads); + removeDecorator(method, decorator); + if (!!newType) { + method.function!.setReturnTypeAnnotation(newType); + } + } else if (method.kind === mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET) { + const newParam: mirrorArkts.Expression | undefined = factory.wrapStateManagementTypeToParam( + method.function!.params.at(0), + decorator + ); + removeDecorator(method, decorator); + if (!!newParam) { + return UIFactory.updateMethodDefinition(method, { function: { params: [newParam] } }); + } + } + return method; + } - // /** - // * create `Type.from()` when translating `@StorageLink`, `@StorageProp`, `@LocalStorageLink` - // * - // * @param typeAnnotation expecting property's original type annotation. - // */ - // static createTypeFrom(typeAnnotation: mirrorArkts.TypeNode | undefined): mirrorArkts.CallExpression { - // return mirrorArkts.factory.createCallExpression( - // mirrorArkts.factory.createMemberExpression( - // mirrorArkts.factory.createIdentifier('Type'), - // mirrorArkts.factory.createIdentifier('from'), - // mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - // false, - // false - // ), - // typeAnnotation ? [typeAnnotation] : undefined, - // undefined - // ); - // } + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to ` | undefined`, where `` is getting from `DecoratorName`; + * + * @param property expecting property with decorator annotation. + */ + static wrapStateManagementTypeToPropertyInInterface( + property: mirrorArkts.ClassProperty, + decorator: DecoratorNames + ): mirrorArkts.ClassProperty { + const newType: mirrorArkts.TypeNode | undefined = factory.wrapStateManagementTypeToType( + property.typeAnnotation, + decorator + ); + removeDecorator(property, decorator); + if (!!newType) { + property.setTypeAnnotation(newType); + } + return property; + } + + /** + * create `Type.from()` when translating `@StorageLink`, `@StorageProp`, `@LocalStorageLink` + * + * @param typeAnnotation expecting property's original type annotation. + */ + static createTypeFrom(typeAnnotation: mirrorArkts.TypeNode | undefined): mirrorArkts.CallExpression { + return mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createIdentifier('Type'), + mirrorArkts.factory.createIdentifier('from'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + [], + typeAnnotation ? mirrorArkts.TSTypeParameterInstantiation.createTSTypeParameterInstantiation([typeAnnotation]) : undefined, + ); + } } diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/index.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/index.ts new file mode 100644 index 000000000..822895e7f --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/index.ts @@ -0,0 +1,133 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { DecoratorNames } from '../../common/predefines'; +import { InterfacePropertyTranslator, PropertyTranslator } from './base'; +import { hasDecorator } from './utils'; +import { StateInterfaceTranslator, StateTranslator } from './state'; +import { PropInterfaceTranslator, PropTranslator } from './prop'; +import { StorageLinkInterfaceTranslator, StorageLinkTranslator } from './storagelink'; +import { LocalStorageLinkInterfaceTranslator, LocalStorageLinkTranslator } from './localstoragelink'; +import { LinkInterfaceTranslator, LinkTranslator } from './link'; +import { ObjectLinkInterfaceTranslator, ObjectLinkTranslator } from './objectlink'; +import { LocalStoragePropInterfaceTranslator, LocalStoragePropTranslator } from './localstorageprop'; +import { RegularInterfaceTranslator, RegularPropertyTranslator } from './regularProperty'; +import { staticPropertyTranslator } from './staticProperty'; +import { CustomComponentInfo, isStatic } from '../utils'; +import { StoragePropInterfaceTranslator, StoragePropTranslator } from './storageProp'; +import { ConsumeInterfaceTranslator, ConsumeTranslator } from './consume'; +import { ProvideInterfaceTranslator, ProvideTranslator } from './provide'; +import { BuilderParamInterfaceTranslator, BuilderParamTranslator } from './builderParam'; +import { ObservedTrackTranslator } from './observedTrack'; +import { ClassScopeInfo } from './types'; + +export { PropertyTranslator, InterfacePropertyTranslator }; +export type { ClassScopeInfo }; + +export function classifyProperty( + property: mirrorArkts.AstNode, + structInfo: CustomComponentInfo +): PropertyTranslator | undefined { + if (!mirrorArkts.isClassProperty(property)) return undefined; + if (isStatic(property)) return new staticPropertyTranslator({ property, structInfo }); + + if (hasDecorator(property, DecoratorNames.STATE)) { + return new StateTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.STORAGE_LINK)) { + return new StorageLinkTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.LOCAL_STORAGE_LINK)) { + return new LocalStorageLinkTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.LINK)) { + return new LinkTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.OBJECT_LINK)) { + return new ObjectLinkTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.LOCAL_STORAGE_PROP)) { + return new LocalStoragePropTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.STORAGE_PROP)) { + return new StoragePropTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.PROP)) { + return new PropTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.PROVIDE)) { + return new ProvideTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.CONSUME)) { + return new ConsumeTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.BUILDER_PARAM)) { + return new BuilderParamTranslator({ property, structInfo }); + } + + return new RegularPropertyTranslator({ property, structInfo }); +} + +export function classifyPropertyInInterface(property: mirrorArkts.AstNode): InterfacePropertyTranslator | undefined { + if (StateInterfaceTranslator.canBeTranslated(property)) { + return new StateInterfaceTranslator({ property }); + } + if (LinkInterfaceTranslator.canBeTranslated(property)) { + return new LinkInterfaceTranslator({ property }); + } + if (PropInterfaceTranslator.canBeTranslated(property)) { + return new PropInterfaceTranslator({ property }); + } + if (ProvideInterfaceTranslator.canBeTranslated(property)) { + return new ProvideInterfaceTranslator({ property }); + } + if (ConsumeInterfaceTranslator.canBeTranslated(property)) { + return new ConsumeInterfaceTranslator({ property }); + } + if (StoragePropInterfaceTranslator.canBeTranslated(property)) { + return new StoragePropInterfaceTranslator({ property }); + } + if (StorageLinkInterfaceTranslator.canBeTranslated(property)) { + return new StorageLinkInterfaceTranslator({ property }); + } + if (BuilderParamInterfaceTranslator.canBeTranslated(property)) { + return new BuilderParamInterfaceTranslator({ property }); + } + if (LocalStoragePropInterfaceTranslator.canBeTranslated(property)) { + return new LocalStoragePropInterfaceTranslator({ property }); + } + if (LocalStorageLinkInterfaceTranslator.canBeTranslated(property)) { + return new LocalStorageLinkInterfaceTranslator({ property }); + } + if (ObjectLinkInterfaceTranslator.canBeTranslated(property)) { + return new ObjectLinkInterfaceTranslator({ property }); + } + if (RegularInterfaceTranslator.canBeTranslated(property)) { + return new RegularInterfaceTranslator({ property }); + } + return undefined; +} + +export function classifyObservedTrack( + member: mirrorArkts.AstNode, + classScopeInfo: ClassScopeInfo +): ObservedTrackTranslator | undefined { + if (!mirrorArkts.isClassProperty(member)) { + return undefined; + } + return new ObservedTrackTranslator(member, classScopeInfo); +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/link.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/link.ts new file mode 100644 index 000000000..d999b1577 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/link.ts @@ -0,0 +1,176 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { CustomComponentNames } from '../utils'; +import { + generateToRecord, + createGetter, + createSetter2, + generateThisBacking, + generateGetOrSetCall, + collectStateManagementTypeImport, + hasDecorator, + PropertyCache, +} from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; + +export class LinkTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(newName, originalName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + if (!!this.structInfo.annotations?.reusable) { + const toRecord = generateToRecord(newName, originalName); + PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); + } + } + + generateInitializeStruct(newName: string, originalName: string) { + const test = factory.createBlockStatementForOptionalExpression( + mirrorArkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME), + newName + ); + + const args: mirrorArkts.Expression[] = [ + mirrorArkts.factory.createStringLiteral(originalName), + mirrorArkts.factory.createTSNonNullExpression( + factory.createNonNullOrOptionalMemberExpression( + CustomComponentNames.COMPONENT_INITIALIZERS_NAME, + newName, + false, + true + ) + ), + ]; + factory.judgeIfAddWatchFunc(args, this.property); + collectStateManagementTypeImport(StateManagementTypes.LINK_DECORATED); + collectStateManagementTypeImport(StateManagementTypes.LINK_SOURCE_TYPE); + const consequent = mirrorArkts.BlockStatement.createBlockStatement([ + mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createAssignmentExpression( + generateThisBacking(newName, false, false), + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_LINK, + this.property.typeAnnotation, + args, + true + ), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + ) + ), + ]); + /** + * @todo fix the red squiggle + */ + return mirrorArkts.factory.createExpressionStatement(mirrorArkts.factory.createIfStatement(test, consequent)); + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const field: mirrorArkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.LINK_DECORATED, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + const thisValue: mirrorArkts.Expression = generateThisBacking(newName, false, true); + const thisGet: mirrorArkts.CallExpression = generateGetOrSetCall(thisValue, GetSetTypes.GET); + const thisSet: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( + generateGetOrSetCall(thisValue, GetSetTypes.SET) + ); + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + const setter: mirrorArkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + statement: mirrorArkts.AstNode + ): mirrorArkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } +} + +export class LinkInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (mirrorArkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (mirrorArkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + if (mirrorArkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.LINK)) { + return true; + } else if (mirrorArkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.LINK)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `DecoratedV1VariableBase | undefined`. + * + * @param method expecting getter with `@Link` and a setter with `@Link` in the overloads. + */ + private updateStateMethodInInterface(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.LINK); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `DecoratedV1VariableBase | undefined`. + * + * @param property expecting property with `@Link`. + */ + private updateStatePropertyInInterface(property: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.LINK); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/localstoragelink.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/localstoragelink.ts new file mode 100755 index 000000000..772b76015 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/localstoragelink.ts @@ -0,0 +1,191 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { + generateToRecord, + createGetter, + createSetter2, + generateThisBacking, + generateGetOrSetCall, + collectStateManagementTypeImport, + hasDecorator, + PropertyCache, +} from './utils'; +import { factory } from './factory'; + +function getLocalStorageLinkValueStr(node: mirrorArkts.AstNode): string | undefined { + if (!mirrorArkts.isClassProperty(node) || !node.value) return undefined; + + return mirrorArkts.isStringLiteral(node.value) ? node.value.str : undefined; +} + +function getLocalStorageLinkAnnotationValue(anno: mirrorArkts.AnnotationUsage): string | undefined { + const isLocalStorageLinkAnnotation: boolean = + !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === DecoratorNames.LOCAL_STORAGE_LINK; + + if (isLocalStorageLinkAnnotation && anno.properties.length === 1) { + return getLocalStorageLinkValueStr(anno.properties.at(0)!); + } + return undefined; +} + +function getLocalStorageLinkValueInAnnotation(node: mirrorArkts.ClassProperty): string | undefined { + const annotations: readonly mirrorArkts.AnnotationUsage[] = node.annotations; + + for (let i = 0; i < annotations.length; i++) { + const anno: mirrorArkts.AnnotationUsage = annotations[i]; + const str: string | undefined = getLocalStorageLinkAnnotationValue(anno); + if (!!str) { + return str; + } + } + return undefined; +} + +export class LocalStorageLinkTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(newName, originalName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + if (!!this.structInfo.annotations?.reusable) { + const toRecord = generateToRecord(newName, originalName); + PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); + } + } + + generateInitializeStruct(newName: string, originalName: string): mirrorArkts.AstNode { + const localStorageLinkValueStr: string | undefined = getLocalStorageLinkValueInAnnotation(this.property); + if (!localStorageLinkValueStr) { + throw new Error('LocalStorageLink required only one value!!'); + } + + const args: mirrorArkts.Expression[] = [ + mirrorArkts.factory.createStringLiteral(localStorageLinkValueStr), + mirrorArkts.factory.createStringLiteral(originalName), + this.property.value ?? mirrorArkts.factory.createUndefinedLiteral(), + factory.createTypeFrom(this.property.typeAnnotation) + ]; + factory.judgeIfAddWatchFunc(args, this.property); + collectStateManagementTypeImport(StateManagementTypes.LOCAL_STORAGE_LINK_DECORATED); + return mirrorArkts.factory.createAssignmentExpression( + generateThisBacking(newName), + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_LOCAL_STORAGE_LINK, + this.property.typeAnnotation, + args, + true + ), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + ); + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const field = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.LOCAL_STORAGE_LINK_DECORATED, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + const thisValue: mirrorArkts.Expression = generateThisBacking(newName, false, true); + const thisGet: mirrorArkts.CallExpression = generateGetOrSetCall(thisValue, GetSetTypes.GET); + const thisSet: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( + generateGetOrSetCall(thisValue, GetSetTypes.SET) + ); + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + const setter: mirrorArkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + statement: mirrorArkts.AstNode + ): mirrorArkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } +} + +export class LocalStorageLinkInterfaceTranslator< + T extends InterfacePropertyTypes +> extends InterfacePropertyTranslator { + translateProperty(): T { + if (mirrorArkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (mirrorArkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + if (mirrorArkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.LOCAL_STORAGE_LINK)) { + return true; + } else if (mirrorArkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.LOCAL_STORAGE_LINK)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `MutableState | undefined`. + * + * @param method expecting getter with `@LocalStorageLink` and a setter with `@LocalStorageLink` in the overloads. + */ + private updateStateMethodInInterface(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.LOCAL_STORAGE_LINK); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `MutableState | undefined`. + * + * @param property expecting property with `@LocalStorageLink`. + */ + private updateStatePropertyInInterface(property: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.LOCAL_STORAGE_LINK); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/localstorageprop.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/localstorageprop.ts new file mode 100644 index 000000000..b71c77806 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/localstorageprop.ts @@ -0,0 +1,244 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, StateManagementTypes } from '../../common/predefines'; +import { + collectStateManagementTypeImport, + generateToRecord, + hasDecorator, + PropertyCache, +} from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; + +function getLocalStorageporpValueStr(node: mirrorArkts.AstNode): string | undefined { + if (!mirrorArkts.isClassProperty(node) || !node.value) return undefined; + + return mirrorArkts.isStringLiteral(node.value) ? node.value.str : undefined; +} +function getLocalStorageporpAnnotationValue(anno: mirrorArkts.AnnotationUsage): string | undefined { + const isLocalStorageporpAnnotation: boolean = + !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === DecoratorNames.LOCAL_STORAGE_PROP; + + if (isLocalStorageporpAnnotation && anno.properties.length === 1) { + return getLocalStorageporpValueStr(anno.properties.at(0)!); + } + return undefined; +} + +function getLocalStorageporpValueInAnnotation(node: mirrorArkts.ClassProperty): string | undefined { + const annotations: readonly mirrorArkts.AnnotationUsage[] = node.annotations; + + for (let i = 0; i < annotations.length; i++) { + const anno: mirrorArkts.AnnotationUsage = annotations[i]; + const str: string | undefined = getLocalStorageporpAnnotationValue(anno); + if (!!str) { + return str; + } + } + + return undefined; +} + +export class LocalStoragePropTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(newName, originalName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + const updateStruct: mirrorArkts.AstNode = this.generateUpdateStruct(newName, originalName); + PropertyCache.getInstance().collectUpdateStruct(this.structInfo.name, [updateStruct]); + if (!!this.structInfo.annotations?.reusable) { + const toRecord = generateToRecord(newName, originalName); + PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); + } + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const field = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.SYNCED_PROPERTY, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + + const member = mirrorArkts.factory.createTSNonNullExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier(newName), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ) + ); + const thisValue: mirrorArkts.MemberExpression = mirrorArkts.factory.createMemberExpression( + member, + mirrorArkts.factory.createIdentifier('value'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); + + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisValue + ); + const setter: mirrorArkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisValue + ); + return [field, getter, setter]; + } + + generateInitializeStruct(newName: string, originalName: string): mirrorArkts.AstNode { + const localStorageporpValueStr: string | undefined = getLocalStorageporpValueInAnnotation(this.property); + if (!localStorageporpValueStr) { + throw new Error('LocalStorageProp required only one value!!'); + } + const insideMember = mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier('_entry_local_storage_'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); + const binaryItem = mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createIdentifier(StateManagementTypes.STORAGE_LINK_STATE), + [ + insideMember, + mirrorArkts.factory.createStringLiteral(localStorageporpValueStr), + this.property.value ?? mirrorArkts.factory.createUndefinedLiteral(), + ], + this.property.typeAnnotation ? mirrorArkts.TSTypeParameterInstantiation.createTSTypeParameterInstantiation([this.property.typeAnnotation]): undefined + ); + collectStateManagementTypeImport(StateManagementTypes.STORAGE_LINK_STATE); + const call = mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createIdentifier(StateManagementTypes.PROP_STATE), + [ + mirrorArkts.factory.createMemberExpression( + binaryItem, + mirrorArkts.factory.createIdentifier('value'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + ], + this.property.typeAnnotation ? mirrorArkts.TSTypeParameterInstantiation.createTSTypeParameterInstantiation([this.property.typeAnnotation]): undefined + ); + collectStateManagementTypeImport(StateManagementTypes.PROP_STATE); + return mirrorArkts.factory.createAssignmentExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier(newName), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + call, + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + ); + } + + generateUpdateStruct(newName: string, originalName: string): mirrorArkts.AstNode { + const localStorageporpValueStr: string | undefined = getLocalStorageporpValueInAnnotation(this.property); + if (!localStorageporpValueStr) { + throw new Error('StorageLink required only one value!!'); + } + const StorageLinkStateValue = factory.createStorageLinkStateValue(this.property, localStorageporpValueStr); + collectStateManagementTypeImport(StateManagementTypes.STORAGE_LINK_STATE); + const test = mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier(newName), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); + const consequent = mirrorArkts.BlockStatement.createBlockStatement([ + mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createTSNonNullExpression(test), + mirrorArkts.factory.createIdentifier('update'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + [StorageLinkStateValue], + undefined + ) + ), + ]); + /** + * @todo fix the red squiggle + */ + return mirrorArkts.factory.createExpressionStatement(mirrorArkts.factory.createIfStatement(test, consequent)); + } +} + +export class LocalStoragePropInterfaceTranslator< + T extends InterfacePropertyTypes +> extends InterfacePropertyTranslator { + translateProperty(): T { + if (mirrorArkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (mirrorArkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + if (mirrorArkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.LOCAL_STORAGE_PROP)) { + return true; + } else if (mirrorArkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.LOCAL_STORAGE_PROP)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `SyncedProperty | undefined`. + * + * @param method expecting getter with `@LocalStorageProp` and a setter with `@LocalStorageProp` in the overloads. + */ + private updateStateMethodInInterface(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.LOCAL_STORAGE_PROP); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `SyncedProperty | undefined`. + * + * @param property expecting property with `@LocalStorageProp`. + */ + private updateStatePropertyInInterface(property: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.LOCAL_STORAGE_PROP); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/objectlink.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/objectlink.ts new file mode 100644 index 000000000..e7ec94546 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/objectlink.ts @@ -0,0 +1,173 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { CustomComponentNames } from '../utils'; +import { + createGetter, + generateGetOrSetCall, + generateThisBacking, + generateToRecord, + hasDecorator, + PropertyCache, +} from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; + +export class ObjectLinkTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + if (!this.ifObservedDecoratedClass()) { + throw new Error('@ObjectLink decorated property only accepts @Observed decorated class instance'); + } + + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(newName, originalName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + const updateStruct: mirrorArkts.AstNode = this.generateUpdateStruct(newName, originalName); + PropertyCache.getInstance().collectUpdateStruct(this.structInfo.name, [updateStruct]); + if (!!this.structInfo.annotations?.reusable) { + const toRecord = generateToRecord(newName, originalName); + PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); + } + } + + generateInitializeStruct(newName: string, originalName: string): mirrorArkts.AstNode { + const initializers = mirrorArkts.factory.createTSNonNullExpression( + factory.createBlockStatementForOptionalExpression( + mirrorArkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME), + originalName + ) + ); + const args: mirrorArkts.Expression[] = [mirrorArkts.factory.createStringLiteral(originalName), initializers]; + factory.judgeIfAddWatchFunc(args, this.property); + + return mirrorArkts.factory.createAssignmentExpression( + generateThisBacking(newName), + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_OBJECT_LINK, + this.property.typeAnnotation, + args, + true + ), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION + ); + } + + generateUpdateStruct(newName: string, originalName: string): mirrorArkts.AstNode { + const member: mirrorArkts.MemberExpression = mirrorArkts.factory.createMemberExpression( + generateThisBacking(newName, false, true), + mirrorArkts.factory.createIdentifier(StateManagementTypes.UPDATE), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); + const nonNullItem = mirrorArkts.factory.createTSNonNullExpression( + factory.createNonNullOrOptionalMemberExpression( + CustomComponentNames.COMPONENT_INITIALIZERS_NAME, + originalName, + false, + true + ) + ); + return factory.createIfInUpdateStruct(originalName, member, [nonNullItem]); + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const field: mirrorArkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.OBJECT_LINK_DECORATED, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + const thisValue: mirrorArkts.Expression = generateThisBacking(newName, false, true); + const thisGet: mirrorArkts.CallExpression = generateGetOrSetCall(thisValue, GetSetTypes.GET); + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + return [field, getter]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + ifObservedDecoratedClass(): boolean { + if (this.property.typeAnnotation && mirrorArkts.isETSTypeReference(this.property.typeAnnotation)) { + const decl = mirrorArkts.getDecl(this.property.typeAnnotation.part?.name!); + if (mirrorArkts.isClassDefinition(decl!) && hasDecorator(decl, DecoratorNames.OBSERVED)) { + return true; + } + } + return false; + } +} + +export class ObjectLinkInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (mirrorArkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (mirrorArkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + if (mirrorArkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.OBJECT_LINK)) { + return true; + } else if (mirrorArkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.OBJECT_LINK)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `ObjectLinkDecoratedVariable | undefined`. + * + * @param method expecting getter with `@ObjectLink` and a setter with `@ObjectLink` in the overloads. + */ + private updateStateMethodInInterface(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.OBJECT_LINK); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `ObjectLinkDecoratedVariable | undefined`. + * + * @param property expecting property with `@ObjectLink`. + */ + private updateStatePropertyInInterface(property: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.OBJECT_LINK); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/observedTrack.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/observedTrack.ts new file mode 100644 index 000000000..1d575bf96 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/observedTrack.ts @@ -0,0 +1,287 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { annotation, backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, StateManagementTypes } from '../../common/predefines'; +import { collectStateManagementTypeImport, hasDecorator, hasDecoratorName, removeDecorator } from './utils'; +import { ClassScopeInfo } from './types'; +import { factory } from './factory'; + +export class ObservedTrackTranslator { + protected property: mirrorArkts.ClassProperty; + protected classScopeInfo: ClassScopeInfo; + private hasImplement: boolean; + private isTracked: boolean; + + constructor(property: mirrorArkts.ClassProperty, classScopeInfo: ClassScopeInfo) { + this.property = property; + this.classScopeInfo = classScopeInfo; + this.hasImplement = expectName(this.property.key).startsWith(''); + this.isTracked = hasDecorator(this.property, DecoratorNames.TRACK); + } + + translateMember(): mirrorArkts.AstNode[] { + if (!this.isTracked && (this.classScopeInfo.classHasTrack || !this.classScopeInfo.isObserved)) { + return [this.property]; + } + const originalName: string = this.hasImplement + ? this.removeImplementProperty(expectName(this.property.key)) + : expectName(this.property.key); + const newName: string = backingField(originalName); + const field = this.createField(originalName, newName); + this.transformGetterSetter(originalName, newName); + return [...field]; + } + + createField(originalName: string, newName: string): mirrorArkts.ClassProperty[] { + const backingField = mirrorArkts.factory.createClassProperty( + mirrorArkts.factory.createIdentifier(newName), + this.property.value, + this.property.typeAnnotation, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + false + ); + const annotations: mirrorArkts.AnnotationUsage[] = [...this.property.annotations]; + if ( + !hasDecoratorName(this.property, DecoratorNames.JSONSTRINGIFYIGNORE) && + !hasDecoratorName(this.property, DecoratorNames.JSONRENAME)) { + annotations.push( + annotation(DecoratorNames.JSONRENAME).addProperty( + mirrorArkts.factory.createClassProperty( + mirrorArkts.factory.createIdentifier('newName'), + mirrorArkts.factory.createStringLiteral(originalName), + undefined, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + false + ) + ) + ); + } + backingField.setAnnotations(annotations); + removeDecorator(backingField, DecoratorNames.TRACK); + if (!this.isTracked) { + return [backingField]; + } + const metaField = this.metaField(originalName); + metaField.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); + return [backingField, metaField]; + } + + createGetter(originalName: string, newName: string): mirrorArkts.MethodDefinition { + const conditionalAddRef = mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier('conditionalAddRef'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + [ + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + this.metaIdentifier(originalName), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + ], + undefined, + ) + ); + const returnMember: mirrorArkts.ReturnStatement = mirrorArkts.factory.createReturnStatement(this.genThisBacking(newName)); + + const body = mirrorArkts.factory.createBlockStatement([conditionalAddRef, returnMember]); + + const scriptFunction = mirrorArkts.factory.createScriptFunction( + body, + undefined, + [], + this.property.typeAnnotation, + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ); + + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET, + mirrorArkts.factory.createIdentifier(originalName), + mirrorArkts.factory.createFunctionExpression(scriptFunction.id, scriptFunction), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); + } + + createSetter(originalName: string, newName: string): mirrorArkts.MethodDefinition { + const ifEqualsNewValue: mirrorArkts.IfStatement = this.setterIfEqualsNewValue(originalName, newName); + const body = mirrorArkts.factory.createBlockStatement([ifEqualsNewValue]); + const param = mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier('newValue', this.property.typeAnnotation), + false + ); + + const scriptFunction = mirrorArkts.factory.createScriptFunction( + body, + undefined, + [param], + undefined, + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ); + + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, + mirrorArkts.factory.createIdentifier(originalName), + mirrorArkts.factory.createFunctionExpression(scriptFunction.id, scriptFunction), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); + } + + genThisBacking(newName: string): mirrorArkts.MemberExpression { + return mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier(newName), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); + } + + metaIdentifier(originalName: string): mirrorArkts.Identifier { + return this.isTracked + ? mirrorArkts.factory.createIdentifier(`${StateManagementTypes.META}_${originalName}`) + : mirrorArkts.factory.createIdentifier(StateManagementTypes.META); + } + + removeImplementProperty(originalName: string): string { + const prefix = ''; + return originalName.substring(prefix.length); + } + + transformGetterSetter(originalName: string, newName: string): void { + const newGetter = this.createGetter(originalName, newName); + const newSetter = this.createSetter(originalName, newName); + if (this.hasImplement) { + { + const idx: number = this.classScopeInfo.getters.findIndex( + (getter) => getter.id?.name === originalName + ); + const originGetter: mirrorArkts.MethodDefinition = this.classScopeInfo.getters[idx]; + const originSetter: mirrorArkts.MethodDefinition = originGetter.overloads[0]; + const updateGetter: mirrorArkts.MethodDefinition = mirrorArkts.factory.updateMethodDefinition( + originGetter, + originGetter.kind, + newGetter.id, + mirrorArkts.factory.createFunctionExpression(newGetter.function!.id, + newGetter.function!.addFlag(mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD)), + originGetter.modifierFlags, + false + ); + mirrorArkts.factory.updateMethodDefinition( + originSetter, + originSetter.kind, + newSetter.id, + mirrorArkts.factory.createFunctionExpression( + newGetter.function!.id, + newSetter.function! + .addFlag(mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_OVERLOAD) + .addFlag(mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD)), + originSetter.modifierFlags, + false + ); + this.classScopeInfo.getters[idx] = updateGetter; + } + } else { + this.classScopeInfo.getters.push(...[newGetter, newSetter]); + } + } + + metaField(originalName: string): mirrorArkts.ClassProperty { + collectStateManagementTypeImport(StateManagementTypes.MUTABLE_STATE_META); + return mirrorArkts.factory.createClassProperty( + mirrorArkts.factory.createIdentifier(`${StateManagementTypes.META}_${originalName}`), + factory.generateStateMgmtFactoryCall(StateManagementTypes.MAKE_MUTABLESTATE_META, undefined, [], false), + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(StateManagementTypes.MUTABLE_STATE_META) + ) + ), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + false + ); + } + + setterIfEqualsNewValue(originalName: string, newName: string): mirrorArkts.IfStatement { + const backingValue = this.genThisBacking(newName); + + const setNewValue = mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createAssignmentExpression( + backingValue, + mirrorArkts.factory.createIdentifier('newValue'), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + ) + ); + + const fireChange = mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + this.metaIdentifier(originalName), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + mirrorArkts.factory.createIdentifier('fireChange'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + [], + undefined + ) + ); + + const subscribingWatches = mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier('executeOnSubscribingWatches'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + [mirrorArkts.factory.createStringLiteral(originalName)], + undefined, + ) + ); + + return mirrorArkts.factory.createIfStatement( + mirrorArkts.factory.createBinaryExpression( + backingValue, + mirrorArkts.factory.createIdentifier('newValue'), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_STRICT_EQUAL + ), + mirrorArkts.factory.createBlockStatement([setNewValue, fireChange, subscribingWatches]) + ); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/prop.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/prop.ts new file mode 100644 index 000000000..d71165463 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/prop.ts @@ -0,0 +1,200 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { CustomComponentNames } from '../utils'; +import { + generateToRecord, + createGetter, + createSetter2, + generateGetOrSetCall, + generateThisBacking, + collectStateManagementTypeImport, + hasDecorator, + PropertyCache, +} from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; + +export class PropTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const mutableThis: mirrorArkts.Expression = generateThisBacking(newName); + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(newName, originalName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + const updateStruct: mirrorArkts.AstNode = this.generateUpdateStruct(mutableThis, originalName); + PropertyCache.getInstance().collectUpdateStruct(this.structInfo.name, [updateStruct]); + if (!!this.structInfo.annotations?.reusable) { + const toRecord = generateToRecord(newName, originalName); + PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); + } + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const field: mirrorArkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.PROP_DECORATED, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + const thisValue: mirrorArkts.Expression = generateThisBacking(newName, false, true); + const thisGet: mirrorArkts.CallExpression = generateGetOrSetCall(thisValue, GetSetTypes.GET); + const thisSet: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( + generateGetOrSetCall(thisValue, GetSetTypes.SET) + ); + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + const setter: mirrorArkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + statement: mirrorArkts.AstNode + ): mirrorArkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } + + generateInitializeStruct(newName: string, originalName: string): mirrorArkts.AstNode { + const binaryItem = mirrorArkts.factory.createBinaryExpression( + factory.createBlockStatementForOptionalExpression( + mirrorArkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME), + originalName + ), + this.property.value ?? mirrorArkts.factory.createUndefinedLiteral(), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING + ); + const args: mirrorArkts.Expression[] = [ + mirrorArkts.factory.createStringLiteral(originalName), + this.property.value + ? binaryItem + : mirrorArkts.factory.createTSAsExpression( + factory.createNonNullOrOptionalMemberExpression( + CustomComponentNames.COMPONENT_INITIALIZERS_NAME, + originalName, + false, + true + ), + this.property.typeAnnotation ? this.property.typeAnnotation.clone() : undefined, + false + ), + ]; + factory.judgeIfAddWatchFunc(args, this.property); + collectStateManagementTypeImport(StateManagementTypes.PROP_DECORATED); + const assign: mirrorArkts.AssignmentExpression = mirrorArkts.factory.createAssignmentExpression( + generateThisBacking(newName), + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_PROP, + this.property.typeAnnotation, + args, + true + ), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION + ); + return mirrorArkts.factory.createExpressionStatement(assign); + } + + generateUpdateStruct(mutableThis: mirrorArkts.Expression, originalName: string): mirrorArkts.AstNode { + const member: mirrorArkts.MemberExpression = mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createTSNonNullExpression(mutableThis), + mirrorArkts.factory.createIdentifier(StateManagementTypes.UPDATE), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); + return factory.createIfInUpdateStruct(originalName, member, [ + mirrorArkts.factory.createTSAsExpression( + factory.createNonNullOrOptionalMemberExpression( + CustomComponentNames.COMPONENT_INITIALIZERS_NAME, + originalName, + false, + true + ), + this.property.typeAnnotation ? this.property.typeAnnotation.clone() : undefined, + false + ), + ]); + } +} + +export class PropInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (mirrorArkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (mirrorArkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + if (mirrorArkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.PROP)) { + return true; + } else if (mirrorArkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.PROP)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `PropDecoratedVariable | undefined`. + * + * @param method expecting getter with `@Prop` and a setter with `@Prop` in the overloads. + */ + private updateStateMethodInInterface(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.PROP); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `PropDecoratedVariable | undefined`. + * + * @param property expecting property with `@Prop`. + */ + private updateStatePropertyInInterface(property: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.PROP); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/provide.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/provide.ts new file mode 100644 index 000000000..d6028f06e --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/provide.ts @@ -0,0 +1,167 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { CustomComponentNames } from '../utils'; +import { + createGetter, + generateToRecord, + createSetter2, + generateThisBacking, + generateGetOrSetCall, + getValueInProvideAnnotation, + ProvideOptions, + hasDecorator, + PropertyCache, +} from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; + +export class ProvideTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(originalName, newName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + if (!!this.structInfo.annotations?.reusable) { + const toRecord = generateToRecord(newName, originalName); + PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); + } + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const field: mirrorArkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.PROVIDE_DECORATED, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + const thisValue: mirrorArkts.Expression = generateThisBacking(newName, false, true); + const thisGet: mirrorArkts.CallExpression = generateGetOrSetCall(thisValue, GetSetTypes.GET); + const thisSet: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( + generateGetOrSetCall(thisValue, GetSetTypes.SET) + ); + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + const setter: mirrorArkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + statement: mirrorArkts.AstNode + ): mirrorArkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } + + generateInitializeStruct(originalName: string, newName: string): mirrorArkts.AstNode { + const options: undefined | ProvideOptions = getValueInProvideAnnotation(this.property); + const alias: string = options?.alias ?? originalName; + const allowOverride: boolean = options?.allowOverride ?? false; + const args: mirrorArkts.Expression[] = [ + mirrorArkts.factory.createStringLiteral(originalName), + mirrorArkts.factory.createStringLiteral(alias), + mirrorArkts.factory.createBinaryExpression( + factory.createBlockStatementForOptionalExpression( + mirrorArkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME), + originalName + ), + this.property.value ?? mirrorArkts.factory.createUndefinedLiteral(), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING + ), + mirrorArkts.factory.createBooleanLiteral(allowOverride), + ]; + factory.judgeIfAddWatchFunc(args, this.property); + const assign: mirrorArkts.AssignmentExpression = mirrorArkts.factory.createAssignmentExpression( + generateThisBacking(newName), + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_PROVIDE, + this.property.typeAnnotation, + args, + true + ), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + ); + return mirrorArkts.factory.createExpressionStatement(assign); + } +} + +export class ProvideInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (mirrorArkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (mirrorArkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + if (mirrorArkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.PROVIDE)) { + return true; + } else if (mirrorArkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.PROVIDE)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `ProvideDecoratedVariable | undefined`. + * + * @param method expecting getter with `@Provide` and a setter with `@Provide` in the overloads. + */ + private updateStateMethodInInterface(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.PROVIDE); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `ProvideDecoratedVariable | undefined`. + * + * @param property expecting property with `@Provide`. + */ + private updateStatePropertyInInterface(property: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.PROVIDE); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/regularProperty.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/regularProperty.ts new file mode 100644 index 000000000..6880bb8e6 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/regularProperty.ts @@ -0,0 +1,128 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { createGetter, generateToRecord, generateThisBacking, createSetter2, PropertyCache } from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { backingField, expectName } from '../../common/arkts-utils'; +import { factory } from './factory'; +import { updateArrow, updateNewClassInstanceExpression } from '../customdialog'; + +export class RegularPropertyTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + isCustomDialogController(node: mirrorArkts.AstNode | undefined): boolean { + if ((node instanceof mirrorArkts.ETSNewClassInstanceExpression) && (node.typeRef instanceof mirrorArkts.ETSTypeReference) && + (node.typeRef?.part?.name instanceof mirrorArkts.Identifier) && (node.typeRef?.part?.name?.name === 'CustomDialogController')) { + return true; + } + return false; + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const value = this.property.value; + if (this.isCustomDialogController(value)) { + const newValue = updateNewClassInstanceExpression(value as mirrorArkts.ETSNewClassInstanceExpression, this.property.key?.name, true); + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(newName, originalName, newValue); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + } else { + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(newName, originalName, value!); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + } + if (!!this.structInfo.annotations?.reusable) { + const toRecord = generateToRecord(newName, originalName); + PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); + } + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const field: mirrorArkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + undefined, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + const thisValue: mirrorArkts.Expression = generateThisBacking(newName, false, false); + const thisSet: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createAssignmentExpression( + thisValue, + mirrorArkts.factory.createIdentifier('value'), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION + ) + ); + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + mirrorArkts.factory.createTSAsExpression(thisValue, this.property.typeAnnotation, false) + ); + const setter: mirrorArkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + statement: mirrorArkts.AstNode + ): mirrorArkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } + + generateInitializeStruct(newName: string, originalName: string, value: mirrorArkts.Expression): mirrorArkts.AstNode { + const binaryItem = mirrorArkts.factory.createBinaryExpression( + factory.createBlockStatementForOptionalExpression( + mirrorArkts.factory.createIdentifier('initializers'), + originalName + ), + value ?? mirrorArkts.factory.createUndefinedLiteral(), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING + ); + const assign: mirrorArkts.AssignmentExpression = mirrorArkts.factory.createAssignmentExpression( + generateThisBacking(newName), + binaryItem, + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION + ); + return mirrorArkts.factory.createExpressionStatement(assign); + } +} + +export class RegularInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + return true; + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/state.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/state.ts new file mode 100644 index 000000000..17a61c252 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/state.ts @@ -0,0 +1,160 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { CustomComponentNames } from '../utils'; +import { + generateToRecord, + createGetter, + createSetter2, + generateThisBacking, + generateGetOrSetCall, + hasDecorator, + collectStateManagementTypeImport, + PropertyCache, +} from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; + +export class StateTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(newName, originalName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + if (!!this.structInfo.annotations?.reusable) { + const toRecord = generateToRecord(newName, originalName); + PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); + } + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const field: mirrorArkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.STATE_DECORATED, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + const thisValue: mirrorArkts.Expression = generateThisBacking(newName, false, true); + const thisGet: mirrorArkts.CallExpression = generateGetOrSetCall(thisValue, GetSetTypes.GET); + const thisSet: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( + generateGetOrSetCall(thisValue, GetSetTypes.SET) + ); + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + const setter: mirrorArkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + statement: mirrorArkts.AstNode + ): mirrorArkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } + + generateInitializeStruct(newName: string, originalName: string): mirrorArkts.AstNode { + const binaryItem = mirrorArkts.factory.createBinaryExpression( + factory.createBlockStatementForOptionalExpression( + mirrorArkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME), + originalName + ), + this.property.value ?? mirrorArkts.factory.createUndefinedLiteral(), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING + ); + const args: mirrorArkts.Expression[] = [mirrorArkts.factory.createStringLiteral(originalName), binaryItem]; + factory.judgeIfAddWatchFunc(args, this.property); + collectStateManagementTypeImport(StateManagementTypes.STATE_DECORATED); + const assign: mirrorArkts.AssignmentExpression = mirrorArkts.factory.createAssignmentExpression( + generateThisBacking(newName), + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_STATE, + this.property.typeAnnotation, + args, + true + ), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + ); + return mirrorArkts.factory.createExpressionStatement(assign); + } +} + +export class StateInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (mirrorArkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (mirrorArkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + if (mirrorArkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.STATE)) { + return true; + } else if (mirrorArkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.STATE)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `StateDecoratedVariable | undefined`. + * + * @param method expecting getter with `@State` and a setter with `@State` in the overloads. + */ + private updateStateMethodInInterface(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.STATE); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `StateDecoratedVariable | undefined`. + * + * @param property expecting property with `@State`. + */ + private updateStatePropertyInInterface(property: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.STATE); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/staticProperty.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/staticProperty.ts new file mode 100644 index 000000000..8e777abc5 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/staticProperty.ts @@ -0,0 +1,52 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { createGetter, createSetter } from './utils'; +import { PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { backingField, expectName } from '../../common/arkts-utils'; + +export class staticPropertyTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void {} + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + return [this.property]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + left: mirrorArkts.MemberExpression + ): mirrorArkts.MethodDefinition { + const right: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier('value'); + return createSetter(originalName, typeAnnotation, left, right); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/storageProp.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/storageProp.ts new file mode 100644 index 000000000..c5c323492 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/storageProp.ts @@ -0,0 +1,190 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { + generateToRecord, + createGetter, + createSetter2, + generateThisBacking, + generateGetOrSetCall, + collectStateManagementTypeImport, + hasDecorator, + PropertyCache, +} from './utils'; +import { factory } from './factory'; + +function getStoragePropValueStr(node: mirrorArkts.AstNode): string | undefined { + if (!mirrorArkts.isClassProperty(node) || !node.value) return undefined; + + return mirrorArkts.isStringLiteral(node.value) ? node.value.str : undefined; +} + +function getStoragePropAnnotationValue(anno: mirrorArkts.AnnotationUsage): string | undefined { + const isStoragePropAnnotation: boolean = + !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === DecoratorNames.STORAGE_PROP; + + if (isStoragePropAnnotation && anno.properties.length === 1) { + return getStoragePropValueStr(anno.properties.at(0)!); + } + return undefined; +} + +function getStoragePropValueInAnnotation(node: mirrorArkts.ClassProperty): string | undefined { + const annotations: readonly mirrorArkts.AnnotationUsage[] = node.annotations; + + for (let i = 0; i < annotations.length; i++) { + const anno: mirrorArkts.AnnotationUsage = annotations[i]; + const str: string | undefined = getStoragePropAnnotationValue(anno); + if (!!str) { + return str; + } + } + return undefined; +} + +export class StoragePropTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(newName, originalName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + if (!!this.structInfo.annotations?.reusable) { + const toRecord = generateToRecord(newName, originalName); + PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); + } + } + + generateInitializeStruct(newName: string, originalName: string): mirrorArkts.AstNode { + const storagePropValueStr: string | undefined = getStoragePropValueInAnnotation(this.property); + if (!storagePropValueStr) { + throw new Error('StorageProp required only one value!!'); + } + + const args: mirrorArkts.Expression[] = [ + mirrorArkts.factory.createStringLiteral(storagePropValueStr), + mirrorArkts.factory.createStringLiteral(originalName), + this.property.value ?? mirrorArkts.factory.createUndefinedLiteral(), + factory.createTypeFrom(this.property.typeAnnotation) + ]; + factory.judgeIfAddWatchFunc(args, this.property); + collectStateManagementTypeImport(StateManagementTypes.STORAGE_PROP_REF_DECORATED); + + return mirrorArkts.factory.createAssignmentExpression( + generateThisBacking(newName), + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_STORAGE_PROP_REF, + this.property.typeAnnotation, + args, + true + ), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + ); + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const field = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.STORAGE_PROP_REF_DECORATED, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + const thisValue: mirrorArkts.Expression = generateThisBacking(newName, false, true); + const thisGet: mirrorArkts.CallExpression = generateGetOrSetCall(thisValue, GetSetTypes.GET); + const thisSet: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( + generateGetOrSetCall(thisValue, GetSetTypes.SET) + ); + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + const setter: mirrorArkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + statement: mirrorArkts.AstNode + ): mirrorArkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } +} + +export class StoragePropInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (mirrorArkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (mirrorArkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + if (mirrorArkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.STORAGE_PROP)) { + return true; + } else if (mirrorArkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.STORAGE_PROP)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `StoragePropDecoratedVariable | undefined`. + * + * @param method expecting getter with `@StorageProp` and a setter with `@StorageProp` in the overloads. + */ + private updateStateMethodInInterface(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.STORAGE_PROP); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `StoragePropDecoratedVariable | undefined`. + * + * @param property expecting property with `@StorageProp`. + */ + private updateStatePropertyInInterface(property: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.STORAGE_PROP); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/storagelink.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/storagelink.ts new file mode 100644 index 000000000..07f1e86f7 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/storagelink.ts @@ -0,0 +1,189 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { + generateToRecord, + createGetter, + createSetter2, + generateThisBacking, + generateGetOrSetCall, + collectStateManagementTypeImport, + hasDecorator, + PropertyCache, +} from './utils'; +import { factory } from './factory'; + +function getStorageLinkValueStr(node: mirrorArkts.AstNode): string | undefined { + if (!mirrorArkts.isClassProperty(node) || !node.value) return undefined; + + return mirrorArkts.isStringLiteral(node.value) ? node.value.str : undefined; +} + +function getStorageLinkAnnotationValue(anno: mirrorArkts.AnnotationUsage): string | undefined { + const isStorageLinkAnnotation: boolean = + !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === DecoratorNames.STORAGE_LINK; + + if (isStorageLinkAnnotation && anno.properties.length === 1) { + return getStorageLinkValueStr(anno.properties.at(0)!); + } + return undefined; +} + +function getStorageLinkValueInAnnotation(node: mirrorArkts.ClassProperty): string | undefined { + const annotations: readonly mirrorArkts.AnnotationUsage[] = node.annotations; + + for (let i = 0; i < annotations.length; i++) { + const anno: mirrorArkts.AnnotationUsage = annotations[i]; + const str: string | undefined = getStorageLinkAnnotationValue(anno); + if (!!str) { + return str; + } + } + return undefined; +} + +export class StorageLinkTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): mirrorArkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: mirrorArkts.AstNode = this.generateInitializeStruct(newName, originalName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + if (!!this.structInfo.annotations?.reusable) { + const toRecord = generateToRecord(newName, originalName); + PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); + } + } + + generateInitializeStruct(newName: string, originalName: string): mirrorArkts.AstNode { + const storageLinkValueStr: string | undefined = getStorageLinkValueInAnnotation(this.property); + if (!storageLinkValueStr) { + throw new Error('StorageLink required only one value!!'); + } + + const args: mirrorArkts.Expression[] = [ + mirrorArkts.factory.createStringLiteral(storageLinkValueStr), + mirrorArkts.factory.createStringLiteral(originalName), + this.property.value ?? mirrorArkts.factory.createUndefinedLiteral(), + factory.createTypeFrom(this.property.typeAnnotation) + ]; + factory.judgeIfAddWatchFunc(args, this.property); + collectStateManagementTypeImport(StateManagementTypes.STORAGE_LINK_DECORATED); + return mirrorArkts.factory.createAssignmentExpression( + generateThisBacking(newName), + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_STORAGE_LINK, + this.property.typeAnnotation, + args, + true + ), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + ); + } + + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[] { + const field = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.STORAGE_LINK_DECORATED, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + const thisValue: mirrorArkts.Expression = generateThisBacking(newName, false, true); + const thisGet: mirrorArkts.CallExpression = generateGetOrSetCall(thisValue, GetSetTypes.GET); + const thisSet: mirrorArkts.ExpressionStatement = mirrorArkts.factory.createExpressionStatement( + generateGetOrSetCall(thisValue, GetSetTypes.SET) + ); + const getter: mirrorArkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + const setter: mirrorArkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + statement: mirrorArkts.AstNode + ): mirrorArkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } +} + +export class StorageLinkInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (mirrorArkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (mirrorArkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: mirrorArkts.AstNode): node is InterfacePropertyTypes { + if (mirrorArkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.STORAGE_LINK)) { + return true; + } else if (mirrorArkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.STORAGE_LINK)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `StorageLinkDecoratedVariable | undefined`. + * + * @param method expecting getter with `@StorageLink` and a setter with `@StorageLink` in the overloads. + */ + private updateStateMethodInInterface(method: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.STORAGE_LINK); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `StorageLinkDecoratedVariable | undefined`. + * + * @param property expecting property with `@StorageLink`. + */ + private updateStatePropertyInInterface(property: mirrorArkts.ClassProperty): mirrorArkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.STORAGE_LINK); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/types.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/types.ts new file mode 100644 index 000000000..f86cc7abc --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/types.ts @@ -0,0 +1,40 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; + +export interface GetterSetter { + translateGetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + returnValue: mirrorArkts.Expression + ): mirrorArkts.MethodDefinition; + translateSetter( + originalName: string, + typeAnnotation: mirrorArkts.TypeNode | undefined, + left: mirrorArkts.MemberExpression + ): mirrorArkts.MethodDefinition; +} + +export interface InitializerConstructor { + cacheTranslatedInitializer(newName: string, originalName: string): void; + translateWithoutInitializer(newName: string, originalName: string): mirrorArkts.AstNode[]; +} + +export type ClassScopeInfo = { + isObserved: boolean; + classHasTrack: boolean; + getters: mirrorArkts.MethodDefinition[] +}; diff --git a/arkui-plugins/mirror-replace/ui-plugins/property-translators/utils.ts b/arkui-plugins/mirror-replace/ui-plugins/property-translators/utils.ts index fd71a216e..7d6b73a8a 100644 --- a/arkui-plugins/mirror-replace/ui-plugins/property-translators/utils.ts +++ b/arkui-plugins/mirror-replace/ui-plugins/property-translators/utils.ts @@ -23,41 +23,41 @@ import { StateManagementTypes, GetSetTypes, } from '../../common/predefines'; -// import { -// addMemoAnnotation, -// findCanAddMemoFromParameter, -// findCanAddMemoFromTypeAnnotation, -// } from '../../collectors/memo-collectors/utils'; +import { + addMemoAnnotation, + findCanAddMemoFromParameter, + findCanAddMemoFromTypeAnnotation, +} from '../../collectors/memo-collectors/utils'; export interface DecoratorInfo { annotation: mirrorArkts.AnnotationUsage; name: DecoratorNames; } -// export function isDecoratorIntrinsicAnnotation( -// anno: mirrorArkts.AnnotationUsage, -// decoratorName: DecoratorIntrinsicNames -// ): boolean { -// return !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; -// } +export function isDecoratorIntrinsicAnnotation( + anno: mirrorArkts.AnnotationUsage, + decoratorName: DecoratorIntrinsicNames +): boolean { + return !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; +} -// export function removeDecorator( -// property: mirrorArkts.ClassProperty | mirrorArkts.ClassDefinition | mirrorArkts.MethodDefinition, -// decoratorName: DecoratorNames, -// ignoreDecl?: boolean -// ): void { -// if (mirrorArkts.isMethodDefinition(property)) { -// property.scriptFunction.setAnnotations( -// property.scriptFunction.annotations.filter( -// (anno) => !isDecoratorAnnotation(anno, decoratorName, ignoreDecl) -// ) -// ); -// } else { -// property.setAnnotations( -// property.annotations.filter((anno) => !isDecoratorAnnotation(anno, decoratorName, ignoreDecl)) -// ); -// } -// } +export function removeDecorator( + property: mirrorArkts.ClassProperty | mirrorArkts.ClassDefinition | mirrorArkts.MethodDefinition, + decoratorName: DecoratorNames, + ignoreDecl?: boolean +): void { + if (mirrorArkts.isMethodDefinition(property)) { + property.function!.setAnnotations( + property.function!.annotations.filter( + (anno) => !isDecoratorAnnotation(anno, decoratorName, ignoreDecl) + ) + ); + } else { + property.setAnnotations( + property.annotations.filter((anno) => !isDecoratorAnnotation(anno, decoratorName, ignoreDecl)) + ); + } +} /** * checking whether astNode's annotations contain given corresponding decorator name, @@ -73,20 +73,20 @@ export function hasDecoratorName( return property.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName, true)); } -// export function hasDecorator( -// property: -// | mirrorArkts.ClassProperty -// | mirrorArkts.ClassDefinition -// | mirrorArkts.MethodDefinition -// | mirrorArkts.ETSParameterExpression -// | mirrorArkts.ETSFunctionType, -// decoratorName: DecoratorNames -// ): boolean { -// if (mirrorArkts.isMethodDefinition(property)) { -// return property.scriptFunction.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName)); -// } -// return property.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName)); -// } +export function hasDecorator( + property: + | mirrorArkts.ClassProperty + | mirrorArkts.ClassDefinition + | mirrorArkts.MethodDefinition + | mirrorArkts.ETSParameterExpression + | mirrorArkts.ETSFunctionType, + decoratorName: DecoratorNames +): boolean { + if (mirrorArkts.isMethodDefinition(property)) { + return property.function!.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName)); + } + return property.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName)); +} /** * Determine whether the node `` is decorated by decorators that need initializing without assignment. @@ -153,291 +153,308 @@ export function collectStateManagementTypeImport(type: StateManagementTypes): vo ImportCollector.getInstance().collectImport(type); } -// export function createGetter( -// name: string, -// type: mirrorArkts.TypeNode | undefined, -// returns: mirrorArkts.Expression, -// needMemo: boolean = false -// ): mirrorArkts.MethodDefinition { -// const returnType: mirrorArkts.TypeNode | undefined = type?.clone(); -// if (needMemo && findCanAddMemoFromTypeAnnotation(returnType)) { -// addMemoAnnotation(returnType); -// } -// const body = mirrorArkts.factory.createBlock([mirrorArkts.factory.createReturnStatement(returns)]); -// const scriptFunction = mirrorArkts.factory.createScriptFunction( -// body, -// mirrorArkts.FunctionSignature.createFunctionSignature(undefined, [], returnType, false), -// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER, -// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC -// ); -// return mirrorArkts.factory.createMethodDefinition( -// mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET, -// mirrorArkts.factory.createIdentifier(name), -// scriptFunction, -// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, -// false -// ); -// } +export function createGetter( + name: string, + type: mirrorArkts.TypeNode | undefined, + returns: mirrorArkts.Expression, + needMemo: boolean = false +): mirrorArkts.MethodDefinition { + const returnType: mirrorArkts.TypeNode | undefined = type?.clone(); + if (needMemo && findCanAddMemoFromTypeAnnotation(returnType)) { + addMemoAnnotation(returnType); + } + const body = mirrorArkts.factory.createBlockStatement([mirrorArkts.factory.createReturnStatement(returns)]); + const scriptFunction = mirrorArkts.factory.createScriptFunction( + body, + undefined, + [], + returnType, + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ); + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET, + mirrorArkts.factory.createIdentifier(name), + mirrorArkts.factory.createFunctionExpression(scriptFunction.id, scriptFunction), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); +} -// export function createSetter( -// name: string, -// type: mirrorArkts.TypeNode | undefined, -// left: mirrorArkts.Expression, -// right: mirrorArkts.AstNode, -// needMemo: boolean = false -// ): mirrorArkts.MethodDefinition { -// const body = mirrorArkts.factory.createBlock([ -// mirrorArkts.factory.createExpressionStatement( -// mirrorArkts.factory.createAssignmentExpression( -// left, -// mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, -// right -// ) -// ), -// ]); -// const param: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createParameterDeclaration( -// mirrorArkts.factory.createIdentifier('value', type?.clone()), -// undefined -// ); -// if (needMemo && findCanAddMemoFromParameter(param)) { -// addMemoAnnotation(param); -// } -// const scriptFunction = mirrorArkts.factory.createScriptFunction( -// body, -// mirrorArkts.FunctionSignature.createFunctionSignature(undefined, [param], undefined, false), -// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, -// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC -// ); - -// return mirrorArkts.factory.createMethodDefinition( -// mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, -// mirrorArkts.factory.createIdentifier(name), -// scriptFunction, -// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, -// false -// ); -// } +export function createSetter( + name: string, + type: mirrorArkts.TypeNode | undefined, + left: mirrorArkts.Expression, + right: mirrorArkts.AstNode, + needMemo: boolean = false +): mirrorArkts.MethodDefinition { + const body = mirrorArkts.factory.createBlockStatement([ + mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createAssignmentExpression( + left, + right as mirrorArkts.Expression, + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + ) + ), + ]); + const param: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier('value', type?.clone()), + false + ); + if (needMemo && findCanAddMemoFromParameter(param)) { + addMemoAnnotation(param); + } + const scriptFunction = mirrorArkts.factory.createScriptFunction( + body, + undefined, + [param], + undefined, + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ); -// export function createSetter2( -// name: string, -// type: mirrorArkts.TypeNode | undefined, -// statement: mirrorArkts.AstNode -// ): mirrorArkts.MethodDefinition { -// const body = mirrorArkts.factory.createBlock([statement]); -// const param: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createParameterDeclaration( -// mirrorArkts.factory.createIdentifier('value', type?.clone()), -// undefined -// ); -// const scriptFunction = mirrorArkts.factory.createScriptFunction( -// body, -// mirrorArkts.FunctionSignature.createFunctionSignature(undefined, [param], undefined, false), -// mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, -// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC -// ); - -// return mirrorArkts.factory.createMethodDefinition( -// mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, -// mirrorArkts.factory.createIdentifier(name), -// scriptFunction, -// mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, -// false -// ); -// } + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, + mirrorArkts.factory.createIdentifier(name), + mirrorArkts.factory.createFunctionExpression(scriptFunction.id, scriptFunction), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); +} -// export function generateThisBackingValue( -// name: string, -// optional: boolean = false, -// nonNull: boolean = false -// ): mirrorArkts.MemberExpression { -// const member: mirrorArkts.Expression = generateThisBacking(name, optional, nonNull); -// return mirrorArkts.factory.createMemberExpression( -// member, -// mirrorArkts.factory.createIdentifier('value'), -// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, -// false, -// false -// ); -// } +export function createSetter2( + name: string, + type: mirrorArkts.TypeNode | undefined, + statement: mirrorArkts.AstNode +): mirrorArkts.MethodDefinition { + const body = mirrorArkts.factory.createBlockStatement([statement as mirrorArkts.Statement]); + const param: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier('value', type?.clone()), + false + ); + const scriptFunction = mirrorArkts.factory.createScriptFunction( + body, + undefined, + [param], + undefined, + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ); -// export function generateThisBacking( -// name: string, -// optional: boolean = false, -// nonNull: boolean = false -// ): mirrorArkts.Expression { -// const member: mirrorArkts.Expression = mirrorArkts.factory.createMemberExpression( -// mirrorArkts.factory.createThisExpression(), -// mirrorArkts.factory.createIdentifier(`${name}`), -// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, -// false, -// optional -// ); -// return nonNull ? mirrorArkts.factory.createTSNonNullExpression(member) : member; -// } + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, + mirrorArkts.factory.createIdentifier(name), + mirrorArkts.factory.createFunctionExpression(scriptFunction.id, scriptFunction), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); +} -// function getValueStr(node: mirrorArkts.AstNode): string | undefined { -// if (!mirrorArkts.isClassProperty(node) || !node.value) return undefined; -// return mirrorArkts.isStringLiteral(node.value) ? node.value.str : undefined; -// } +export function generateThisBackingValue( + name: string, + optional: boolean = false, + nonNull: boolean = false +): mirrorArkts.MemberExpression { + const member: mirrorArkts.Expression = generateThisBacking(name, optional, nonNull); + return mirrorArkts.factory.createMemberExpression( + member, + mirrorArkts.factory.createIdentifier('value'), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); +} -// function getAnnotationValue(anno: mirrorArkts.AnnotationUsage, decoratorName: DecoratorNames): string | undefined { -// const isSuitableAnnotation: boolean = -// !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; -// if (isSuitableAnnotation && anno.properties.length === 1) { -// return getValueStr(anno.properties.at(0)!); -// } -// return undefined; -// } +export function generateThisBacking( + name: string, + optional: boolean = false, + nonNull: boolean = false +): mirrorArkts.Expression { + const member: mirrorArkts.Expression = mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier(`${name}`), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + optional + ); + return nonNull ? mirrorArkts.factory.createTSNonNullExpression(member) : member; +} -// export function getValueInAnnotation(node: mirrorArkts.ClassProperty, decoratorName: DecoratorNames): string | undefined { -// const annotations: readonly mirrorArkts.AnnotationUsage[] = node.annotations; -// for (let i = 0; i < annotations.length; i++) { -// const anno: mirrorArkts.AnnotationUsage = annotations[i]; -// const str: string | undefined = getAnnotationValue(anno, decoratorName); -// if (!!str) { -// return str; -// } -// } -// return undefined; -// } +function getValueStr(node: mirrorArkts.AstNode): string | undefined { + if (!mirrorArkts.isClassProperty(node) || !node.value) return undefined; + return mirrorArkts.isStringLiteral(node.value) ? node.value.str : undefined; +} -// export interface ProvideOptions { -// alias: string; -// allowOverride: boolean; -// } +function getAnnotationValue(anno: mirrorArkts.AnnotationUsage, decoratorName: DecoratorNames): string | undefined { + const isSuitableAnnotation: boolean = + !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; + if (isSuitableAnnotation && anno.properties.length === 1) { + return getValueStr(anno.properties.at(0)!); + } + return undefined; +} -// export function getValueInProvideAnnotation(node: mirrorArkts.ClassProperty): ProvideOptions | undefined { -// const annotations: readonly mirrorArkts.AnnotationUsage[] = node.annotations; -// for (let i = 0; i < annotations.length; i++) { -// const anno: mirrorArkts.AnnotationUsage = annotations[i]; -// if (anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === DecoratorNames.PROVIDE) { -// const alias: string = getValueInObjectAnnotation(anno, DecoratorNames.PROVIDE, 'alias'); -// const allowOverride: boolean = getValueInObjectAnnotation(anno, DecoratorNames.PROVIDE, 'allowOverride') -// ? true -// : false; -// return { alias, allowOverride }; -// } -// } -// return undefined; -// } +export function getValueInAnnotation(node: mirrorArkts.ClassProperty, decoratorName: DecoratorNames): string | undefined { + const annotations: readonly mirrorArkts.AnnotationUsage[] = node.annotations; + for (let i = 0; i < annotations.length; i++) { + const anno: mirrorArkts.AnnotationUsage = annotations[i]; + const str: string | undefined = getAnnotationValue(anno, decoratorName); + if (!!str) { + return str; + } + } + return undefined; +} -// function getValueInObjectAnnotation(anno: mirrorArkts.AnnotationUsage, decoratorName: DecoratorNames, key: string): any { -// const isSuitableAnnotation: boolean = -// !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; -// if (!isSuitableAnnotation) { -// return undefined; -// } -// const keyItem: mirrorArkts.AstNode | undefined = anno.properties.find( -// (annoProp: mirrorArkts.AstNode) => -// mirrorArkts.isClassProperty(annoProp) && -// annoProp.key && -// mirrorArkts.isIdentifier(annoProp.key) && -// annoProp.key.name === key -// ); -// if (keyItem && mirrorArkts.isClassProperty(keyItem) && keyItem.value) { -// return getDifferentAnnoTypeValue(keyItem.value); -// } -// return undefined; -// } +export interface ProvideOptions { + alias: string; + allowOverride: boolean; +} -// function getDifferentAnnoTypeValue(value: mirrorArkts.Expression): string | boolean { -// if (mirrorArkts.isBooleanLiteral(value)) { -// return value.value; -// } else if (mirrorArkts.isStringLiteral(value)) { -// return value.str; -// } -// return value.dumpSrc(); -// } +export function getValueInProvideAnnotation(node: mirrorArkts.ClassProperty): ProvideOptions | undefined { + const annotations: readonly mirrorArkts.AnnotationUsage[] = node.annotations; + for (let i = 0; i < annotations.length; i++) { + const anno: mirrorArkts.AnnotationUsage = annotations[i]; + if (anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === DecoratorNames.PROVIDE) { + const alias: string = getValueInObjectAnnotation(anno, DecoratorNames.PROVIDE, 'alias'); + const allowOverride: boolean = getValueInObjectAnnotation(anno, DecoratorNames.PROVIDE, 'allowOverride') + ? true + : false; + return { alias, allowOverride }; + } + } + return undefined; +} -// export function generateGetOrSetCall(beforCall: mirrorArkts.AstNode, type: GetSetTypes) { -// return mirrorArkts.factory.createCallExpression( -// mirrorArkts.factory.createMemberExpression( -// beforCall, -// mirrorArkts.factory.createIdentifier(type), -// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, -// false, -// false -// ), -// undefined, -// type === 'set' ? [mirrorArkts.factory.createIdentifier('value')] : undefined, -// undefined -// ); -// } +function getValueInObjectAnnotation(anno: mirrorArkts.AnnotationUsage, decoratorName: DecoratorNames, key: string): any { + const isSuitableAnnotation: boolean = + !!anno.expr && mirrorArkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; + if (!isSuitableAnnotation) { + return undefined; + } + const keyItem: mirrorArkts.AstNode | undefined = anno.properties.find( + (annoProp: mirrorArkts.AstNode) => + mirrorArkts.isClassProperty(annoProp) && + annoProp.key && + mirrorArkts.isIdentifier(annoProp.key) && + annoProp.key.name === key + ); + if (keyItem && mirrorArkts.isClassProperty(keyItem) && keyItem.value) { + return getDifferentAnnoTypeValue(keyItem.value); + } + return undefined; +} -// export function generateToRecord(newName: string, originalName: string): mirrorArkts.Property { -// return mirrorArkts.Property.createProperty( -// mirrorArkts.factory.createStringLiteral(originalName), -// mirrorArkts.factory.createBinaryExpression( -// mirrorArkts.factory.createMemberExpression( -// mirrorArkts.factory.createIdentifier('paramsCasted'), -// mirrorArkts.factory.createIdentifier(originalName), -// mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, -// false, -// false -// ), -// mirrorArkts.ETSNewClassInstanceExpression.createETSNewClassInstanceExpression( -// mirrorArkts.factory.createTypeReference( -// mirrorArkts.factory.createTypeReferencePart(mirrorArkts.factory.createIdentifier('Object')) -// ), -// [] -// ), -// mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING -// ) -// ); -// } +function getDifferentAnnoTypeValue(value: mirrorArkts.Expression): string | boolean { + if (mirrorArkts.isBooleanLiteral(value)) { + return value.value; + } else if (mirrorArkts.isStringLiteral(value)) { + return value.str; + } + return value.dumpSrc(); +} -// // CACHE -// export interface PropertyCachedBody { -// initializeBody?: mirrorArkts.AstNode[]; -// updateBody?: mirrorArkts.AstNode[]; -// toRecordBody?: mirrorArkts.Property[]; -// } +export function generateGetOrSetCall(beforCall: mirrorArkts.AstNode, type: GetSetTypes) { + return mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + beforCall as mirrorArkts.Expression, + mirrorArkts.factory.createIdentifier(type), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + type === 'set' ? [mirrorArkts.factory.createIdentifier('value')] : [], + undefined, + ); +} -// export class PropertyCache { -// private _cache: Map; -// private static instance: PropertyCache; +export function generateToRecord(newName: string, originalName: string): mirrorArkts.Property { + return mirrorArkts.factory.createProperty( + mirrorArkts.Es2pandaPropertyKind.PROPERTY_KIND_INIT, + mirrorArkts.factory.createStringLiteral(originalName), + mirrorArkts.factory.createBinaryExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createIdentifier('paramsCasted'), + mirrorArkts.factory.createIdentifier(originalName), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + mirrorArkts.ETSNewClassInstanceExpression.createETSNewClassInstanceExpression( + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart(mirrorArkts.factory.createIdentifier('Object')) + ), + [] + ), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING + ), + false, + false + ); +} -// private constructor() { -// this._cache = new Map(); -// } +// CACHE +export interface PropertyCachedBody { + initializeBody?: mirrorArkts.AstNode[]; + updateBody?: mirrorArkts.AstNode[]; + toRecordBody?: mirrorArkts.Property[]; +} -// static getInstance(): PropertyCache { -// if (!this.instance) { -// this.instance = new PropertyCache(); -// } -// return this.instance; -// } +export class PropertyCache { + private _cache: Map; + private static instance: PropertyCache; -// reset(): void { -// this._cache.clear(); -// } + private constructor() { + this._cache = new Map(); + } -// getInitializeBody(name: string): mirrorArkts.AstNode[] { -// return this._cache.get(name)?.initializeBody ?? []; -// } + static getInstance(): PropertyCache { + if (!this.instance) { + this.instance = new PropertyCache(); + } + return this.instance; + } -// getUpdateBody(name: string): mirrorArkts.AstNode[] { -// return this._cache.get(name)?.updateBody ?? []; -// } + reset(): void { + this._cache.clear(); + } -// getToRecordBody(name: string): mirrorArkts.Property[] { -// return this._cache.get(name)?.toRecordBody ?? []; -// } + getInitializeBody(name: string): mirrorArkts.AstNode[] { + return this._cache.get(name)?.initializeBody ?? []; + } -// collectInitializeStruct(name: string, initializeStruct: mirrorArkts.AstNode[]): void { -// const initializeBody = this._cache.get(name)?.initializeBody ?? []; -// const newInitializeBody = [...initializeBody, ...initializeStruct]; -// this._cache.set(name, { ...this._cache.get(name), initializeBody: newInitializeBody }); -// } + getUpdateBody(name: string): mirrorArkts.AstNode[] { + return this._cache.get(name)?.updateBody ?? []; + } -// collectUpdateStruct(name: string, updateStruct: mirrorArkts.AstNode[]): void { -// const updateBody = this._cache.get(name)?.updateBody ?? []; -// const newUpdateBody = [...updateBody, ...updateStruct]; -// this._cache.set(name, { ...this._cache.get(name), updateBody: newUpdateBody }); -// } + getToRecordBody(name: string): mirrorArkts.Property[] { + return this._cache.get(name)?.toRecordBody ?? []; + } -// collectToRecord(name: string, toRecord: mirrorArkts.Property[]): void { -// const toRecordBody = this._cache.get(name)?.toRecordBody ?? []; -// const newToRecordBody = [...toRecordBody, ...toRecord]; -// this._cache.set(name, { ...this._cache.get(name), toRecordBody: newToRecordBody }); -// } -// } + collectInitializeStruct(name: string, initializeStruct: mirrorArkts.AstNode[]): void { + const initializeBody = this._cache.get(name)?.initializeBody ?? []; + const newInitializeBody = [...initializeBody, ...initializeStruct]; + this._cache.set(name, { ...this._cache.get(name), initializeBody: newInitializeBody }); + } + + collectUpdateStruct(name: string, updateStruct: mirrorArkts.AstNode[]): void { + const updateBody = this._cache.get(name)?.updateBody ?? []; + const newUpdateBody = [...updateBody, ...updateStruct]; + this._cache.set(name, { ...this._cache.get(name), updateBody: newUpdateBody }); + } + + collectToRecord(name: string, toRecord: mirrorArkts.Property[]): void { + const toRecordBody = this._cache.get(name)?.toRecordBody ?? []; + const newToRecordBody = [...toRecordBody, ...toRecord]; + this._cache.set(name, { ...this._cache.get(name), toRecordBody: newToRecordBody }); + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/struct-translators/factory.ts b/arkui-plugins/mirror-replace/ui-plugins/struct-translators/factory.ts new file mode 100644 index 000000000..6c7b92c93 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/struct-translators/factory.ts @@ -0,0 +1,937 @@ +/* + * 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 mirrorArkts from '@koalaui/libarkts-mirror'; +import { + CustomComponentNames, + getCustomComponentOptionsName, + getGettersFromClassDecl, + getTypeNameFromTypeParameter, + getTypeParamsFromClassDecl, + isCustomComponentInterface, + isKnownMethodDefinition +} from '../utils'; +import { factory as uiFactory } from '../ui-factory'; +import { factory as propertyFactory } from '../property-translators/factory'; +import { collect, filterDefined } from '../../common/arkts-utils'; +import { + classifyObservedTrack, + classifyProperty, + classifyPropertyInInterface, + ClassScopeInfo, + InterfacePropertyTranslator, + PropertyTranslator, +} from '../property-translators'; +import { + CustomComponentScopeInfo, + isEtsGlobalClass, + ResourceInfo, + checkRawfileResource, + generateResourceModuleName, + generateResourceBundleName, + isDynamicName, + preCheckResourceData, + ResourceParameter, + getResourceParams, + isResourceNode, + isForEachCall, +} from './utils'; +import { collectStateManagementTypeImport, hasDecorator, PropertyCache } from '../property-translators/utils'; +import { ProjectConfig } from '../../common/plugin-context'; +import { ImportCollector } from '../../common/import-collector'; +import { + AnimationNames, + ARKUI_COMPONENT_COMMON_SOURCE_NAME, + DecoratorNames, + Dollars, + ModuleType, + StateManagementTypes, + RESOURCE_TYPE, +} from '../../common/predefines'; +import { ObservedTrackTranslator } from '../property-translators/observedTrack'; +import { addMemoAnnotation } from '../../collectors/memo-collectors/utils'; +import { generateArkUICompatible, isArkUICompatible } from '../interop/interop'; + +export class factory { + /** + * update class `constructor` to private. + */ + static setStructConstructorToPrivate(member: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + member.modifierFlags &= mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC; + member.modifierFlags &= mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PROTECTED; + member.modifierFlags |= mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE; + return member; + } + + /** + * create __initializeStruct method. + */ + static createInitializeStruct(optionsTypeName: string, scope: CustomComponentScopeInfo): mirrorArkts.MethodDefinition { + const updateKey: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier( + CustomComponentNames.COMPONENT_INITIALIZE_STRUCT + ); + + let body: mirrorArkts.BlockStatement | undefined; + let modifiers: mirrorArkts.Es2pandaModifierFlags = mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_ABSTRACT; + if (!scope.isDecl) { + body = mirrorArkts.factory.createBlockStatement(PropertyCache.getInstance().getInitializeBody(scope.name) as mirrorArkts.Statement[]); + modifiers = mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC; + } + const scriptFunction: mirrorArkts.ScriptFunction = mirrorArkts.factory + .createScriptFunction( + body, + undefined, + [uiFactory.createInitializersOptionsParameter(optionsTypeName), uiFactory.createContentParameter()], + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + modifiers, + undefined, + undefined + ) + .setIdent(updateKey); + + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + updateKey, + mirrorArkts.factory.createFunctionExpression(scriptFunction.id, scriptFunction), + modifiers, + false + ); + } + + /** + * create __updateStruct method. + */ + static createUpdateStruct(optionsTypeName: string, scope: CustomComponentScopeInfo): mirrorArkts.MethodDefinition { + const updateKey: mirrorArkts.Identifier = mirrorArkts.factory.createIdentifier( + CustomComponentNames.COMPONENT_UPDATE_STRUCT + ); + + let body: mirrorArkts.BlockStatement | undefined; + let modifiers: mirrorArkts.Es2pandaModifierFlags = mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_ABSTRACT; + if (!scope.isDecl) { + body = mirrorArkts.factory.createBlockStatement(PropertyCache.getInstance().getUpdateBody(scope.name) as mirrorArkts.Statement[]); + modifiers = mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC; + } + + const scriptFunction: mirrorArkts.ScriptFunction = mirrorArkts.factory + .createScriptFunction( + body, + undefined, + [uiFactory.createInitializersOptionsParameter(optionsTypeName)], + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + modifiers, + undefined, + undefined + ) + .setIdent(updateKey); + + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + updateKey, + mirrorArkts.factory.createFunctionExpression(scriptFunction.id, scriptFunction), + modifiers, + false + ); + } + + /** + * create __toRecord method when the component is decorated with @Reusable. + */ + static createToRecord(optionsTypeName: string, scope: CustomComponentScopeInfo): mirrorArkts.MethodDefinition { + const paramsCasted = factory.generateParamsCasted(optionsTypeName); + const returnRecord = mirrorArkts.factory.createReturnStatement( + mirrorArkts.ObjectExpression.createObjectExpression( + mirrorArkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, + PropertyCache.getInstance().getToRecordBody(scope.name), + false + ) + ); + const body: mirrorArkts.BlockStatement = mirrorArkts.factory.createBlockStatement([paramsCasted, returnRecord]); + + const params = mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier('params', factory.generateTypeReferenceWithTypeName('Object')), + false + ); + + const toRecordScriptFunction = mirrorArkts.factory.createScriptFunction( + body, + undefined, + [params], + factory.generateTypeRecord(), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ); + + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_CONSTRUCTOR, + mirrorArkts.factory.createIdentifier('__toRecord'), + mirrorArkts.factory.createFunctionExpression(toRecordScriptFunction.id, toRecordScriptFunction), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OVERRIDE, + false + ); + } + + /** + * generate `const paramsCasted = (params as )`. + */ + static generateParamsCasted(optionsTypeName: string): mirrorArkts.VariableDeclaration { + return mirrorArkts.factory.createVariableDeclaration( + mirrorArkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_CONST, + [ + mirrorArkts.factory.createVariableDeclarator( + mirrorArkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_CONST, + mirrorArkts.factory.createIdentifier('paramsCasted'), + mirrorArkts.TSAsExpression.createTSAsExpression( + mirrorArkts.factory.createIdentifier('params'), + factory.generateTypeReferenceWithTypeName(optionsTypeName), + false + ) + ), + ] + ); + } + + /** + * generate Record type. + */ + static generateTypeRecord(): mirrorArkts.ETSTypeReference { + return mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier('Record'), + mirrorArkts.factory.createTSTypeParameterInstantiation([ + factory.generateTypeReferenceWithTypeName('string'), + factory.generateTypeReferenceWithTypeName('Object'), + ]) + ) + ); + } + + /** + * create type reference with type name, e.g. number. + */ + static generateTypeReferenceWithTypeName(typeName: string): mirrorArkts.ETSTypeReference { + return mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart(mirrorArkts.factory.createIdentifier(typeName)) + ); + } + + /** + * create type reference with type name, e.g. number. + */ + static updateCustomComponentClass( + definition: mirrorArkts.ClassDefinition, + members: mirrorArkts.AstNode[] + ): mirrorArkts.ClassDefinition { + return mirrorArkts.factory.updateClassDefinition( + definition, + definition.ident, + definition.typeParams, + definition.superTypeParams, + definition.implements, + undefined, + definition.super, + members, + definition.modifiers, + mirrorArkts.classDefinitionFlags(definition) + ); + } + + /** + * add headers for animation & @AnimatableExtend in CommonMethod + */ + static modifyExternalComponentCommon(node: mirrorArkts.TSInterfaceDeclaration): mirrorArkts.TSInterfaceDeclaration { + const animationStart = factory.createAnimationMethod(AnimationNames.ANIMATION_START); + const animationStop = factory.createAnimationMethod(AnimationNames.ANIMATION_STOP); + const createOrSetAniProperty = factory.createOrSetAniProperty(); + const updatedBody = mirrorArkts.factory.updateInterfaceBody(node.body!, [ + animationStart, + animationStop, + createOrSetAniProperty, + ...node.body!.body, + ]); + return mirrorArkts.factory.updateInterfaceDeclaration( + node, + node.extends, + node.id, + node.typeParams, + updatedBody, + node.isStatic, + node.isFromExternal + ); + } + + /** + * helper to create value parameter for AnimatableExtend methods + */ + static createAniExtendValueParam(): mirrorArkts.ETSParameterExpression { + const numberType = uiFactory.createTypeReferenceFromString('number'); + const AnimatableArithmeticType = mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(AnimationNames.ANIMATABLE_ARITHMETIC), + mirrorArkts.factory.createTSTypeParameterInstantiation([uiFactory.createTypeReferenceFromString('T')]) + ) + ); + return mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier( + 'value', + mirrorArkts.factory.createETSUnionType([numberType, AnimatableArithmeticType]) + ), + false + ); + } + + /** + * generate __createOrSetAnimatableProperty(...) for AnimatableExtend + */ + static createOrSetAniProperty(): mirrorArkts.MethodDefinition { + const funcNameParam: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier('functionName', uiFactory.createTypeReferenceFromString('string')), + false + ); + const cbParam = mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier( + 'callback', + mirrorArkts.factory.createETSFunctionType( + undefined, + [factory.createAniExtendValueParam()], + mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW + ) + ), + false + ); + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + mirrorArkts.factory.createIdentifier(AnimationNames.CREATE_OR_SET_ANIMATABLEPROPERTY), + mirrorArkts.factory.createFunctionExpression(undefined, uiFactory.createScriptFunction({ + typeParams: mirrorArkts.factory.createTSTypeParameterDeclaration( + [mirrorArkts.factory.createTypeParameter( + mirrorArkts.factory.createIdentifier('T'), + undefined, + undefined, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE)], + 0 + ), + params: [funcNameParam, factory.createAniExtendValueParam(), cbParam], + returnTypeAnnotation: mirrorArkts.factory.createETSPrimitiveType( + mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID + ), + flags: mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + modifiers: mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + })), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); + } + + /** + * generate animationStart(...) and animationStop(...) + */ + static createAnimationMethod(key: string): mirrorArkts.MethodDefinition { + const aniparams: mirrorArkts.Expression[] = [ + mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier( + 'value', + mirrorArkts.factory.createETSUnionType([ + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart(mirrorArkts.factory.createIdentifier('AnimateParam')) + ), + mirrorArkts.factory.createETSUndefinedType(), + ]) + ), + false + ), + ]; + const aniFunc = mirrorArkts.factory.createScriptFunction( + undefined, + undefined, aniparams, mirrorArkts.TSThisType.createTSThisType(), false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + undefined, + undefined + ); + return mirrorArkts.factory.createMethodDefinition( + mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + mirrorArkts.factory.createIdentifier(key), + mirrorArkts.factory.createFunctionExpression(aniFunc.id, aniFunc), + mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ); + } + + /** + * transform property members in custom-component class. + */ + static tranformPropertyMembers( + propertyTranslators: PropertyTranslator[], + optionsTypeName: string, + scope: CustomComponentScopeInfo + ): mirrorArkts.AstNode[] { + const propertyMembers = propertyTranslators.map((translator) => translator.translateMember()); + const collections = []; + if (!scope.hasInitializeStruct) { + collections.push(this.createInitializeStruct(optionsTypeName, scope)); + } + if (!scope.hasUpdateStruct) { + collections.push(this.createUpdateStruct(optionsTypeName, scope)); + } + if (!!scope.annotations?.reusable) { + collections.push(this.createToRecord(optionsTypeName, scope)); + } + return collect(...collections, ...propertyMembers); + } + + /** + * transform non-property members in custom-component class. + */ + static transformNonPropertyMembersInClass(member: mirrorArkts.AstNode, isDecl?: boolean): mirrorArkts.AstNode { + if (mirrorArkts.isMethodDefinition(member)) { + propertyFactory.addMemoToBuilderClassMethod(member); + if (isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI) && !isDecl) { + return this.setStructConstructorToPrivate(member); + } + if (isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_BUILD_ORI)) { + addMemoAnnotation(member.function!); + } + return member; + } + return member; + } + + /** + * transform members in custom-component class. + */ + static tranformClassMembers(node: mirrorArkts.ClassDeclaration, scope: CustomComponentScopeInfo): mirrorArkts.ClassDeclaration { + if (!node.definition) { + return node; + } + let classOptionsName: string | undefined; + if (scope.isDecl) { + const [_, classOptions] = getTypeParamsFromClassDecl(node); + classOptionsName = getTypeNameFromTypeParameter(classOptions); + } + const definition: mirrorArkts.ClassDefinition = node.definition; + const className: string | undefined = node.definition.ident?.name; + if (!className) { + throw new Error('Non Empty className expected for Component'); + } + + const propertyTranslators: PropertyTranslator[] = filterDefined( + definition.body.map((it) => classifyProperty(it, scope)) + ); + const translatedMembers: mirrorArkts.AstNode[] = this.tranformPropertyMembers( + propertyTranslators, + classOptionsName ?? getCustomComponentOptionsName(className), + scope + ); + const updateMembers: mirrorArkts.AstNode[] = definition.body + .filter((member) => !mirrorArkts.isClassProperty(member)) + .map((member: mirrorArkts.AstNode) => factory.transformNonPropertyMembersInClass(member, scope.isDecl)); + + const updateClassDef: mirrorArkts.ClassDefinition = this.updateCustomComponentClass(definition, [ + ...translatedMembers, + ...updateMembers, + ]); + return mirrorArkts.factory.updateClassDeclaration(node, updateClassDef); + } + + /** + * transform `$r` and `$rawfile` function calls. + */ + static transformResource( + resourceNode: mirrorArkts.CallExpression, + projectConfig: ProjectConfig | undefined, + resourceInfo: ResourceInfo + ): mirrorArkts.CallExpression { + if (!mirrorArkts.isIdentifier(resourceNode.callee) || !projectConfig) { + return resourceNode; + } + const resourceKind: Dollars = resourceNode.callee.name as Dollars; + if (mirrorArkts.isStringLiteral(resourceNode.arguments[0])) { + return factory.processStringLiteralResourceNode( + resourceNode, + resourceInfo, + projectConfig, + resourceKind, + resourceNode.arguments[0] + ); + } else if (resourceNode.arguments && resourceNode.arguments.length) { + return factory.generateTransformedResourceCall( + resourceNode, + getResourceParams( + -1, + resourceKind === Dollars.DOLLAR_RAWFILE ? RESOURCE_TYPE.rawfile : -1, + Array.from(resourceNode.arguments) + ), + '', + false, + projectConfig, + resourceKind + ); + } + return resourceNode; + } + + /* + * Process string Literal type arguments for resource node. + */ + static processStringLiteralResourceNode( + resourceNode: mirrorArkts.CallExpression, + resourceInfo: ResourceInfo, + projectConfig: ProjectConfig, + resourceKind: Dollars, + literalArg: mirrorArkts.StringLiteral + ): mirrorArkts.CallExpression { + const resourceData: string[] = literalArg.str.trim().split('.'); + const fromOtherModule: boolean = !!resourceData.length && /^\[.*\]$/g.test(resourceData[0]); + if (resourceKind === Dollars.DOLLAR_RAWFILE) { + checkRawfileResource(resourceNode, literalArg, fromOtherModule, resourceInfo.rawfile); + let resourceId: number = projectConfig.moduleType === ModuleType.HAR ? -1 : 0; + let resourceModuleName: string = ''; + if (resourceData && resourceData[0] && fromOtherModule) { + resourceId = -1; + resourceModuleName = resourceData[0]; + } + return factory.generateTransformedResourceCall( + resourceNode, + getResourceParams(resourceId, RESOURCE_TYPE.rawfile, [literalArg]), + resourceModuleName, + fromOtherModule, + projectConfig, + Dollars.DOLLAR_RAWFILE + ); + } else { + return factory.processStringLiteralDollarResourceNode( + resourceNode, + resourceInfo, + projectConfig, + resourceData, + fromOtherModule + ); + } + } + + /* + * Process string Literal type arguments for $r node. + */ + static processStringLiteralDollarResourceNode( + resourceNode: mirrorArkts.CallExpression, + resourceInfo: ResourceInfo, + projectConfig: ProjectConfig, + resourceData: string[], + fromOtherModule: boolean + ): mirrorArkts.CallExpression { + if ( + preCheckResourceData(resourceNode, resourceData, resourceInfo.resourcesList, fromOtherModule, projectConfig) + ) { + const resourceId: number = + projectConfig.moduleType === ModuleType.HAR || + fromOtherModule || + !resourceInfo.resourcesList[resourceData[0]] + ? -1 + : resourceInfo.resourcesList[resourceData[0]].get(resourceData[1])![resourceData[2]]; + return factory.generateTransformedResourceCall( + resourceNode, + getResourceParams( + resourceId, + RESOURCE_TYPE[resourceData[1].trim()], + projectConfig.moduleType === ModuleType.HAR || fromOtherModule + ? Array.from(resourceNode.arguments) + : Array.from(resourceNode.arguments.slice(1)) + ), + resourceData.length ? resourceData[0] : '', + fromOtherModule, + projectConfig, + Dollars.DOLLAR_RESOURCE + ); + } + return resourceNode; + } + + /* + * generate tramsformed resource node, e.g. {id, type, params, bundleName, moduleName}. + */ + static generateTransformedResourceCall( + resourceNode: mirrorArkts.CallExpression, + resourceParams: ResourceParameter, + resourceModuleName: string, + fromOtherModule: boolean, + projectConfig: ProjectConfig, + resourceKind: Dollars + ): mirrorArkts.CallExpression { + const transformedKey: string = + resourceKind === Dollars.DOLLAR_RESOURCE + ? Dollars.TRANSFORM_DOLLAR_RESOURCE + : Dollars.TRANSFORM_DOLLAR_RAWFILE; + ImportCollector.getInstance().collectImport(transformedKey); + const isDynamicBundleOrModule: boolean = isDynamicName(projectConfig); + const args: mirrorArkts.AstNode[] = [ + mirrorArkts.factory.createNumberLiteral(resourceParams.id), + mirrorArkts.factory.createNumberLiteral(resourceParams.type), + mirrorArkts.factory.createStringLiteral(generateResourceBundleName(projectConfig, isDynamicBundleOrModule)), + mirrorArkts.factory.createStringLiteral( + generateResourceModuleName(projectConfig, isDynamicBundleOrModule, resourceModuleName, fromOtherModule) + ), + ...resourceParams.params, + ]; + return mirrorArkts.factory.updateCallExpression( + resourceNode, + mirrorArkts.factory.createIdentifier(transformedKey), + args as mirrorArkts.Expression[], + undefined + ); + } + + /** + * transform members in interface. + */ + static tranformInterfaceMembers( + node: mirrorArkts.TSInterfaceDeclaration, + externalSourceName?: string + ): mirrorArkts.TSInterfaceDeclaration { + if (!node.id || !node.body) { + return node; + } + if (externalSourceName === ARKUI_COMPONENT_COMMON_SOURCE_NAME && node.id.name === 'CommonMethod') { + return factory.modifyExternalComponentCommon(node); + } + if (isCustomComponentInterface(node)) { + return factory.tranformCustomComponentInterfaceMembers(node); + } + return factory.tranformInterfaceBuildMember(node); + } + + static tranformInterfaceBuildMember(node: mirrorArkts.TSInterfaceDeclaration): mirrorArkts.TSInterfaceDeclaration { + const newBody: mirrorArkts.AstNode[] = node.body!.body.map((it) => { + if (mirrorArkts.isMethodDefinition(it)) { + propertyFactory.addMemoToBuilderClassMethod(it); + } + return it; + }); + return mirrorArkts.factory.updateInterfaceDeclaration( + node, + node.extends, + node.id, + node.typeParams, + mirrorArkts.factory.updateInterfaceBody(node.body!, newBody), + node.isStatic, + node.isFromExternal + ); + } + + /** + * transform members in custom-component related interface. + */ + static tranformCustomComponentInterfaceMembers(node: mirrorArkts.TSInterfaceDeclaration): mirrorArkts.TSInterfaceDeclaration { + const propertyTranslators: InterfacePropertyTranslator[] = filterDefined( + node.body!.body.map((it) => classifyPropertyInInterface(it)) + ); + + let shouldUpdate: boolean = false; + const newBody = propertyTranslators.map((translator) => { + const newProperty = translator.translateProperty(); + shouldUpdate ||= translator.modified; + return newProperty; + }); + + if (shouldUpdate) { + return mirrorArkts.factory.updateInterfaceDeclaration( + node, + node.extends, + node.id, + node.typeParams, + mirrorArkts.factory.updateInterfaceBody(node.body!, newBody), + node.isStatic, + node.isFromExternal + ); + } + + return node; + } + + static transformNormalClass(node: mirrorArkts.ClassDeclaration): mirrorArkts.ClassDeclaration { + if (!node.definition) { + return node; + } + if (isEtsGlobalClass(node)) { + const updatedBody = node.definition.body.map((member: mirrorArkts.AstNode) => { + mirrorArkts.isMethodDefinition(member) && propertyFactory.addMemoToBuilderClassMethod(member); + if (mirrorArkts.isMethodDefinition(member) && hasDecorator(member, DecoratorNames.ANIMATABLE_EXTEND)) { + member = mirrorArkts.factory.updateMethodDefinition( + member, + member.kind, + member.id, + mirrorArkts.factory.createFunctionExpression(undefined, factory.transformAnimatableExtend(member.function!)), + member.modifierFlags, + false + ); + } + return member; + }); + return mirrorArkts.factory.updateClassDeclaration( + node, + mirrorArkts.factory.updateClassDefinition( + node.definition, + node.definition.ident, + node.definition.typeParams, + node.definition.superTypeParams, + node.definition.implements, + undefined, + node.definition.super, + updatedBody, + node.definition.modifiers, + mirrorArkts.classDefinitionFlags(node.definition) + ) + ); + } + const newClassDef = factory.updateObservedTrackClassDef(node.definition); + return mirrorArkts.factory.updateClassDeclaration(node, newClassDef); + } + + static updateObservedTrackClassDef(node: mirrorArkts.ClassDefinition): mirrorArkts.ClassDefinition { + const isObserved: boolean = hasDecorator(node, DecoratorNames.OBSERVED); + const classHasTrack: boolean = node.body.some( + (member) => mirrorArkts.isClassProperty(member) && hasDecorator(member, DecoratorNames.TRACK) + ); + if (!isObserved && !classHasTrack) { + return node; + } + const updateClassDef: mirrorArkts.ClassDefinition = mirrorArkts.factory.updateClassDefinition( + node, + node.ident, + node.typeParams, + node.superTypeParams, + [ + ...node.implements, + mirrorArkts.TSClassImplements.createTSClassImplements( + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(StateManagementTypes.OBSERVED_OBJECT) + ) + ) + ), + mirrorArkts.TSClassImplements.createTSClassImplements( + mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(StateManagementTypes.SUBSCRIBED_WATCHES) + ) + ) + ), + ], + undefined, + node.super, + factory.observedTrackPropertyMembers(classHasTrack, node, isObserved), + node.modifiers, + mirrorArkts.classDefinitionFlags(node) + ); + collectStateManagementTypeImport(StateManagementTypes.OBSERVED_OBJECT); + return updateClassDef; + } + + static observedTrackPropertyMembers( + classHasTrack: boolean, + definition: mirrorArkts.ClassDefinition, + isObserved: boolean + ): mirrorArkts.AstNode[] { + const watchMembers: mirrorArkts.AstNode[] = propertyFactory.createWatchMembers(); + const v1RenderIdMembers: mirrorArkts.AstNode[] = propertyFactory.createV1RenderIdMembers(); + const conditionalAddRef: mirrorArkts.MethodDefinition = propertyFactory.conditionalAddRef(); + const getters: mirrorArkts.MethodDefinition[] = getGettersFromClassDecl(definition); + const classScopeInfo: ClassScopeInfo = { + isObserved: isObserved, + classHasTrack: classHasTrack, + getters: getters, + }; + const propertyTranslators: ObservedTrackTranslator[] = filterDefined( + definition.body.map((it) => classifyObservedTrack(it, classScopeInfo)) + ); + const propertyMembers = propertyTranslators.map((translator) => translator.translateMember()); + const nonClassPropertyOrGetter: mirrorArkts.AstNode[] = definition.body.filter( + (member) => + !mirrorArkts.isClassProperty(member) && + !( + mirrorArkts.isMethodDefinition(member) && + mirrorArkts.hasModifierFlag(member, mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_GETTER) + ) + ); + return [ + ...[...watchMembers, ...v1RenderIdMembers, conditionalAddRef], + ...(classHasTrack ? [] : [propertyFactory.createMetaInObservedClass()]), + ...collect(...propertyMembers), + ...nonClassPropertyOrGetter, + ...classScopeInfo.getters, + ]; + } + + /* + * helper for transformAnimatableExtend to create callback argument in __createAnimatableProperty + */ + static createAniExtendCbArg( + param: mirrorArkts.ETSParameterExpression, + originStatements: mirrorArkts.Statement[] + ): mirrorArkts.ArrowFunctionExpression { + const assignmentExpr = mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createAssignmentExpression( + param.ident!.clone(), + mirrorArkts.factory.createTSAsExpression(param.ident!.clone(), param.typeAnnotation as mirrorArkts.TypeNode, false), + mirrorArkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION + ) + ); + const numberType = uiFactory.createTypeReferenceFromString('number'); + const AnimatableArithmeticType = mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(AnimationNames.ANIMATABLE_ARITHMETIC), + mirrorArkts.factory.createTSTypeParameterInstantiation([param.typeAnnotation as mirrorArkts.TypeNode]) + ) + ); + ImportCollector.getInstance().collectImport(AnimationNames.ANIMATABLE_ARITHMETIC); + return mirrorArkts.factory.createArrowFunctionExpression( + uiFactory.createScriptFunction({ + body: mirrorArkts.factory.createBlockStatement([assignmentExpr, ...originStatements]), + params: [ + mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier( + param.ident!.name, + mirrorArkts.factory.createETSUnionType([numberType, AnimatableArithmeticType]) + ), + false + ), + ], + flags: mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + }) + ); + } + + /* + * transform @AnimatableExtend method + */ + static transformAnimatableExtend(node: mirrorArkts.ScriptFunction): mirrorArkts.ScriptFunction { + if (!mirrorArkts.isETSParameterExpression(node.params[1]) || !node.body || !mirrorArkts.isBlockStatement(node.body)) { + return node; + } + const funcName: mirrorArkts.StringLiteral = mirrorArkts.factory.createStringLiteral(node.id?.name!); + const paramValue: mirrorArkts.ETSParameterExpression = node.params[1]; + const originStatements: mirrorArkts.Statement[] = [...node.body.statements]; + const createOrSetStatement = mirrorArkts.factory.createExpressionStatement( + mirrorArkts.factory.createCallExpression( + mirrorArkts.factory.createMemberExpression( + mirrorArkts.factory.createThisExpression(), + mirrorArkts.factory.createIdentifier(AnimationNames.CREATE_OR_SET_ANIMATABLEPROPERTY), + mirrorArkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + [ + funcName, + paramValue.ident!, + factory.createAniExtendCbArg(paramValue, originStatements.slice(0, -1)), + ], + undefined, + ) + ); + return mirrorArkts.factory.updateScriptFunction( + node, + mirrorArkts.factory.createBlockStatement([createOrSetStatement, originStatements[originStatements.length - 1]]), + node.typeParams, + node.params, + node.returnTypeAnnotation, + node.hasReceiver, + node.flags, + node.modifierFlags, + undefined, + undefined + ); + } + + /* + * add arrow function type to arguments of call expression. + */ + static transformCallArguments(node: mirrorArkts.CallExpression): mirrorArkts.CallExpression { + if (!mirrorArkts.isArrowFunctionExpression(node.arguments[1])) { + return node; + } + const argTypeParam: mirrorArkts.Expression = node.arguments[1].function!.params[0]; + if ( + !mirrorArkts.isETSParameterExpression(argTypeParam) || + !argTypeParam.typeAnnotation || + !mirrorArkts.isTypeNode(argTypeParam.typeAnnotation) + ) { + return node; + } + const referenceType = uiFactory.createComplexTypeFromStringAndTypeParameter('Array', [ + argTypeParam.typeAnnotation.clone(), + ]); + const newArrowArg: mirrorArkts.ArrowFunctionExpression = mirrorArkts.factory.createArrowFunctionExpression( + uiFactory.createScriptFunction({ + body: mirrorArkts.factory.createBlockStatement([mirrorArkts.factory.createReturnStatement(node.arguments[0])]), + returnTypeAnnotation: referenceType, + flags: mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + modifiers: mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + }) + ); + return mirrorArkts.factory.updateCallExpression(node, node.callee, [ + newArrowArg, + ...node.arguments.slice(1), + ], node.typeParams); + } + + static AddArrowTypeForParameter(node: mirrorArkts.MethodDefinition): mirrorArkts.MethodDefinition { + if (node.function!.params.length < 2) { + return node; + } + const paramFirst = node.function!.params[0]; + if (!mirrorArkts.isETSParameterExpression(paramFirst) || !paramFirst.typeAnnotation || !mirrorArkts.isTypeNode(paramFirst.typeAnnotation)) { + return node; + } + const script = uiFactory.updateScriptFunction(node.function!, { + params: [ + mirrorArkts.factory.createETSParameterExpression( + mirrorArkts.factory.createIdentifier( + paramFirst.ident!.name, + uiFactory.createLambdaFunctionType([], paramFirst.typeAnnotation) + ), + false + ), + ...node.function!.params.slice(1), + ], + }); + return mirrorArkts.factory.updateMethodDefinition(node, node.kind, node.id, mirrorArkts.factory.createFunctionExpression(script.id, script), node.modifierFlags, false); + } + + static transformCallExpression( + node: mirrorArkts.CallExpression, + projectConfig: ProjectConfig | undefined, + resourceInfo: ResourceInfo + ): mirrorArkts.CallExpression { + if (mirrorArkts.isCallExpression(node) && isResourceNode(node)) { + return this.transformResource(node, projectConfig, resourceInfo); + } + if (mirrorArkts.isCallExpression(node) && isForEachCall(node)) { + return this.transformCallArguments(node); + } + if (isArkUICompatible(node)) { + return generateArkUICompatible(node as mirrorArkts.CallExpression); + } + return node; + } +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/struct-translators/utils.ts b/arkui-plugins/mirror-replace/ui-plugins/struct-translators/utils.ts new file mode 100644 index 000000000..46d0dfe11 --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/struct-translators/utils.ts @@ -0,0 +1,522 @@ +/* + * 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 fs from 'fs'; +import * as path from 'path'; +import * as mirrorArkts from '@koalaui/libarkts-mirror'; +import { CustomComponentInfo } from '../utils'; +import { matchPrefix } from '../../common/arkts-utils'; +import { + ARKUI_IMPORT_PREFIX_NAMES, + Dollars, + ModuleType, + DefaultConfiguration, + LogType, + RESOURCE_TYPE, + InnerComponentNames, + ARKUI_FOREACH_SOURCE_NAME, +} from '../../common/predefines'; +import { DeclarationCollector } from '../../common/declaration-collector'; +import { ProjectConfig } from '../../common/plugin-context'; +import { LogCollector } from '../../common/log-collector'; + +export type ScopeInfoCollection = { + customComponents: CustomComponentScopeInfo[]; +}; + +export type CustomComponentScopeInfo = CustomComponentInfo & { + hasInitializeStruct?: boolean; + hasUpdateStruct?: boolean; + hasReusableRebind?: boolean; +}; + +type ResourceMap = Map>; + +export interface ResourceList { + [key: string]: ResourceMap; +} + +export interface ResourceInfo { + resourcesList: ResourceList; + rawfile: Set; +} + +export interface LoaderJson { + hspResourcesMap: Record; +} + +export interface ResourceParameter { + id: number; + type: number; + params: mirrorArkts.Expression[]; +} + +export function getResourceParams(id: number, type: number, params: mirrorArkts.Expression[]): ResourceParameter { + return { id, type, params }; +} + +/** + * Determine whether it is ETSGLOBAL class. + * + * @param node class declaration node + */ +export function isEtsGlobalClass(node: mirrorArkts.ClassDeclaration): boolean { + if (node.definition?.ident?.name === 'ETSGLOBAL') { + return true; + } + return false; +} + +/** + * Determine whether it is resource node begin with `$r` or `$rawfile`. + * + * @param node call expression node + */ +export function isResourceNode(node: mirrorArkts.CallExpression, ignoreDecl: boolean = false): boolean { + if ( + !( + mirrorArkts.isIdentifier(node.callee) && + (node.callee.name === Dollars.DOLLAR_RESOURCE || node.callee.name === Dollars.DOLLAR_RAWFILE) + ) + ) { + return false; + } + if (!ignoreDecl) { + const decl = mirrorArkts.getDecl(node.callee); + if (!decl) { + return false; + } + const moduleName: string = mirrorArkts.getProgramFromAstNode(decl).moduleName; + if (!moduleName || !matchPrefix(ARKUI_IMPORT_PREFIX_NAMES, moduleName)) { + return false; + } + DeclarationCollector.getInstance().collect(decl); + } + return true; +} + +export function isForEachCall(node: mirrorArkts.CallExpression): boolean { + if ( + mirrorArkts.isIdentifier(node.callee) && + node.callee.name === InnerComponentNames.FOR_EACH && + node.arguments.length >= 2 + ) { + return true; + } + return false; +} + +/** + * Read the content of file 'loader.json'. + * + * @param projectConfig configuration information of the project. + */ +export function loadBuildJson(projectConfig: ProjectConfig | undefined): any { + if (!!projectConfig && projectConfig.buildLoaderJson && fs.existsSync(projectConfig.buildLoaderJson)) { + try { + const content = fs.readFileSync(projectConfig.buildLoaderJson, 'utf-8'); + const parsedContent = JSON.parse(content); + return parsedContent; + } catch (error) { + throw new Error('Error: The file is not a valid JSON format.'); + } + } + return {}; +} + +/** + * Initialize all resources information, including app resources, system resources, dependent hap resources and rawfile resources. + * + * @param projectConfig configuration information of the project. + * @param aceBuildJson content of the file 'loader.json'. + */ +export function initResourceInfo(projectConfig: ProjectConfig | undefined, aceBuildJson: LoaderJson): ResourceInfo { + let resourcesList: ResourceList = { + app: new Map>(), + sys: new Map>(), + }; + let rawfile: Set = new Set(); + if (!!projectConfig) { + readAppResource(resourcesList, projectConfig, aceBuildJson, rawfile); + } + return { resourcesList, rawfile }; +} + +/** + * Fill in the resource details to the resourcesList and rawfile. + * + * @param resourcesList resources including app, sys and hsp. + * @param projectConfig configuration information of the project. + * @param aceBuildJson content of the file 'loader.json'. + * @param rawfile rawfile resource name set. + */ +function readAppResource( + resourcesList: ResourceList, + projectConfig: ProjectConfig, + aceBuildJson: LoaderJson, + rawfile: Set +): void { + if ('hspResourcesMap' in aceBuildJson && aceBuildJson.hspResourcesMap) { + readHspResource(aceBuildJson, projectConfig, resourcesList); + } + readSystemResource(resourcesList); + if (!!projectConfig.appResource && fs.existsSync(projectConfig.appResource)) { + const appResource: string = fs.readFileSync(projectConfig.appResource, 'utf-8'); + const resourceArr: string[] = appResource.split(/\n/); + const resourceMap: ResourceMap = new Map>(); + processResourceArr(resourceArr, resourceMap, projectConfig.appResource); + for (let [key, value] of resourceMap) { + resourcesList.app.set(key, value); + } + } + if (projectConfig.rawFileResource) { + processResourcesRawfile(projectConfig, projectConfig.rawFileResource, rawfile); + } +} + +/** + * Fill in the resource details to the system resource. + * + * @param resourcesList resources including app, sys and hsp. + */ +function readSystemResource(resourcesList: ResourceList): void { + const sysResourcePath = path.resolve(__dirname, '../sysResource.js'); + if (fs.existsSync(sysResourcePath)) { + const sysObj: Record> = require(sysResourcePath).sys; + Object.keys(sysObj).forEach((key: string) => { + resourcesList.sys.set(key, sysObj[key]); + }); + } +} + +/** + * generate resource map. + * + * @param resourceArr lines of file 'ResourceTable.txt'. + * @param resourceMap A map that records the mapping of resource type, name and id. + * @param resourcePath path of file 'ResourceTable.txt'. + */ +function processResourceArr( + resourceArr: string[], + resourceMap: Map>, + resourcePath: string +): void { + for (let i = 0; i < resourceArr.length; i++) { + if (!resourceArr[i].length) { + continue; + } + const resourceData = resourceArr[i].split(/\s/); + if (resourceData.length === 3 && !isNaN(Number(resourceData[2]))) { + rescordResourceNameAndIdMap(resourceMap, resourceData); + } else { + console.warn(`ArkTS:WARN The format of file '${resourcePath}' is incorrect.`); + break; + } + } +} + +/** + * Construct the mapping of resource type, name and id with 'ResourceTable.txt'. + * + * @param resourceMap A map that records the mapping of resource type, name and id. + * @param resourceData array of type, name and id. + */ +function rescordResourceNameAndIdMap(resourceMap: Map>, resourceData: string[]): void { + if (resourceMap.get(resourceData[0])) { + const resourceNameAndId: Record = resourceMap.get(resourceData[0])!; + if (!resourceNameAndId[resourceData[1]] || resourceNameAndId[resourceData[1]] !== Number(resourceData[2])) { + resourceNameAndId[resourceData[1]] = Number(resourceData[2]); + } + } else { + let obj: Record = {}; + obj[resourceData[1]] = Number(resourceData[2]); + resourceMap.set(resourceData[0], obj); + } +} + +/** + * Fill in the resource details to the hsp resource. + * + * @param projectConfig configuration information of the project. + * @param aceBuildJson content of the file 'loader.json'. + * @param resourcesList resources including app, sys and hsp. + */ +function readHspResource(aceBuildJson: LoaderJson, projectConfig: ProjectConfig, resourcesList: ResourceList): void { + projectConfig.hspResourcesMap = true; + for (const hspName in aceBuildJson.hspResourcesMap) { + if (fs.existsSync(aceBuildJson.hspResourcesMap[hspName])) { + const resourceMap: ResourceMap = new Map>(); + resourcesList[hspName] = new Map>(); + const hspResource: string = fs.readFileSync(aceBuildJson.hspResourcesMap[hspName], 'utf-8'); + const resourceArr: string[] = hspResource.split(/\n/); + processResourceArr(resourceArr, resourceMap, aceBuildJson.hspResourcesMap[hspName]); + for (const [key, value] of resourceMap) { + resourcesList[hspName].set(key, value); + } + } + } +} + +/** + * Record the information of the rawfile resource. + * + * @param projectConfig configuration information of the project. + * @param rawfilePath path of rawfile directory. + * @param rawfileSet a set includes rawfile resource names. + * @param resourceName combination of existing directory names. + */ +function processResourcesRawfile( + projectConfig: ProjectConfig, + rawfilePath: string, + rawfileSet: Set, + resourceName: string = '' +): void { + if (fs.existsSync(projectConfig.rawFileResource) && fs.statSync(rawfilePath).isDirectory()) { + const files: string[] = fs.readdirSync(rawfilePath); + files.forEach((file: string) => { + if (fs.statSync(path.join(rawfilePath, file)).isDirectory()) { + processResourcesRawfile( + projectConfig, + path.join(rawfilePath, file), + rawfileSet, + resourceName ? resourceName + '/' + file : file + ); + } else { + addRawfileResourceToSet(rawfileSet, file, resourceName); + } + }); + } +} + +/** + * Add rawfile name to the collection of rawfile set. + * + * @param rawfileSet a set includes rawfile resource names. + * @param file rawfile name. + * @param resourceName combination of existing directory names. + */ +function addRawfileResourceToSet(rawfileSet: Set, file: string, resourceName: string = ''): void { + if (resourceName) { + rawfileSet.add(resourceName + '/' + file); + } else { + rawfileSet.add(file); + } +} + +/** + * Verify whether the rawfile resource exists in the current module. + * + * @param resourceNode resource node. + * @param rawfileStr rawfile string. + * @param fromOtherModule flag about whether it is a resource for other modules. + * @param rawfileSet a set that records all the rawfile resources. + */ +export function checkRawfileResource( + resourceNode: mirrorArkts.CallExpression, + rawfileStr: mirrorArkts.StringLiteral, + fromOtherModule: boolean, + rawfileSet: Set +): void { + if (!fromOtherModule && !rawfileSet.has(rawfileStr.str)) { + LogCollector.getInstance().collectLogInfo({ + type: LogType.ERROR, + node: resourceNode, + message: `No such '${rawfileStr.str}' resource in current module.`, + code: '10904333', + }); + } +} + +/** + * Check the format and the existance of resource string literal. + * + * @param resourceData array of resource string literals. + * @param resourcesList resources including app, sys and hsp. + * @param literalArg string literal argument node. + * @param fromOtherModule flag about whether it is a resource for other modules. + * @param projectConfig configuration information of the project. + */ +export function preCheckResourceData( + resourceNode: mirrorArkts.CallExpression, + resourceData: string[], + resourcesList: ResourceList, + fromOtherModule: boolean, + projectConfig: ProjectConfig +): boolean { + let code: string | undefined; + let message: string | undefined; + if (resourceData.length !== 3) { + message = 'The input parameter is not supported.'; + code = '10905332'; + } + if (!RESOURCE_TYPE[resourceData[1]]) { + message = `The resource type ${resourceData[1]} is not supported.`; + code = '10906334'; + } + if (!!code && !!message) { + LogCollector.getInstance().collectLogInfo({ + type: LogType.ERROR, + node: resourceNode, + message: message, + code: code, + }); + return false; + } + return preCheckResourceDataExistance(resourceNode, resourceData, resourcesList, fromOtherModule, projectConfig); +} + +/** + * Check the existance of resource string literal when the format of the string literal is correct. + * + * @param resourceData array of resource string literals. + * @param resourcesList resources including app, sys and hsp. + * @param literalArg string literal argument node. + * @param fromOtherModule flag about whether it is a resource for other modules. + * @param projectConfig configuration information of the project. + */ +export function preCheckResourceDataExistance( + resourceNode: mirrorArkts.CallExpression, + resourceData: string[], + resourcesList: ResourceList, + fromOtherModule: boolean, + projectConfig: ProjectConfig +): boolean { + if (fromOtherModule) { + if (/^\[.*\]$/.test(resourceData[0]) && projectConfig.hspResourcesMap) { + const resourceDataFirst: string = resourceData[0].replace(/^\[/, '').replace(/\]$/, '').trim(); + return resourceCheck(resourceNode, resourceData, resourcesList, true, resourceDataFirst, false); + } else { + return resourceCheck(resourceNode, resourceData, resourcesList, false, resourceData[0], true); + } + } else { + return resourceCheck(resourceNode, resourceData, resourcesList, false, resourceData[0], false); + } +} + +/** + * Verify whether the app resource exists in the current module. + * + * @param resourceNode resource node. + * @param resourceData array of resource string literals. + * @param resourcesList resources including app, sys and hsp. + * @param isHarHspResourceModule flag about whether it is from hsp or har module. + * @param resourceDataFirst the first element of resource string literals. + * @param noHspResourcesMap the non-existence of hspResourcesMap. + */ +function resourceCheck( + resourceNode: mirrorArkts.CallExpression, + resourceData: string[], + resourcesList: ResourceList, + isHarHspResourceModule: boolean, + resourceDataFirst: string, + noHspResourcesMap: boolean +): boolean { + let checkResult: boolean = true; + const logType: LogType = isHarHspResourceModule ? LogType.WARN : LogType.ERROR; + let code: string | undefined; + let message: string | undefined; + if (!noHspResourcesMap && !resourcesList[resourceDataFirst]) { + code = '10903331'; + message = `Unknown resource source '${resourceDataFirst}'.`; + checkResult = isHarHspResourceModule ? checkResult : false; + } else if (!noHspResourcesMap && !resourcesList[resourceDataFirst].get(resourceData[1])) { + code = '10903330'; + message = `Unknown resource type '${resourceData[1]}'.`; + checkResult = isHarHspResourceModule ? checkResult : false; + } else if (!noHspResourcesMap && !resourcesList[resourceDataFirst].get(resourceData[1])![resourceData[2]]) { + code = '10903329'; + message = `Unknown resource name '${resourceData[2]}'.`; + checkResult = isHarHspResourceModule ? checkResult : false; + } + if (!!code && !!message) { + LogCollector.getInstance().collectLogInfo({ + type: logType, + node: resourceNode, + message: message, + code: code, + }); + } + return checkResult; +} + +/** + * generate bundleName for $r and $rawfile. + * + * @param projectConfig project config. + * @param isDynamicBundleOrModule a flag for determining whether to use dynamic module name and bundle name. + */ +export function generateResourceBundleName(projectConfig: ProjectConfig, isDynamicBundleOrModule: boolean): string { + if (projectConfig.resetBundleName || projectConfig.allowEmptyBundleName) { + return ''; + } + if (isDynamicBundleOrModule) { + return DefaultConfiguration.DYNAMIC_BUNDLE_NAME; + } + return projectConfig.moduleType === ModuleType.HAR + ? DefaultConfiguration.HAR_DEFAULT_BUNDLE_NAME + : projectConfig.bundleName + ? projectConfig.bundleName + : ''; +} + +/** + * generate moduleName for $r and $rawfile. + * + * @param projectConfig project config. + * @param isDynamicBundleOrModule a flag for determining whether to use dynamic module name and bundle name. + */ +export function generateResourceModuleName( + projectConfig: ProjectConfig, + isDynamicBundleOrModule: boolean = false, + resourceModuleName: string, + fromOtherModule: boolean +): string { + if (fromOtherModule && resourceModuleName) { + return resourceModuleName.replace(/^\[|\]$/g, ''); + } + if (isDynamicBundleOrModule) { + return DefaultConfiguration.DYNAMIC_MODULE_NAME; + } + return projectConfig.moduleType === ModuleType.HAR + ? DefaultConfiguration.HAR_DEFAULT_MODULE_NAME + : projectConfig.moduleName + ? projectConfig.moduleName + : ''; +} + +/** + * Determine whether to use dynamic module name and bundle name. + * + * @param projectConfig project config. + */ +export function isDynamicName(projectConfig: ProjectConfig): boolean { + const isByteCodeHar: boolean = projectConfig.moduleType === ModuleType.HAR && projectConfig.byteCodeHar; + const uiTransformOptimization: boolean = !!projectConfig.uiTransformOptimization; + return uiTransformOptimization ? uiTransformOptimization : isByteCodeHar; +} + +/** + * Determine whether the node is ForEach method declaration. + * + * @param node method definition node. + * @param sourceName external source name. + */ +export function isForEachDecl(node: mirrorArkts.MethodDefinition, sourceName: string | undefined): boolean { + const isForEach: boolean = !!node.id && node.id.name === InnerComponentNames.FOR_EACH; + const isMethodDecl: boolean = + !!node.function && + mirrorArkts.hasModifierFlag(node.function, mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE); + return isForEach && isMethodDecl && !!sourceName && sourceName === ARKUI_FOREACH_SOURCE_NAME; +} diff --git a/arkui-plugins/mirror-replace/ui-plugins/test.ts b/arkui-plugins/mirror-replace/ui-plugins/test.ts new file mode 100644 index 000000000..01ab4c6cd --- /dev/null +++ b/arkui-plugins/mirror-replace/ui-plugins/test.ts @@ -0,0 +1,10 @@ + +import * as mirrorArkts from '@koalaui/libarkts-mirror'; + +let func = mirrorArkts.arktsGlobal.es2panda._EmptyFunc; + +console.time("testNapi") +for (let i = 0; i < 10000; ++i) { + func(); +} +console.timeEnd("testNapi") \ No newline at end of file diff --git a/arkui-plugins/mirror-replace/ui-plugins/ui-factory.ts b/arkui-plugins/mirror-replace/ui-plugins/ui-factory.ts index 6dbd575db..0a588d44c 100644 --- a/arkui-plugins/mirror-replace/ui-plugins/ui-factory.ts +++ b/arkui-plugins/mirror-replace/ui-plugins/ui-factory.ts @@ -24,7 +24,7 @@ import { import { PartialExcept, PartialNested, PartialNestedExcept } from '../common/safe-types'; import { DecoratorNames } from '../common/predefines'; import { needDefiniteOrOptionalModifier } from './property-translators/utils'; -// import { addMemoAnnotation } from '../collectors/memo-collectors/utils'; +import { addMemoAnnotation } from '../collectors/memo-collectors/utils'; export interface ScriptFunctionConfiguration { key: mirrorArkts.Identifier | undefined; @@ -53,22 +53,22 @@ export interface IntrinsicAnnotationDeclarationConfiguration { } export class factory { - // /** - // * create `instance: ` as identifier - // */ - // static createInstanceIdentifier(typeName: string): mirrorArkts.Identifier { - // return mirrorArkts.factory.createIdentifier( - // BuilderLambdaNames.STYLE_ARROW_PARAM_NAME, - // factory.createTypeReferenceFromString(typeName) - // ); - // } + /** + * create `instance: ` as identifier + */ + static createInstanceIdentifier(typeName: string): mirrorArkts.Identifier { + return mirrorArkts.factory.createIdentifier( + BuilderLambdaNames.STYLE_ARROW_PARAM_NAME, + factory.createTypeReferenceFromString(typeName) + ); + } - // /** - // * create `instance: ` as parameter - // */ - // static createInstanceParameter(typeName: string): mirrorArkts.ETSParameterExpression { - // return mirrorArkts.factory.createParameterDeclaration(factory.createInstanceIdentifier(typeName), undefined); - // } + /** + * create `instance: ` as parameter + */ + static createInstanceParameter(typeName: string): mirrorArkts.ETSParameterExpression { + return mirrorArkts.factory.createETSParameterExpression(factory.createInstanceIdentifier(typeName), false); + } // /** // * create `(instance: ) => void` @@ -98,48 +98,48 @@ export class factory { // ); // } - // /** - // * create `initializers: | undefined` as identifier - // */ - // static createInitializerOptionsIdentifier(optionsName: string): mirrorArkts.Identifier { - // return mirrorArkts.factory.createIdentifier( - // CustomComponentNames.COMPONENT_INITIALIZERS_NAME, - // mirrorArkts.factory.createUnionType([ - // factory.createTypeReferenceFromString(optionsName), - // mirrorArkts.factory.createETSUndefinedType(), - // ]) - // ); - // } + /** + * create `initializers: | undefined` as identifier + */ + static createInitializerOptionsIdentifier(optionsName: string): mirrorArkts.Identifier { + return mirrorArkts.factory.createIdentifier( + CustomComponentNames.COMPONENT_INITIALIZERS_NAME, + mirrorArkts.factory.createETSUnionType([ + factory.createTypeReferenceFromString(optionsName), + mirrorArkts.factory.createETSUndefinedType(), + ]) + ); + } - // /** - // * create `initializers: | undefined` as parameter - // */ - // static createInitializersOptionsParameter(optionsName: string): mirrorArkts.ETSParameterExpression { - // return mirrorArkts.factory.createParameterDeclaration( - // factory.createInitializerOptionsIdentifier(optionsName), - // undefined - // ); - // } + /** + * create `initializers: | undefined` as parameter + */ + static createInitializersOptionsParameter(optionsName: string): mirrorArkts.ETSParameterExpression { + return mirrorArkts.factory.createETSParameterExpression( + factory.createInitializerOptionsIdentifier(optionsName), + false + ); + } - // /** - // * create `content: (() => void) | undefined` as identifier - // */ - // static createContentIdentifier(): mirrorArkts.Identifier { - // return mirrorArkts.factory.createIdentifier( - // BuilderLambdaNames.CONTENT_PARAM_NAME, - // mirrorArkts.factory.createUnionType([factory.createLambdaFunctionType(), mirrorArkts.factory.createETSUndefinedType()]) - // ); - // } + /** + * create `content: (() => void) | undefined` as identifier + */ + static createContentIdentifier(): mirrorArkts.Identifier { + return mirrorArkts.factory.createIdentifier( + BuilderLambdaNames.CONTENT_PARAM_NAME, + mirrorArkts.factory.createETSUnionType([factory.createLambdaFunctionType(), mirrorArkts.factory.createETSUndefinedType()]) + ); + } - // /** - // * create `@memo() content: (() => void) | undefined` as parameter - // */ - // static createContentParameter(): mirrorArkts.ETSParameterExpression { - // const contentParam: mirrorArkts.Identifier = factory.createContentIdentifier(); - // const param: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createParameterDeclaration(contentParam, undefined); - // addMemoAnnotation(param); - // return param; - // } + /** + * create `@memo() content: (() => void) | undefined` as parameter + */ + static createContentParameter(): mirrorArkts.ETSParameterExpression { + const contentParam: mirrorArkts.Identifier = factory.createContentIdentifier(); + const param: mirrorArkts.ETSParameterExpression = mirrorArkts.factory.createETSParameterExpression(contentParam, false); + addMemoAnnotation(param); + return param; + } /** * create type from string @@ -150,35 +150,33 @@ export class factory { ); } - // /** - // * create complex type from string and type parameter, e.g. `Set` - // */ - // static createComplexTypeFromStringAndTypeParameter(name: string, params: mirrorArkts.TypeNode[]): mirrorArkts.TypeNode { - // return mirrorArkts.factory.createTypeReference( - // mirrorArkts.factory.createTypeReferencePart( - // mirrorArkts.factory.createIdentifier(name), - // mirrorArkts.factory.createTSTypeParameterInstantiation(params) - // ) - // ); - // } + /** + * create complex type from string and type parameter, e.g. `Set` + */ + static createComplexTypeFromStringAndTypeParameter(name: string, params: mirrorArkts.TypeNode[]): mirrorArkts.TypeNode { + return mirrorArkts.factory.createETSTypeReference( + mirrorArkts.factory.createETSTypeReferencePart( + mirrorArkts.factory.createIdentifier(name), + mirrorArkts.factory.createTSTypeParameterInstantiation(params) + ) + ); + } - // /** - // * create `() => `. If returnType is not given, then using `void`. - // */ - // static createLambdaFunctionType( - // params?: mirrorArkts.Expression[], - // returnType?: mirrorArkts.TypeNode | undefined - // ): mirrorArkts.ETSFunctionType { - // return mirrorArkts.factory.createFunctionType( - // mirrorArkts.FunctionSignature.createFunctionSignature( - // undefined, - // params ?? [], - // returnType ?? mirrorArkts.factory.createPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), - // false - // ), - // mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW - // ); - // } + /** + * create `() => `. If returnType is not given, then using `void`. + */ + static createLambdaFunctionType( + params?: mirrorArkts.Expression[], + returnType?: mirrorArkts.TypeNode | undefined + ): mirrorArkts.ETSFunctionType { + return mirrorArkts.factory.createETSFunctionType( + undefined, + params ?? [], + returnType ?? mirrorArkts.factory.createETSPrimitiveType(mirrorArkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + mirrorArkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW + ); + } // /** // * create `import { as } ...`. @@ -190,33 +188,33 @@ export class factory { // ); // } - // /** - // * update ScriptFunction with configurations. - // */ - // static updateScriptFunction( - // original: mirrorArkts.ScriptFunction, - // config: Partial - // ): mirrorArkts.ScriptFunction { - // const newFunc: mirrorArkts.ScriptFunction = mirrorArkts.factory.updateScriptFunction( - // original, - // config.body ?? original.body, - // mirrorArkts.factory.createFunctionSignature( - // config.typeParams ?? original.typeParams, - // config.params ?? original.params, - // config.returnTypeAnnotation ?? original.returnTypeAnnotation, - // config.hasReceiver ?? original.hasReceiver - // ), - // config.flags ?? original.flags, - // config.modifiers ?? original.modifiers - // ); - // if (!!config.key) { - // newFunc.setIdent(config.key); - // } - // if (!!config.annotations) { - // newFunc.setAnnotations(config.annotations); - // } - // return newFunc; - // } + /** + * update ScriptFunction with configurations. + */ + static updateScriptFunction( + original: mirrorArkts.ScriptFunction, + config: Partial + ): mirrorArkts.ScriptFunction { + const newFunc: mirrorArkts.ScriptFunction = mirrorArkts.factory.updateScriptFunction( + original, + config.body ?? original.body, + config.typeParams ?? original.typeParams, + config.params ?? original.params, + config.returnTypeAnnotation ?? original.returnTypeAnnotation, + config.hasReceiver ?? original.hasReceiver, + config.flags ?? original.flags, + config.modifiers ?? original.modifierFlags, + undefined, + undefined + ); + if (!!config.key) { + newFunc.setIdent(config.key); + } + if (!!config.annotations) { + newFunc.setAnnotations(config.annotations); + } + return newFunc; + } /** * create ScriptFunction with configurations. @@ -236,31 +234,31 @@ export class factory { return newFunc; } - // /** - // * update MethodDefinition with configurations. - // */ - // static updateMethodDefinition( - // original: mirrorArkts.MethodDefinition, - // config: PartialNested - // ): mirrorArkts.MethodDefinition { - // const key: mirrorArkts.Identifier = config.key ?? original.name; - // const newFunc: mirrorArkts.ScriptFunction = factory.updateScriptFunction(original.scriptFunction, { - // ...config.function, - // key, - // }); - // const newMethod: mirrorArkts.MethodDefinition = mirrorArkts.factory.updateMethodDefinition( - // original, - // config.kind ?? original.kind, - // key, - // newFunc, - // config.modifiers ?? original.modifiers, - // config.isComputed ?? false - // ); - // if (!!config.overloads) { - // newMethod.setOverloads(config.overloads); - // } - // return newMethod; - // } + /** + * update MethodDefinition with configurations. + */ + static updateMethodDefinition( + original: mirrorArkts.MethodDefinition, + config: PartialNested + ): mirrorArkts.MethodDefinition { + const key: mirrorArkts.Identifier = config.key ?? original.id!; + const newFunc: mirrorArkts.ScriptFunction = factory.updateScriptFunction(original.function!, { + ...config.function, + key, + }); + const newMethod: mirrorArkts.MethodDefinition = mirrorArkts.factory.updateMethodDefinition( + original, + config.kind ?? original.kind, + key, + mirrorArkts.factory.createFunctionExpression(newFunc.id, newFunc), + config.modifiers ?? original.modifiers, + config.isComputed ?? false + ); + if (!!config.overloads) { + newMethod.setOverloads(config.overloads); + } + return newMethod; + } /** * create MethodDefinition with configurations. @@ -273,7 +271,7 @@ export class factory { const newMethod: mirrorArkts.MethodDefinition = mirrorArkts.factory.createMethodDefinition( config.kind ?? mirrorArkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_NONE, config.key!, - mirrorArkts.factory.createFunctionExpression(undefined, newFunc), + mirrorArkts.factory.createFunctionExpression(newFunc.id, newFunc), config.modifiers ?? mirrorArkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, config.isComputed ?? false ); diff --git a/arkui-plugins/mirror-replace/ui-plugins/utils.ts b/arkui-plugins/mirror-replace/ui-plugins/utils.ts index e7d401a20..e970b6e5f 100644 --- a/arkui-plugins/mirror-replace/ui-plugins/utils.ts +++ b/arkui-plugins/mirror-replace/ui-plugins/utils.ts @@ -166,6 +166,9 @@ export function isCustomComponentAnnotation( return false; } if (!ignoreDecl) { + /** + * judge if this Annotation is declared in ARK_UI module + */ const decl = mirrorArkts.getDecl(anno.expr); if (!decl) { return false; @@ -249,26 +252,26 @@ export function isComponentStruct(node: mirrorArkts.ETSStructDeclaration, scopeI * * @param node class declaration node */ -// export function isCustomComponentClass(node: mirrorArkts.ClassDeclaration, scopeInfo: CustomComponentInfo): boolean { -// if (!node.definition?.ident?.name) { -// return false; -// } -// const name: string = node.definition.ident.name; -// if (scopeInfo.isDecl) { -// return ( -// name === CustomComponentNames.COMPONENT_CLASS_NAME || name === CustomComponentNames.COMPONENT_V2_CLASS_NAME -// ); -// } -// return name === scopeInfo.name; -// } +export function isCustomComponentClass(node: mirrorArkts.ClassDeclaration, scopeInfo: CustomComponentInfo): boolean { + if (!node.definition?.ident?.name) { + return false; + } + const name: string = node.definition.ident.name; + if (scopeInfo.isDecl) { + return ( + name === CustomComponentNames.COMPONENT_CLASS_NAME || name === CustomComponentNames.COMPONENT_V2_CLASS_NAME + ); + } + return name === scopeInfo.name; +} -// export function isCustomComponentInterface(node: mirrorArkts.TSInterfaceDeclaration): boolean { -// const checkPrefix = !!node.id?.name.startsWith(CustomComponentNames.COMPONENT_INTERFACE_PREFIX); -// const checkComponent = node.annotations.some((anno) => -// isCustomComponentAnnotation(anno, StructDecoratorNames.COMPONENT) -// ); -// return checkPrefix && checkComponent; -// } +export function isCustomComponentInterface(node: mirrorArkts.TSInterfaceDeclaration): boolean { + const checkPrefix = !!node.id?.name.startsWith(CustomComponentNames.COMPONENT_INTERFACE_PREFIX); + const checkComponent = node.annotations.some((anno) => + isCustomComponentAnnotation(anno, StructDecoratorNames.COMPONENT) + ); + return checkPrefix && checkComponent; +} export function getCustomComponentOptionsName(className: string): string { return `${CustomComponentNames.COMPONENT_INTERFACE_PREFIX}${className}`; @@ -280,12 +283,12 @@ export function getCustomComponentOptionsName(className: string): string { * @param method method definition node * @param name specified method name */ -// export function isKnownMethodDefinition(method: mirrorArkts.MethodDefinition, name: string): boolean { -// if (!method || !mirrorArkts.isMethodDefinition(method)) { -// return false; -// } +export function isKnownMethodDefinition(method: mirrorArkts.MethodDefinition, name: string): boolean { + if (!method || !mirrorArkts.isMethodDefinition(method)) { + return false; + } -// // For now, we only considered matched method name. -// const isNameMatched: boolean = method.name?.name === name; -// return isNameMatched; -// } + // For now, we only considered matched method name. + const isNameMatched: boolean = method.id?.name === name; + return isNameMatched; +} -- Gitee