From 28942e87c6e04eaae242bdc050d5dadb1a50e767 Mon Sep 17 00:00:00 2001 From: xieziang Date: Tue, 24 Jun 2025 15:12:00 +0800 Subject: [PATCH 1/3] 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/3] 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 a52d6bc86472d1d2faf66df97d36ffc02f6c5739 Mon Sep 17 00:00:00 2001 From: xieziang Date: Mon, 7 Jul 2025 17:34:31 +0800 Subject: [PATCH 3/3] 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..ee92b4b7d 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 = "//developtools/ace_ets2bundle/test_koala_mirror.py" + outputs = [ "$target_gen_dir" ] + 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 (is_linux) { + deps += [":test_koala_mirror"] + } + 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