diff --git a/compiler/src/interop/compile_plugin.js b/compiler/src/interop/compile_plugin.js new file mode 100644 index 0000000000000000000000000000000000000000..23ee31e23a8922f3f4887548d7bdb3984654ca5c --- /dev/null +++ b/compiler/src/interop/compile_plugin.js @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 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 { initConfig } = require('./lib/interop/src/fast_build/common/init_config'); +const { getCleanConfig } = require('./lib/interop/main'); +const { etsTransform, createProgramPlugin } = require('./lib/interop/src/fast_build/ets_ui/rollup-plugin-ets-typescript'); +const { etsChecker } = require('./lib/interop/src/fast_build/ets_ui/rollup-plugin-ets-checker'); +const { apiTransform } = require('./lib/interop/src/fast_build/system_api/rollup-plugin-system-api'); +const { genAbc } = require('./lib/interop/src/fast_build/ark_compiler/rollup-plugin-gen-abc'); +const { watchChangeFiles } = require('./lib/interop/src/fast_build/common/rollup-plugin-watch-change'); +const { visualTransform } = require('./lib/interop/src/fast_build/visual/rollup-plugin-visual'); +const { terserPlugin } = require('./lib/interop/src/fast_build/ark_compiler/terser-plugin'); +const { babelPlugin } = require('./lib/interop/src/fast_build/ark_compiler/babel-plugin'); +const { JSBUNDLE, RELEASE } = require('./lib/interop/src/fast_build/ark_compiler/common/ark_define'); +const { generateConsumerObConfigFile } = require('./lib/interop/src/fast_build/ark_compiler/common/ob_config_resolver'); +const { etsStandaloneChecker } = require('./lib/interop/src/ets_checker'); +const { memoryMonitor } = require('./lib/interop/src/fast_build/meomry_monitor/rollup-plugin-memory-monitor'); + +exports.initConfig = initConfig; +exports.getCleanConfig = getCleanConfig; +exports.generateConsumerObConfigFile = generateConsumerObConfigFile; +exports.etsStandaloneChecker = etsStandaloneChecker; + +// list of functional plugins +exports.sdkPlugins = (projectConfig) => { + return [ + memoryMonitor(), + watchChangeFiles(), + etsChecker(), + visualTransform(), + etsTransform(), + apiTransform(), + genAbc(), + projectConfig.buildMode?.toLowerCase() === RELEASE && terserPlugin(), + projectConfig.compileMode === JSBUNDLE && babelPlugin(projectConfig), + createProgramPlugin() + ]; +}; +exports.sdkPluginsMap = { + 'memoryMonitor': 0, + 'watchChangeFiles': 1, + 'etsChecker': 2, + 'visualTransform': 3, + 'etsTransform': 4, + 'apiTransform': 5, + 'genAbc': 6, + 'terserPlugin': 7, + 'babelPlugin': 8, + 'createProgramPlugin': 9 +}; + +exports.resolveFileExtensions = []; diff --git a/compiler/src/interop/interop_config.ts b/compiler/src/interop/interop_config.ts new file mode 100644 index 0000000000000000000000000000000000000000..36ece15599e1c7081dd4d5aaec93b25159161d7b --- /dev/null +++ b/compiler/src/interop/interop_config.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 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 plugin_path = '../../compile_plugin.js' +export const run_declgen_standalone_path = '../fast_build/ark_compiler/interop/run_declgen_standalone.ts' +export const main_path = '../../main.js' +export const module_source_file_path = '../fast_build/ark_compiler/module/module_source_file.ts' +export const ets_checker_path = '../ets_checker.ts' +export const compile_plugin_path = '../../compile_plugin.js' +export const build_pipe_server_path = '../../server/build_pipe_server.js' +export const run_es2abc_standalone_path = '../fast_build/ark_compiler/interop/run_declgen_standalone.ts' \ No newline at end of file diff --git a/compiler/src/interop/main.js b/compiler/src/interop/main.js new file mode 100644 index 0000000000000000000000000000000000000000..c4a5661d0ddaadcb8f0774ccb36ce8488e35acaf --- /dev/null +++ b/compiler/src/interop/main.js @@ -0,0 +1,1211 @@ +/* + * Copyright (c) 2020 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 crypto = require('crypto'); +const JSON5 = require('json5'); + +const { + readFile, + writeFileSync, + resourcesRawfile, + getStoredFileInfo +} = require('./lib/interop/src/utils'); + +const { + COLD_RELOAD_MODE, + ES2ABC, + FAIL, + TEST_RUNNER_DIR_SET, + TS2ABC, + WORKERS_DIR +} = require('./lib/interop/src/pre_define'); + +const { + checkAotConfig +} = require('./lib/interop/src/gen_aot'); + +const { + configure, + getLogger +} = require('log4js'); + +configure({ + appenders: { 'ETS': {type: 'stderr', layout: {type: 'messagePassThrough'}}}, + categories: {'default': {appenders: ['ETS'], level: 'info'}} +}); +const logger = getLogger('ETS'); + +const { + initMixCompileHar, + isRemoteModule, + isMixCompile, + getRootPackageJsonPath +} = require('./lib/interop/src/fast_build/ark_compiler/interop/interop_manager'); + +let staticPreviewPage = process.env.aceStaticPreview; +let aceCompileMode = process.env.aceCompileMode || 'page'; +const abilityConfig = { + abilityType: process.env.abilityType || 'page', + abilityEntryFile: null, + projectAbilityPath: [], + testRunnerFile: [] +}; +const projectConfig = {}; +const resources = { + app: {}, + sys: {} +}; +const systemModules = []; +const abilityPagesFullPath = new Set(); +let globalModulePaths = []; +let sdkConfigs = []; +let defaultSdkConfigs = []; +let extendSdkConfigs = []; +let sdkConfigPrefix = 'ohos|system|kit|arkts'; +let ohosSystemModulePaths = []; +let ohosSystemModuleSubDirPaths = []; +let allModulesPaths = []; + +function initProjectConfig(projectConfig) { + initProjectPathConfig(projectConfig); + projectConfig.entryObj = {}; + projectConfig.entryArrayForObf = []; // Only used for arkguard + projectConfig.cardObj = {}; + projectConfig.aceBuildJson = projectConfig.aceBuildJson || process.env.aceBuildJson; + projectConfig.xtsMode = /ets_loader_ark$/.test(__dirname) || process.env.xtsMode === 'true'; + projectConfig.isPreview = projectConfig.isPreview || process.env.isPreview === 'true'; + projectConfig.compileMode = projectConfig.compileMode || process.env.compileMode || 'jsbundle'; + projectConfig.runtimeOS = projectConfig.runtimeOS || process.env.runtimeOS || 'default'; + projectConfig.sdkInfo = projectConfig.sdkInfo || process.env.sdkInfo || 'default'; + projectConfig.compileHar = false; + projectConfig.compileShared = false; + projectConfig.splitCommon = false; + projectConfig.allowEtsAnnotations = true; + projectConfig.checkEntry = projectConfig.checkEntry || process.env.checkEntry; + projectConfig.obfuscateHarType = projectConfig.obfuscateHarType || process.env.obfuscate; + projectConfig.packageDir = 'node_modules'; + projectConfig.packageJson = 'package.json'; + projectConfig.packageManagerType = 'npm'; + projectConfig.cardEntryObj = {}; + projectConfig.compilerTypes = []; + projectConfig.isCrossplatform = projectConfig.isCrossplatform || false; + projectConfig.enableDebugLine = projectConfig.enableDebugLine || process.env.enableDebugLine || false; + projectConfig.bundleType = projectConfig.bundleType || process.env.bundleType || ''; + projectConfig.optLazyForEach = false; + projectConfig.hspResourcesMap = false; + projectConfig.useArkoala = false; + projectConfig.resetBundleName = false; + projectConfig.integratedHsp = false; + projectConfig.useTsHar = false; + projectConfig.optTryCatchFunc = true; + // All files which dependent on bytecode har, and should be added to compilation entries. + projectConfig.otherCompileFiles = {}; + // Packages which need to update version in bytecode har + projectConfig.updateVersionInfo = undefined; + projectConfig.allowEmptyBundleName = false; + projectConfig.uiTransformOptimization = false; + projectConfig.ignoreCrossplatformCheck = false; +} + +function initProjectPathConfig(projectConfig) { + projectConfig.projectPath = projectConfig.projectPath || process.env.aceModuleRoot || + path.join(process.cwd(), 'sample'); + projectConfig.buildPath = projectConfig.buildPath || process.env.aceModuleBuild || + path.resolve(projectConfig.projectPath, 'build'); + projectConfig.aceModuleBuild = projectConfig.buildPath; // To be compatible with both webpack and rollup + projectConfig.manifestFilePath = projectConfig.manifestFilePath || process.env.aceManifestPath || + path.join(projectConfig.projectPath, 'manifest.json'); + projectConfig.aceProfilePath = projectConfig.aceProfilePath || process.env.aceProfilePath; + projectConfig.aceModuleJsonPath = projectConfig.aceModuleJsonPath || process.env.aceModuleJsonPath; + projectConfig.aceSuperVisualPath = projectConfig.aceSuperVisualPath || + process.env.aceSuperVisualPath; + projectConfig.hashProjectPath = projectConfig.hashProjectPath || + hashProjectPath(projectConfig.projectPath); + projectConfig.cachePath = projectConfig.cachePath || process.env.cachePath || + path.resolve(__dirname, 'node_modules/.cache'); + projectConfig.aceSoPath = projectConfig.aceSoPath || process.env.aceSoPath; + projectConfig.localPropertiesPath = projectConfig.localPropertiesPath || process.env.localPropertiesPath; + projectConfig.projectProfilePath = projectConfig.projectProfilePath || process.env.projectProfilePath; +} + +function loadMemoryTrackingConfig(projectConfig) { + projectConfig.enableMemoryDotting = process.env.enableMemoryDotting || false; + projectConfig.memoryDottingPath = path.resolve(projectConfig.buildPath, '../', '../', 'dottingfile'); + // recordInterval config, unit is ms + projectConfig.memoryDottingRecordInterval = process.env.memoryDottingRecordInterval || 100; + // records the config interval for writing files, unit is ms. + projectConfig.memoryDottingWriteFileInterval = process.env.memoryDottingWriteFileInterval || 1000; +} + +function loadEntryObj(projectConfig) { + let manifest = {}; + initMain(); + initProjectConfig(projectConfig); + loadMemoryTrackingConfig(projectConfig); + loadBuildJson(); + // Initialize hybrid compilation related configuration + if (isRemoteModule()) { + initMixCompileHar(projectConfig); + } + if (process.env.aceManifestPath && aceCompileMode === 'page') { + setEntryFile(projectConfig); + setFaTestRunnerFile(projectConfig); + } + if (process.env.aceModuleJsonPath) { + setIntentEntryPages(projectConfig); + setAbilityPages(projectConfig); + setStageTestRunnerFile(projectConfig); + loadNavigationConfig(aceBuildJson); + } + + /** + * In the case of hybrid compilation mode and remote modules + * do not perform operations such as page path parsing + */ + if (isRemoteModule()) { + return; + } + if (staticPreviewPage) { + projectConfig.entryObj['./' + staticPreviewPage] = projectConfig.projectPath + path.sep + + staticPreviewPage + '.ets?entry'; + setEntryArrayForObf(staticPreviewPage); + } else if (abilityConfig.abilityType === 'page') { + if (fs.existsSync(projectConfig.manifestFilePath)) { + const jsonString = fs.readFileSync(projectConfig.manifestFilePath).toString(); + manifest = JSON.parse(jsonString); + if (manifest && manifest.minPlatformVersion) { + process.env.minPlatformVersion = manifest.minPlatformVersion; + partialUpdateController(manifest.minPlatformVersion); + } + projectConfig.pagesJsonFileName = 'config.json'; + } else if (projectConfig.aceModuleJsonPath && fs.existsSync(projectConfig.aceModuleJsonPath)) { + process.env.compileMode = 'moduleJson'; + buildManifest(manifest, projectConfig.aceModuleJsonPath); + } else { + throw Error('\u001b[31m ERROR: the manifest file ' + projectConfig.manifestFilePath.replace(/\\/g, '/') + + ' or module.json is lost or format is invalid. \u001b[39m').message; + } + if (!projectConfig.compileHar) { + if (manifest.pages) { + const pages = manifest.pages; + pages.forEach((element) => { + const sourcePath = element.replace(/^\.\/ets\//, ''); + const fileName = path.resolve(projectConfig.projectPath, sourcePath + '.ets'); + if (fs.existsSync(fileName)) { + projectConfig.entryObj['./' + sourcePath] = fileName + '?entry'; + // Collect the file paths in main_pages.json + setEntryArrayForObf(sourcePath); + } else { + throw Error(`\u001b[31m ERROR: page '${fileName.replace(/\\/g, '/')}' does not exist. \u001b[39m`) + .message; + } + }); + } else { + throw Error('\u001b[31m ERROR: missing pages attribute in ' + + projectConfig.manifestFilePath.replace(/\\/g, '/') + + '. \u001b[39m').message; + } + } + } +} + +function loadNavigationConfig(aceBuildJson) { + if (aceBuildJson && aceBuildJson.routerMap && Array.isArray(aceBuildJson.routerMap)) { + aceBuildJson.routerMap.forEach((item) => { + if (item.pageSourceFile && (item.name || item.name === '') && item.buildFunction) { + const filePath = path.resolve(item.pageSourceFile); + const storedFileInfo = getStoredFileInfo(); + if (storedFileInfo.routerInfo.has(filePath)) { + storedFileInfo.routerInfo.get(filePath).push({name: item.name, buildFunction: item.buildFunction}); + } else { + storedFileInfo.routerInfo.set(filePath, [{name: item.name, buildFunction: item.buildFunction}]); + } + } + }); + } +} + +function buildManifest(manifest, aceConfigPath) { + try { + const moduleConfigJson = JSON.parse(fs.readFileSync(aceConfigPath).toString()); + manifest.type = process.env.abilityType; + if (moduleConfigJson && moduleConfigJson.app && moduleConfigJson.app.minAPIVersion) { + if (moduleConfigJson.module && moduleConfigJson.module.metadata) { + partialUpdateController(moduleConfigJson.app.minAPIVersion, moduleConfigJson.module.metadata, + moduleConfigJson.module.type); + stageOptimization(moduleConfigJson.module.metadata); + } else { + partialUpdateController(moduleConfigJson.app.minAPIVersion); + } + } + if (moduleConfigJson.module) { + switch (moduleConfigJson.module.type) { + case 'har': + projectConfig.compileHar = true; + getPackageJsonEntryPath(); + break; + case 'shared': + projectConfig.compileShared = true; + getPackageJsonEntryPath(); + manifest.pages = getPages(moduleConfigJson); + break; + default: + manifest.pages = getPages(moduleConfigJson); + break; + } + } else { + throw Error('\u001b[31m' + + 'BUIDERROR: the config.json file miss key word module || module[abilities].' + + '\u001b[39m').message; + } + } catch (e) { + if (/BUIDERROR/.test(e)) { + throw Error(e.replace('BUIDERROR', 'ERROR')).message; + } else { + throw Error('\x1B[31m' + 'ERROR: the module.json file is lost or format is invalid.' + + '\x1B[39m').message; + } + } +} + +function getPackageJsonEntryPath() { + let rootPackageJsonPath = path.resolve(projectConfig.projectPath, '../../../', projectConfig.packageJson); + if (isRemoteModule()) { + rootPackageJsonPath = getRootPackageJsonPath(projectConfig.projectPath); + } + if (fs.existsSync(rootPackageJsonPath)) { + let rootPackageJsonContent; + try { + rootPackageJsonContent = (projectConfig.packageManagerType === 'npm' ? + JSON : JSON5).parse(fs.readFileSync(rootPackageJsonPath, 'utf-8')); + } catch (e) { + throw Error('\u001b[31m' + 'BUIDERROR: ' + rootPackageJsonPath + ' format is invalid.' + '\u001b[39m').message; + } + if (rootPackageJsonContent) { + if (rootPackageJsonContent.module) { + getEntryPath(rootPackageJsonContent.module, rootPackageJsonPath); + } else if (rootPackageJsonContent.main) { + getEntryPath(rootPackageJsonContent.main, rootPackageJsonPath); + } else { + getEntryPath('', rootPackageJsonPath); + } + } else if (projectConfig.compileHar) { + throw Error('\u001b[31m' + 'BUIDERROR: lack message in ' + projectConfig.packageJson + '.' + + '\u001b[39m').message; + } + } +} + +function supportSuffix(mainEntryPath) { + if (fs.existsSync(path.join(mainEntryPath, 'index.ets'))) { + mainEntryPath = path.join(mainEntryPath, 'index.ets'); + } else if (fs.existsSync(path.join(mainEntryPath, 'index.ts'))) { + mainEntryPath = path.join(mainEntryPath, 'index.ts'); + } else if (fs.existsSync(path.join(mainEntryPath, 'index.js'))) { + mainEntryPath = path.join(mainEntryPath, 'index.js'); + } else if (projectConfig.compileHar) { + throw Error('\u001b[31m' + 'BUIDERROR: not find entry file in ' + projectConfig.packageJson + + '.' + '\u001b[39m').message; + } + return mainEntryPath; +} + +function supportExtName(mainEntryPath) { + if (path.extname(mainEntryPath) === '') { + if (fs.existsSync(mainEntryPath + '.ets')) { + mainEntryPath = mainEntryPath + '.ets'; + } else if (fs.existsSync(mainEntryPath + '.ts')) { + mainEntryPath = mainEntryPath + '.ts'; + } else if (fs.existsSync(mainEntryPath + '.js')) { + mainEntryPath = mainEntryPath + '.js'; + } + } + return mainEntryPath; +} + +function getEntryPath(entryPath, rootPackageJsonPath) { + let mainEntryPath = path.resolve(rootPackageJsonPath, '../', entryPath); + if (fs.existsSync(mainEntryPath) && fs.statSync(mainEntryPath).isDirectory()) { + mainEntryPath = supportSuffix(mainEntryPath); + } else { + mainEntryPath = supportExtName(mainEntryPath); + } + if (fs.existsSync(mainEntryPath) && fs.statSync(mainEntryPath).isFile()) { + const entryKey = path.relative(projectConfig.projectPath, mainEntryPath); + projectConfig.entryObj[entryKey] = mainEntryPath; + setEntryArrayForObf(entryKey); + abilityPagesFullPath.add(path.resolve(mainEntryPath).toLowerCase()); + } else if (projectConfig.compileHar) { + throw Error('\u001b[31m' + `BUIDERROR: not find entry file in ${rootPackageJsonPath}.` + '\u001b[39m').message; + } +} + +function stageOptimization(metadata) { + if (Array.isArray(metadata) && metadata.length) { + metadata.some(item => { + if (item.name && item.name === 'USE_COMMON_CHUNK' && + item.value && item.value === 'true') { + projectConfig.splitCommon = true; + return true; + } + }); + } +} + +function getPages(configJson) { + const pages = []; + let pagesJsonFileName = ''; + // pages is not necessary in stage + if (process.env.compileMode === 'moduleJson' && configJson.module && configJson.module.pages) { + pagesJsonFileName = `${configJson.module.pages.replace(/\$profile\:/, '')}.json`; + } else { + return pages; + } + const modulePagePath = path.resolve(projectConfig.aceProfilePath, pagesJsonFileName); + if (fs.existsSync(modulePagePath)) { + try { + const pagesConfig = JSON.parse(fs.readFileSync(modulePagePath, 'utf-8')); + if (pagesConfig && pagesConfig.src) { + projectConfig.pagesJsonFileName = pagesJsonFileName; + return pagesConfig.src; + } + } catch (e) { + throw Error('\x1B[31m' + `BUIDERROR: the ${modulePagePath} file format is invalid.` + + '\x1B[39m').message; + } + } + return pages; +} + +function setEntryFile(projectConfig) { + const entryFileName = abilityConfig.abilityType === 'page' ? 'app' : abilityConfig.abilityType; + const extendFile = entryFileName === 'app' ? '.ets' : '.ts'; + const entryFileRealPath = entryFileName + extendFile; + const entryFilePath = path.resolve(projectConfig.projectPath, entryFileRealPath); + abilityConfig.abilityEntryFile = entryFilePath; + if (!fs.existsSync(entryFilePath) && aceCompileMode === 'page') { + throw Error(`\u001b[31m ERROR: missing ${entryFilePath.replace(/\\/g, '/')}. \u001b[39m`).message; + } + projectConfig.entryObj[`./${entryFileName}`] = entryFilePath + '?entry'; + setEntryArrayForObf(entryFileName); +} + +function setIntentEntryPages(projectConfig) { + projectConfig.intentEntry.forEach(pages => { + const entryKey = path.relative(projectConfig.projectPath, pages).replace(/\.(ets|ts|js)$/, ''); + projectConfig.entryObj[entryKey] = pages; + if (/\.ets$/.test(pages)) { + abilityPagesFullPath.add(path.resolve(pages).toLowerCase()); + } + }); +} + +function setAbilityPages(projectConfig) { + if (isRemoteModule()) { + return; + } + let abilityPages = []; + if (projectConfig.aceModuleJsonPath && fs.existsSync(projectConfig.aceModuleJsonPath)) { + const moduleJson = JSON.parse(fs.readFileSync(projectConfig.aceModuleJsonPath).toString()); + abilityPages = readAbilityEntrance(moduleJson); + setAbilityFile(projectConfig, abilityPages); + setBundleModuleInfo(projectConfig, moduleJson); + } +} + +function setTestRunnerFile(projectConfig, isStageBased) { + const index = projectConfig.projectPath.split(path.sep).join('/').lastIndexOf('\/'); + TEST_RUNNER_DIR_SET.forEach((dir) => { + const projectPath = isStageBased ? projectConfig.projectPath : projectConfig.projectPath.substring(0, index + 1); + const testRunnerPath = path.resolve(projectPath, dir); + if (fs.existsSync(testRunnerPath) && folderExistsCaseSensitive(testRunnerPath)) { + const testRunnerFiles = []; + readFile(testRunnerPath, testRunnerFiles); + testRunnerFiles.forEach((item) => { + if (/\.(ts|js|ets)$/.test(item)) { + if (/\.ets$/.test(item)) { + abilityPagesFullPath.add(path.resolve(item).toLowerCase()); + } + const relativePath = path.relative(testRunnerPath, item).replace(/\.(ts|js|ets)$/, ''); + if (isStageBased) { + projectConfig.entryObj[`./${dir}/${relativePath}`] = item; + } else { + projectConfig.entryObj[`../${dir}/${relativePath}`] = item; + } + setEntryArrayForObf(dir, relativePath); + abilityConfig.testRunnerFile.push(item); + } + }); + } + }); +} + +// entryPath: the filename of the entry file and the name of the outer directory. +// The directory should be placed before the filename, and the filename must be the last argument. +function setEntryArrayForObf(...entryPath) { + projectConfig.entryArrayForObf?.push(entryPath.join('/')); +} + +function folderExistsCaseSensitive(folderPath) { + try { + const folders = fs.readdirSync(path.dirname(folderPath), { withFileTypes: true }) + .filter(entry => entry.isDirectory()) + .map(entry => entry.name); + const targetFolderName = path.basename(folderPath); + return folders.includes(targetFolderName); + } catch (error) { + return false; + } +} + +function setFaTestRunnerFile(projectConfig) { + setTestRunnerFile(projectConfig, false); +} + +function setStageTestRunnerFile(projectConfig) { + setTestRunnerFile(projectConfig, true); +} + +function setBundleModuleInfo(projectConfig, moduleJson) { + if (moduleJson.module) { + projectConfig.moduleName = moduleJson.module.name; + } + if (moduleJson.app) { + projectConfig.bundleName = moduleJson.app.bundleName; + } +} + +function setAbilityFile(projectConfig, abilityPages) { + abilityPages.forEach(abilityPath => { + const projectAbilityPath = path.resolve(projectConfig.projectPath, '../', abilityPath); + if (path.isAbsolute(abilityPath)) { + abilityPath = '.' + abilityPath.slice(projectConfig.projectPath.length); + } + const entryPageKey = abilityPath.replace(/^\.\/ets\//, './').replace(/\.ts$/, '').replace(/\.ets$/, ''); + if (fs.existsSync(projectAbilityPath)) { + abilityConfig.projectAbilityPath.push(projectAbilityPath); + projectConfig.entryObj[entryPageKey] = projectAbilityPath + '?entry'; + setEntryArrayForObf(entryPageKey); + } else { + throw Error( + `\u001b[31m ERROR: srcEntry file '${projectAbilityPath.replace(/\\/g, '/')}' does not exist. \u001b[39m` + ).message; + } + }); +} + +function readAbilityEntrance(moduleJson) { + const abilityPages = []; + if (moduleJson.module) { + const moduleSrcEntrance = moduleJson.module.srcEntrance; + const moduleSrcEntry = moduleJson.module.srcEntry; + if (moduleSrcEntry) { + abilityPages.push(moduleSrcEntry); + abilityPagesFullPath.add(getAbilityFullPath(projectConfig.projectPath, moduleSrcEntry)); + } else if (moduleSrcEntrance) { + abilityPages.push(moduleSrcEntrance); + abilityPagesFullPath.add(getAbilityFullPath(projectConfig.projectPath, moduleSrcEntrance)); + } + if (moduleJson.module.abilities && moduleJson.module.abilities.length > 0) { + setEntrance(moduleJson.module.abilities, abilityPages); + } + if (moduleJson.module.extensionAbilities && moduleJson.module.extensionAbilities.length > 0) { + setEntrance(moduleJson.module.extensionAbilities, abilityPages); + setCardPages(moduleJson.module.extensionAbilities); + } + } + return abilityPages; +} + +function setEntrance(abilityConfig, abilityPages) { + if (abilityConfig && abilityConfig.length > 0) { + abilityConfig.forEach(ability => { + if (ability.srcEntry) { + abilityPages.push(ability.srcEntry); + abilityPagesFullPath.add(getAbilityFullPath(projectConfig.projectPath, ability.srcEntry)); + } else if (ability.srcEntrance) { + abilityPages.push(ability.srcEntrance); + abilityPagesFullPath.add(getAbilityFullPath(projectConfig.projectPath, ability.srcEntrance)); + } + }); + } +} + +function setCardPages(extensionAbilities) { + if (extensionAbilities && extensionAbilities.length > 0) { + extensionAbilities.forEach(extensionAbility => { + if (extensionAbility.metadata) { + extensionAbility.metadata.forEach(metadata => { + if (metadata.resource) { + readCardResource(metadata.resource); + } + }); + } + }); + } +} + +function readCardResource(resource) { + const cardJsonFileName = `${resource.replace(/\$profile\:/, '')}.json`; + const modulePagePath = path.resolve(projectConfig.aceProfilePath, cardJsonFileName); + if (fs.existsSync(modulePagePath)) { + const cardConfig = JSON.parse(fs.readFileSync(modulePagePath, 'utf-8')); + if (cardConfig.forms) { + cardConfig.forms.forEach(form => { + readCardForm(form); + }); + } + } +} + +function readCardForm(form) { + if ((form.type && form.type === 'eTS') || + (form.uiSyntax && form.uiSyntax === 'arkts')) { + const sourcePath = form.src.replace(/\.ets$/, ''); + const cardPath = path.resolve(projectConfig.projectPath, '..', sourcePath + '.ets'); + if (cardPath && fs.existsSync(cardPath)) { + projectConfig.entryObj['../' + sourcePath] = cardPath + '?entry'; + setEntryArrayForObf(sourcePath); + projectConfig.cardEntryObj['../' + sourcePath] = cardPath; + projectConfig.cardObj[cardPath] = sourcePath.replace(/^\.\//, ''); + } + } +} + +function getAbilityFullPath(projectPath, abilityPath) { + const finalPath = path.resolve(path.resolve(projectPath, '../'), abilityPath); + if (fs.existsSync(finalPath)) { + return finalPath.toLowerCase(); + } else { + return path.resolve(abilityPath).toLowerCase(); + } +} + +function loadWorker(projectConfig, workerFileEntry) { + if (workerFileEntry) { + projectConfig.entryObj = Object.assign(projectConfig.entryObj, workerFileEntry); + const keys = Object.keys(workerFileEntry); + for (const key of keys) { + setEntryArrayForObf(key); + } + } else { + const workerPath = path.resolve(projectConfig.projectPath, WORKERS_DIR); + if (fs.existsSync(workerPath)) { + const workerFiles = []; + readFile(workerPath, workerFiles); + workerFiles.forEach((item) => { + if (/\.(ts|js|ets)$/.test(item)) { + const relativePath = path.relative(workerPath, item) + .replace(/\.(ts|js|ets)$/, '').replace(/\\/g, '/'); + projectConfig.entryObj[`./${WORKERS_DIR}/` + relativePath] = item; + setEntryArrayForObf(WORKERS_DIR, relativePath); + abilityPagesFullPath.add(path.resolve(item).toLowerCase()); + } + }); + } + } +} + +let aceBuildJson = {}; +function loadBuildJson() { + if (projectConfig.aceBuildJson && fs.existsSync(projectConfig.aceBuildJson)) { + aceBuildJson = JSON.parse(fs.readFileSync(projectConfig.aceBuildJson).toString()); + } + if (aceBuildJson.packageManagerType === 'ohpm') { + projectConfig.packageManagerType = 'ohpm'; + projectConfig.packageDir = 'oh_modules'; + projectConfig.packageJson = 'oh-package.json5'; + } + // add intent framework entry file + projectConfig.intentEntry = aceBuildJson.compileEntry || []; + if (!!aceBuildJson.otherCompileFiles) { + aceBuildJson.otherCompileFiles.forEach(pages => { + const entryKey = path.relative(projectConfig.projectPath, pages).replace(/\.(ets|ts|js|mjs|cjs)$/, ''); + projectConfig.otherCompileFiles[entryKey] = pages; + if (/\.ets$/.test(pages)) { + abilityPagesFullPath.add(path.resolve(pages).toLowerCase()); + } + }); + } + if (!!aceBuildJson.byteCodeHar) { + projectConfig.useTsHar = true; + } + if (aceBuildJson.updateVersionInfo) { + projectConfig.updateVersionInfo = aceBuildJson.updateVersionInfo; + } +} + +function initBuildInfo() { + projectConfig.projectRootPath = aceBuildJson.projectRootPath; + if (projectConfig.compileHar && aceBuildJson.moduleName && + aceBuildJson.modulePathMap[aceBuildJson.moduleName]) { + projectConfig.moduleRootPath = aceBuildJson.modulePathMap[aceBuildJson.moduleName]; + } +} + +function readWorkerFile() { + const workerFileEntry = {}; + if (aceBuildJson.workers) { + aceBuildJson.workers.forEach(worker => { + if (!/\.(ts|js|ets)$/.test(worker)) { + throw Error( + '\u001b[31m10906402 ArkTS Compiler Error' + '\n' + + 'Error Message: ' + 'File: ' + worker + '.' + '\n' + + "The worker file can only be an '.ets', '.ts', or '.js' file.\u001b[39m" + ).message; + } + const relativePath = path.relative(projectConfig.projectPath, worker); + if (filterWorker(relativePath)) { + const workerKey = relativePath.replace(/\.(ts|js)$/, '').replace(/\\/g, '/'); + if (workerFileEntry[workerKey]) { + throw Error( + '\u001b[31m10905407 ArkTS Compiler Error' + '\n' + + 'Error Message: ' + 'The worker file cannot use the same file name: \n' + + workerFileEntry[workerKey] + '\n' + worker + '\u001b[39m' + ).message; + } else { + workerFileEntry[workerKey] = worker; + abilityPagesFullPath.add(path.resolve(workerFileEntry[workerKey]).toLowerCase()); + } + } + }); + return workerFileEntry; + } + return null; +} + +function readPatchConfig() { + if (aceBuildJson.patchConfig) { + projectConfig.hotReload = (process.env.watchMode === 'true' && !projectConfig.isPreview) || + aceBuildJson.patchConfig.mode === 'hotReload'; + projectConfig.coldReload = aceBuildJson.patchConfig.mode === COLD_RELOAD_MODE ? true : false; + // The "isFirstBuild" field indicates whether it is the first compilation of cold reload mode + // It is determined by hvigor and passed via env + projectConfig.isFirstBuild = process.env.isFirstBuild === 'false' ? false : true; + projectConfig.patchAbcPath = aceBuildJson.patchConfig.patchAbcPath; + projectConfig.changedFileList = aceBuildJson.patchConfig.changedFileList ? + aceBuildJson.patchConfig.changedFileList : path.join(projectConfig.cachePath, 'changedFileList.json'); + projectConfig.removeChangedFileListInSdk = aceBuildJson.patchConfig.removeChangedFileListInSdk === 'true' || false; + if (!projectConfig.removeChangedFileListInSdk && projectConfig.hotReload) { + writeFileSync(projectConfig.changedFileList, JSON.stringify({ + modifiedFiles: [], + removedFiles: [] + })); + } + } +} + +function filterWorker(workerPath) { + return /\.(ts|js|ets)$/.test(workerPath); +} + +;(function initSystemResource() { + const sysResourcePath = path.resolve(__dirname, './sysResource.js'); + if (fs.existsSync(sysResourcePath)) { + resources.sys = require(sysResourcePath).sys; + } + if (!process.env.externalApiPaths) { + return; + } + const sdkPaths = process.env.externalApiPaths.split(path.delimiter); + for (let i = 0; i < sdkPaths.length; i++) { + const sysResourceExtPath = path.resolve(__dirname, sdkPaths[i], 'sysResource.js'); + if (!fs.existsSync(sysResourceExtPath)) { + continue; + } + Object.entries(require(sysResourceExtPath).sys).forEach(([key, value]) => { + if (key in resources.sys) { + Object.assign(resources.sys[key], value); + } + }); + } +})(); + +;(function readSystemModules() { + const apiDirPath = path.resolve(__dirname, '../../api'); + const arktsDirPath = path.resolve(__dirname, '../../arkts'); + const kitsDirPath = path.resolve(__dirname, '../../kits'); + const systemModulePathArray = [apiDirPath]; + if (!process.env.isFaMode) { + systemModulePathArray.push(arktsDirPath, kitsDirPath); + } + systemModulePathArray.forEach(systemModulesPath => { + if (fs.existsSync(systemModulesPath)) { + globalModulePaths.push(systemModulesPath); + const modulePaths = []; + readFile(systemModulesPath, modulePaths); + systemModules.push(...fs.readdirSync(systemModulesPath)); + ohosSystemModulePaths.push(...modulePaths); + const moduleSubdir = modulePaths.filter(filePath => { + const dirName = path.dirname(filePath); + return !(dirName === apiDirPath || dirName === arktsDirPath || dirName === kitsDirPath); + }).map(filePath => { + return filePath + .replace(apiDirPath, '') + .replace(arktsDirPath, '') + .replace(kitsDirPath, '') + .replace(/\\/g, '/') + .replace(/(^\/)|(.d.e?ts$)/g, ''); + }); + ohosSystemModuleSubDirPaths.push(...moduleSubdir); + allModulesPaths.push(...modulePaths); + } + }); + defaultSdkConfigs = [ + { + 'apiPath': systemModulePathArray, + 'prefix': '@ohos' + }, { + 'apiPath': systemModulePathArray, + 'prefix': '@system' + }, { + 'apiPath': systemModulePathArray, + 'prefix': '@arkts' + } + ]; + const externalApiPathStr = process.env.externalApiPaths || ''; + const externalApiPaths = externalApiPathStr.split(path.delimiter); + collectExternalModules(externalApiPaths); + sdkConfigs = [...defaultSdkConfigs, ...extendSdkConfigs]; +})(); + +function collectExternalModules(sdkPaths) { + for (let i = 0; i < sdkPaths.length; i++) { + const sdkPath = sdkPaths[i]; + const sdkConfigPath = path.resolve(sdkPath, 'sdkConfig.json'); + if (!fs.existsSync(sdkConfigPath)) { + continue; + } + const sdkConfig = JSON.parse(fs.readFileSync(sdkConfigPath, 'utf-8')); + if (!sdkConfig.apiPath) { + continue; + } + let externalApiPathArray = []; + if (Array.isArray(sdkConfig.apiPath)) { + externalApiPathArray = sdkConfig.apiPath; + } else { + externalApiPathArray.push(sdkConfig.apiPath); + } + const resolveApiPathArray = []; + externalApiPathArray.forEach(element => { + const resolvePath = path.resolve(sdkPath, element); + resolveApiPathArray.push(resolvePath); + if (fs.existsSync(resolvePath)) { + const extrenalModulePaths = []; + globalModulePaths.push(resolvePath); + systemModules.push(...fs.readdirSync(resolvePath)); + readFile(resolvePath, extrenalModulePaths); + allModulesPaths.push(...extrenalModulePaths); + } + }); + sdkConfigPrefix += `|${sdkConfig.prefix.replace(/^@/, '')}`; + sdkConfig.apiPath = resolveApiPathArray; + extendSdkConfigs.push(sdkConfig); + } +} + +function readHspResource() { + if (aceBuildJson.hspResourcesMap) { + projectConfig.hspResourcesMap = true; + for (const hspName in aceBuildJson.hspResourcesMap) { + if (fs.existsSync(aceBuildJson.hspResourcesMap[hspName])) { + const resourceMap = new Map(); + const hspResourceCollect = resources[hspName] = {}; + const hspResource = fs.readFileSync(aceBuildJson.hspResourcesMap[hspName], 'utf-8'); + const resourceArr = hspResource.split(/\n/); + processResourceArr(resourceArr, resourceMap, aceBuildJson.hspResourcesMap[hspName]); + for (const [key, value] of resourceMap) { + hspResourceCollect[key] = value; + } + } + } + } +} + +function readAppResource(filePath) { + readHspResource(); + if (fs.existsSync(filePath)) { + const appResource = fs.readFileSync(filePath, 'utf-8'); + const resourceArr = appResource.split(/\n/); + const resourceMap = new Map(); + processResourceArr(resourceArr, resourceMap, filePath); + for (let [key, value] of resourceMap) { + resources.app[key] = value; + } + } + if (process.env.rawFileResource && process.env.compileMode === 'moduleJson') { + resourcesRawfile(process.env.rawFileResource, getStoredFileInfo().resourcesArr); + } +} + +function processResourceArr(resourceArr, resourceMap, filePath) { + 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]))) { + if (resourceMap.get(resourceData[0])) { + const resourceKeys = resourceMap.get(resourceData[0]); + if (!resourceKeys[resourceData[1]] || resourceKeys[resourceData[1]] !== Number(resourceData[2])) { + resourceKeys[resourceData[1]] = Number(resourceData[2]); + } + } else { + let obj = {}; + obj[resourceData[1]] = Number(resourceData[2]); + resourceMap.set(resourceData[0], obj); + } + if (process.env.compileTool === 'rollup' && process.env.compileMode === 'moduleJson') { + getStoredFileInfo().updateResourceList(resourceData[0] + '_' + resourceData[1]); + } + } else { + logger.warn(`\u001b[31m ArkTS:WARN The format of file '${filePath}' is incorrect. \u001b[39m`); + break; + } + } +} + +function hashProjectPath(projectPath) { + const hash = crypto.createHash('sha256'); + hash.update(projectPath.toString()); + process.env.hashProjectPath = '_' + hash.digest('hex'); + return process.env.hashProjectPath; +} + +function loadModuleInfo(projectConfig, envArgs) { + if (projectConfig.aceBuildJson && fs.existsSync(projectConfig.aceBuildJson)) { + const buildJsonInfo = JSON.parse(fs.readFileSync(projectConfig.aceBuildJson).toString()); + if (buildJsonInfo.compileMode) { + projectConfig.compileMode = buildJsonInfo.compileMode; + } + projectConfig.projectRootPath = buildJsonInfo.projectRootPath; + projectConfig.modulePathMap = buildJsonInfo.modulePathMap; + projectConfig.isOhosTest = buildJsonInfo.isOhosTest; + let faultHandler = function (error) { + // rollup's error will be handled in fast build + if (process.env.compileTool === 'rollup') { + return; + } + logger.error(error); + process.exit(FAIL); + }; + projectConfig.es2abcCompileTsInAotMode = true; + projectConfig.es2abcCompileTsInNonAotMode = false; + const compileMode = process.env.compileTool === 'rollup' ? projectConfig.compileMode : buildJsonInfo.compileMode; + if (checkAotConfig(compileMode, buildJsonInfo, faultHandler)) { + projectConfig.processTs = true; + projectConfig.pandaMode = TS2ABC; + projectConfig.anBuildOutPut = buildJsonInfo.anBuildOutPut; + projectConfig.anBuildMode = buildJsonInfo.anBuildMode; + projectConfig.apPath = buildJsonInfo.apPath; + if (projectConfig.es2abcCompileTsInAotMode) { + projectConfig.pandaMode = ES2ABC; + } + } else { + projectConfig.processTs = false; + projectConfig.pandaMode = buildJsonInfo.pandaMode; + if (projectConfig.es2abcCompileTsInNonAotMode) { + projectConfig.pandaMode = ES2ABC; + projectConfig.processTs = true; + } + } + if (envArgs !== undefined) { + projectConfig.buildArkMode = envArgs.buildMode; + } + if (compileMode === 'esmodule') { + projectConfig.nodeModulesPath = buildJsonInfo.nodeModulesPath; + projectConfig.harNameOhmMap = buildJsonInfo.harNameOhmMap; + } + if (projectConfig.compileHar && buildJsonInfo.moduleName && + buildJsonInfo.modulePathMap[buildJsonInfo.moduleName]) { + if (projectConfig.useTsHar) { + projectConfig.processTs = true; + } + projectConfig.moduleRootPath = buildJsonInfo.modulePathMap[buildJsonInfo.moduleName]; + } + } +} + +function checkAppResourcePath(appResourcePath, config) { + if (appResourcePath) { + readAppResource(appResourcePath); + if (fs.existsSync(appResourcePath) && config.cache) { + config.cache.buildDependencies.config.push(appResourcePath); + } + if (!projectConfig.xtsMode) { + const appResourcePathSavePath = path.resolve(projectConfig.cachePath, 'resource_path.txt'); + saveAppResourcePath(appResourcePath, appResourcePathSavePath); + if (fs.existsSync(appResourcePathSavePath) && config.cache) { + config.cache.buildDependencies.config.push(appResourcePathSavePath); + } + } + } +} + +function saveAppResourcePath(appResourcePath, appResourcePathSavePath) { + let isSave = false; + if (fs.existsSync(appResourcePathSavePath)) { + const saveContent = fs.readFileSync(appResourcePathSavePath); + if (appResourcePath !== saveContent) { + isSave = true; + } + } else { + isSave = true; + } + if (isSave) { + fs.writeFileSync(appResourcePathSavePath, appResourcePath); + } +} + +function addSDKBuildDependencies(config) { + if (projectConfig.localPropertiesPath && + fs.existsSync(projectConfig.localPropertiesPath) && config.cache) { + config.cache.buildDependencies.config.push(projectConfig.localPropertiesPath); + } + if (projectConfig.projectProfilePath && + fs.existsSync(projectConfig.projectProfilePath) && config.cache) { + config.cache.buildDependencies.config.push(projectConfig.projectProfilePath); + } +} + +function getCleanConfig(workerFile) { + const cleanPath = []; + if (projectConfig.compileMode === 'esmodule') { + return cleanPath; + } + cleanPath.push(projectConfig.buildPath); + if (workerFile) { + const workerFilesPath = Object.keys(workerFile); + for (const workerFilePath of workerFilesPath) { + cleanPath.push(path.join(projectConfig.buildPath, workerFilePath, '..')); + } + } + return cleanPath; +} + +function isPartialUpdate(metadata, moduleType) { + if (!Array.isArray(metadata) || !metadata.length) { + return; + } + metadata.some(item => { + if (item.name && item.value) { + if (item.name === 'ArkTSPartialUpdate' && item.value === 'false') { + partialUpdateConfig.partialUpdateMode = false; + if (projectConfig.aceModuleJsonPath) { + logger.warn('\u001b[33m ArkTS:WARN File: ' + projectConfig.aceModuleJsonPath + '.' + '\n' + + " The 'ArkTSPartialUpdate' field will no longer be supported in the future. \u001b[39m"); + } + } + if (item.name === 'ArkTSBuilderCheck' && item.value === 'false') { + partialUpdateConfig.builderCheck = false; + } + if (item.name === 'Api11ArkTSCheck' && item.value === 'SkipArkTSCheckInApi11') { + partialUpdateConfig.executeArkTSLinter = false; + } + if (item.name === 'Api11ArkTSCheckMode' && item.value === 'DoArkTSCheckInCompatibleModeInApi11') { + partialUpdateConfig.standardArkTSLinter = false; + } + if (item.name === 'ArkTSVersion') { + partialUpdateConfig.arkTSVersion = item.value; + } + if (item.name === 'OPTLazyForEach' && item.value === 'true') { + projectConfig.optLazyForEach = true; + } + if (item.name === 'SkipTscOhModuleCheck' && item.value === 'true') { + partialUpdateConfig.skipTscOhModuleCheck = true; + } + if (item.name === 'SkipArkTSStaticBlocksCheck' && item.value === 'true') { + partialUpdateConfig.skipArkTSStaticBlocksCheck = true; + } + if (item.name === 'ArkoalaPlugin' && item.value === 'true') { + projectConfig.useArkoala = true; + } + if (item.name === 'UseTsHar' && item.value === 'true' && moduleType === 'har') { + projectConfig.useTsHar = true; + } + if (item.name === 'OptTryCatchFunc' && item.value === 'false') { + projectConfig.optTryCatchFunc = false; + } + } + return !partialUpdateConfig.partialUpdateMode && !partialUpdateConfig.builderCheck && + !partialUpdateConfig.executeArkTSLinter && !partialUpdateConfig.standardArkTSLinter && + partialUpdateConfig.arkTSVersion !== undefined && projectConfig.optLazyForEach && + partialUpdateConfig.skipTscOhModuleCheck && partialUpdateConfig.skipArkTSStaticBlocksCheck; + }); +} + +function applicationConfig() { + const localProperties = path.resolve(aceBuildJson.projectRootPath, 'local.properties'); + if (fs.existsSync(localProperties)) { + try { + const localPropertiesFile = fs.readFileSync(localProperties, {encoding: 'utf-8'}).split(/\r?\n/); + localPropertiesFile.some((item) => { + const builderCheckValue = item.replace(/\s+|;/g, ''); + if (builderCheckValue === 'ArkTSConfig.ArkTSBuilderCheck=false') { + partialUpdateConfig.builderCheck = false; + return true; + } + }); + } catch (err) { + } + } +} + +function partialUpdateController(minAPIVersion, metadata = null, moduleType = '') { + projectConfig.minAPIVersion = minAPIVersion; + if (minAPIVersion >= 9) { + partialUpdateConfig.partialUpdateMode = true; + } + const MIN_VERSION_OPTIMIZE_COMPONENT = 10; + if (minAPIVersion < MIN_VERSION_OPTIMIZE_COMPONENT) { + partialUpdateConfig.optimizeComponent = false; + } + if (metadata) { + isPartialUpdate(metadata, moduleType); + } + if (aceBuildJson.projectRootPath) { + applicationConfig(); + } +} + +const globalProgram = { + builderProgram: null, + program: null, + watchProgram: null, + checker: null, + strictChecker: null, + strictLanguageService: null, +}; + +const partialUpdateConfig = { + partialUpdateMode: false, + builderCheck: true, + executeArkTSLinter: true, + standardArkTSLinter: true, + optimizeComponent: true, + arkTSVersion: undefined, + skipTscOhModuleCheck: false, + skipArkTSStaticBlocksCheck: false +}; + +function resetMain() { + staticPreviewPage = undefined; + aceCompileMode = 'page'; + resetAbilityConfig(); + resetProjectConfig(); + resources.app = {}; + abilityPagesFullPath.clear(); + aceBuildJson = {}; + partialUpdateConfig.builderCheck = true; + globalModulePaths = []; + sdkConfigs = []; + defaultSdkConfigs = []; + extendSdkConfigs = []; + sdkConfigPrefix = 'ohos|system|kit'; + ohosSystemModulePaths = []; + ohosSystemModuleSubDirPaths = []; + allModulesPaths = []; +} + +function resetAbilityConfig() { + abilityConfig.abilityType = 'page'; + abilityConfig.abilityEntryFile = null; + abilityConfig.projectAbilityPath = []; + abilityConfig.testRunnerFile = []; +} + +function resetProjectConfig() { + projectConfig.entryObj = {}; + projectConfig.entryArrayForObf = []; + projectConfig.cardObj = {}; + projectConfig.compileHar = false; + projectConfig.compileShared = false; + projectConfig.packageDir = 'node_modules'; + projectConfig.packageJson = 'package.json'; + projectConfig.packageManagerType = 'npm'; + projectConfig.cardEntryObj = {}; + projectConfig.compilerTypes = []; + projectConfig.optLazyForEach = false; + projectConfig.hspResourcesMap = false; + projectConfig.coldReload = undefined; + projectConfig.hotReload = undefined; + projectConfig.isFirstBuild = undefined; + projectConfig.changedFileList = undefined; + projectConfig.patchAbcPath = undefined; + projectConfig.removeChangedFileListInSdk = false; + projectConfig.allowEmptyBundleName = false; + projectConfig.uiTransformOptimization = false; + projectConfig.ignoreCrossplatformCheck = false; + const props = ['projectPath', 'buildPath', 'aceModuleBuild', 'manifestFilePath', 'aceProfilePath', + 'aceModuleJsonPath', 'aceSuperVisualPath', 'hashProjectPath', 'aceBuildJson', 'cachePath', + 'aceSoPath', 'localPropertiesPath', 'projectProfilePath', 'isPreview', 'compileMode', 'runtimeOS', + 'sdkInfo', 'checkEntry', 'obfuscateHarType', 'isCrossplatform', 'enableDebugLine', 'bundleType' + ]; + for (let key in projectConfig) { + if (props.includes(key)) { + projectConfig[key] = undefined; + } + } + projectConfig.otherCompileFiles = {}; + projectConfig.updateVersionInfo = undefined; +} + +function resetGlobalProgram() { + globalProgram.builderProgram = null; + globalProgram.program = null; + globalProgram.checker = null; + globalProgram.strictChecker = null; + globalProgram.strictLanguageService = null; +} + +function initMain() { + staticPreviewPage = process.env.aceStaticPreview; + aceCompileMode = process.env.aceCompileMode || 'page'; + abilityConfig.abilityType = process.env.abilityType || 'page'; +} + +exports.globalProgram = globalProgram; +exports.projectConfig = projectConfig; +exports.loadEntryObj = loadEntryObj; +exports.readAppResource = readAppResource; +exports.resources = resources; +exports.loadWorker = loadWorker; +exports.abilityConfig = abilityConfig; +exports.readWorkerFile = readWorkerFile; +exports.abilityPagesFullPath = abilityPagesFullPath; +exports.loadModuleInfo = loadModuleInfo; +exports.systemModules = systemModules; +exports.checkAppResourcePath = checkAppResourcePath; +exports.addSDKBuildDependencies = addSDKBuildDependencies; +exports.partialUpdateConfig = partialUpdateConfig; +exports.readPatchConfig = readPatchConfig; +exports.initBuildInfo = initBuildInfo; +exports.getCleanConfig = getCleanConfig; +exports.globalModulePaths = globalModulePaths; +exports.defaultSdkConfigs = defaultSdkConfigs; +exports.extendSdkConfigs = extendSdkConfigs; +exports.sdkConfigs = sdkConfigs; +exports.sdkConfigPrefix = sdkConfigPrefix; +exports.ohosSystemModulePaths = ohosSystemModulePaths; +exports.resetMain = resetMain; +exports.ohosSystemModuleSubDirPaths = ohosSystemModuleSubDirPaths; +exports.allModulesPaths = allModulesPaths; +exports.resetProjectConfig = resetProjectConfig; +exports.resetGlobalProgram = resetGlobalProgram; +exports.setEntryArrayForObf = setEntryArrayForObf; +exports.getPackageJsonEntryPath = getPackageJsonEntryPath; +exports.setIntentEntryPages = setIntentEntryPages; diff --git a/compiler/src/interop/src/ark_utils.ts b/compiler/src/interop/src/ark_utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..8aead7287b9ab7326a7a5ba6603dba871e23918e --- /dev/null +++ b/compiler/src/interop/src/ark_utils.ts @@ -0,0 +1,1020 @@ +/* + * Copyright (c) 2023 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 path from 'path'; +import fs from 'fs'; +import type sourceMap from 'source-map'; + +import { minify, MinifyOutput } from 'terser'; +import { + endFilesEvent, + endSingleFileEvent, + EventList, + MemoryUtils, + nameCacheMap, + performancePrinter, + ProjectCollections, + startFilesEvent, + startSingleFileEvent, +} from 'arkguard'; +import type { HvigorErrorInfo } from 'arkguard'; +import { + OH_MODULES, + SEPARATOR_AT, + SEPARATOR_BITWISE_AND, + SEPARATOR_SLASH +} from './fast_build/ark_compiler/common/ark_define'; +import { + ARKTS_MODULE_NAME, + PACKAGES, + TEMPORARY, + ZERO, + ONE, + EXTNAME_JS, + EXTNAME_TS, + EXTNAME_MJS, + EXTNAME_CJS, + EXTNAME_ABC, + EXTNAME_ETS, + EXTNAME_D_ETS, + EXTNAME_TS_MAP, + EXTNAME_JS_MAP, + ESMODULE, + FAIL, + TS2ABC, + ES2ABC, + EXTNAME_PROTO_BIN, + NATIVE_MODULE +} from './pre_define'; +import { + isMac, + isWindows, + isPackageModulesFile, + genTemporaryPath, + getExtensionIfUnfullySpecifiedFilepath, + mkdirsSync, + toUnixPath, + validateFilePathLength, + harFilesRecord, + getProjectRootPath +} from './utils'; +import type { GeneratedFileInHar } from './utils'; +import { + extendSdkConfigs, + projectConfig, + sdkConfigPrefix +} from '../main'; +import { + getRelativeSourcePath, + mangleFilePath, + setNewNameCache, + getNameCacheByPath, + setUnobfuscationNames, + writeObfuscatedFile +} from './fast_build/ark_compiler/common/ob_config_resolver'; +import { moduleRequestCallback } from './fast_build/system_api/api_check_utils'; +import { SourceMapGenerator } from './fast_build/ark_compiler/generate_sourcemap'; +import { sourceFileBelongProject } from './fast_build/ark_compiler/module/module_source_file'; +import { MemoryMonitor } from './fast_build/meomry_monitor/rollup-plugin-memory-monitor'; +import { MemoryDefine } from './fast_build/meomry_monitor/memory_define'; +import { + ArkTSInternalErrorDescription, + ArkTSErrorDescription, + ErrorCode +} from './fast_build/ark_compiler/error_code'; +import { + LogData, + LogDataFactory +} from './fast_build/ark_compiler/logger'; +import * as ts from 'typescript'; + +export const SRC_MAIN: string = 'src/main'; + +export let newSourceMaps: Object = {}; + +export const packageCollection: Map> = new Map(); +// Splicing ohmurl or record name based on filePath and context information table. +export function getNormalizedOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object, + pkgParams: Object, importerFile: string): string { + const { pkgName, pkgPath, isRecordName } = pkgParams; + const { projectFilePath, pkgInfo } = getPkgInfo(filePath, projectConfig, logger, pkgPath, pkgName, importerFile); + const recordName: string = `${pkgInfo.bundleName}&${pkgName}/${projectFilePath}&${pkgInfo.version}`; + if (isRecordName) { + // record name style: &/entry/ets/xxx/yyy& + return recordName; + } + return `${pkgInfo.isSO ? 'Y' : 'N'}&${pkgInfo.moduleName}&${recordName}`; +} + +export function getPkgInfo(filePath: string, projectConfig: Object, logger: Object, pkgPath: string, + pkgName: string, importerFile?: string): Object { + // rollup uses commonjs plugin to handle commonjs files, + // the commonjs files are prefixed with '\x00' and need to be removed. + if (filePath.startsWith('\x00')) { + filePath = filePath.replace('\x00', ''); + } + let unixFilePath: string = toUnixPath(filePath); + unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.')); // remove extension + // case1: /entry/src/main/ets/xxx/yyy + // case2: /entry/src/ohosTest/ets/xxx/yyy + // case3: /node_modules/xxx/yyy + // case4: /entry/node_modules/xxx/yyy + // case5: /library/node_modules/xxx/yyy + // case6: /library/index.ts + // ---> @normalized:N&&&/entry/ets/xxx/yyy& + let pkgInfo = projectConfig.pkgContextInfo[pkgName]; + if (!pkgInfo || !pkgPath) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_FAILED_TO_RESOLVE_OHM_URL, + ArkTSErrorDescription, + 'Failed to resolve OhmUrl. ' + + `Failed to get a resolved OhmUrl for "${filePath}" imported by "${importerFile}".`, + '', + [`Check whether the "${pkgName}" module which ${filePath} belongs to is correctly configured.`, + 'Check the corresponding file name is correct(including case-sensitivity).'] + ); + logger.printError(errInfo); + return filePath; + } + const projectFilePath: string = unixFilePath.replace(toUnixPath(pkgPath) + '/', ''); + return { projectFilePath, pkgInfo }; +} + +export function getOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object, namespace?: string, + importerFile?: string): string { + // remove '\x00' from the rollup virtual commonjs file's filePath + if (filePath.startsWith('\x00')) { + filePath = filePath.replace('\x00', ''); + } + let unixFilePath: string = toUnixPath(filePath); + unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.')); // remove extension + const REG_PROJECT_SRC: RegExp = /(\S+)\/src\/(?:main|ohosTest)\/(ets|js|mock)\/(\S+)/; + + const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); + const bundleName: string = packageInfo[0]; + const moduleName: string = packageInfo[1]; + const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[moduleName]); + const projectRootPath: string = toUnixPath(getProjectRootPath(filePath, projectConfig, projectConfig?.rootPathSet)); + // case1: /entry/src/main/ets/xxx/yyy ---> @bundle:/entry/ets/xxx/yyy + // case2: /entry/src/ohosTest/ets/xxx/yyy ---> @bundle:/entry_test@entry/ets/xxx/yyy + // case3: /node_modules/xxx/yyy ---> @package:pkg_modules/xxx/yyy + // case4: /entry/node_modules/xxx/yyy ---> @package:pkg_modules@entry/xxx/yyy + // case5: /library/node_modules/xxx/yyy ---> @package:pkg_modules@library/xxx/yyy + // case6: /library/index.ts ---> @bundle:/library/index + const projectFilePath: string = unixFilePath.replace(projectRootPath, ''); + const packageDir: string = projectConfig.packageDir; + const result: RegExpMatchArray | null = projectFilePath.match(REG_PROJECT_SRC); + if (result && result[1].indexOf(packageDir) === -1) { + const relativePath = processSrcMain(result, projectFilePath); + if (namespace && moduleName !== namespace) { + return `${bundleName}/${moduleName}@${namespace}/${relativePath}`; + } + return `${bundleName}/${moduleName}/${relativePath}`; + } + + const processParams: Object = { + projectFilePath, + unixFilePath, + packageDir, + projectRootPath, + moduleRootPath, + projectConfig, + namespace, + logger, + importerFile, + originalFilePath: filePath + }; + return processPackageDir(processParams); +} + +function processSrcMain(result: RegExpMatchArray | null, projectFilePath: string): string { + let langType: string = result[2]; + let relativePath: string = result[3]; + // case7: /entry/src/main/ets/xxx/src/main/js/yyy ---> @bundle:/entry/ets/xxx/src/main/js/yyy + const REG_SRC_MAIN: RegExp = /src\/(?:main|ohosTest)\/(ets|js)\//; + const srcMainIndex: number = result[1].search(REG_SRC_MAIN); + if (srcMainIndex !== -1) { + relativePath = projectFilePath.substring(srcMainIndex).replace(REG_SRC_MAIN, ''); + langType = projectFilePath.replace(relativePath, '').match(REG_SRC_MAIN)[1]; + } + return `${langType}/${relativePath}`; +} + +function processPackageDir(params: Object): string { + const { projectFilePath, unixFilePath, packageDir, projectRootPath, moduleRootPath, + projectConfig, namespace, logger, importerFile, originalFilePath } = params; + if (projectFilePath.indexOf(packageDir) !== -1) { + if (compileToolIsRollUp()) { + const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir)); + if (unixFilePath.indexOf(tryProjectPkg) !== -1) { + return unixFilePath.replace(tryProjectPkg, `${packageDir}`).replace(new RegExp(packageDir, 'g'), PACKAGES); + } + + // iterate the modulePathMap to find the module which contains the pkg_module's file + for (const moduleName in projectConfig.modulePathMap) { + const modulePath: string = projectConfig.modulePathMap[moduleName]; + const tryModulePkg: string = toUnixPath(path.resolve(modulePath, packageDir)); + if (unixFilePath.indexOf(tryModulePkg) !== -1) { + return unixFilePath.replace(tryModulePkg, `${packageDir}@${moduleName}`).replace(new RegExp(packageDir, 'g'), PACKAGES); + } + } + + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_FAILED_TO_RESOLVE_OHM_URL, + ArkTSErrorDescription, + 'Failed to resolve OhmUrl. ' + + `Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".`, + '', + [`Check whether the module which ${originalFilePath} belongs to is correctly configured.`, + 'Check the corresponding file name is correct(including case-sensitivity).'] + ); + logger.printError(errInfo); + return originalFilePath; + } + + // webpack with old implematation + const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir)); + if (unixFilePath.indexOf(tryProjectPkg) !== -1) { + return unixFilePath.replace(tryProjectPkg, `${packageDir}/${ONE}`).replace(new RegExp(packageDir, 'g'), PACKAGES); + } + + const tryModulePkg: string = toUnixPath(path.join(moduleRootPath, packageDir)); + if (unixFilePath.indexOf(tryModulePkg) !== -1) { + return unixFilePath.replace(tryModulePkg, `${packageDir}/${ZERO}`).replace(new RegExp(packageDir, 'g'), PACKAGES); + } + } + + const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); + const bundleName: string = packageInfo[0]; + const moduleName: string = packageInfo[1]; + for (const key in projectConfig.modulePathMap) { + const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[key]); + if (unixFilePath.indexOf(moduleRootPath + '/') !== -1) { + const relativeModulePath: string = unixFilePath.replace(moduleRootPath + '/', ''); + if (namespace && moduleName !== namespace) { + return `${bundleName}/${moduleName}@${namespace}/${relativeModulePath}`; + } + return `${bundleName}/${moduleName}/${relativeModulePath}`; + } + } + + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_FAILED_TO_RESOLVE_OHM_URL, + ArkTSErrorDescription, + 'Failed to resolve OhmUrl. ' + + `Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".`, + '', + [`Check whether the module which ${originalFilePath} belongs to is correctly configured.`, + 'Check the corresponding file name is correct(including case-sensitivity).'] + ); + logger.printError(errInfo); + return originalFilePath; +} + + +export function getOhmUrlBySystemApiOrLibRequest(moduleRequest: string, config?: Object, logger?: Object, + importerFile?: string, useNormalizedOHMUrl: boolean = false): string { + // 'arkui-x' represents cross platform related APIs, processed as 'ohos' + const REG_SYSTEM_MODULE: RegExp = new RegExp(`@(${sdkConfigPrefix})\\.(\\S+)`); + const REG_LIB_SO: RegExp = /lib(\S+)\.so/; + + if (REG_SYSTEM_MODULE.test(moduleRequest.trim())) { + return moduleRequest.replace(REG_SYSTEM_MODULE, (_, moduleType, systemKey) => { + let moduleRequestStr = ''; + if (extendSdkConfigs) { + moduleRequestStr = moduleRequestCallback(moduleRequest, _, moduleType, systemKey); + } + if (moduleRequestStr !== '') { + return moduleRequestStr; + } + const systemModule: string = `${moduleType}.${systemKey}`; + if (NATIVE_MODULE.has(systemModule)) { + return `@native:${systemModule}`; + } else if (moduleType === ARKTS_MODULE_NAME) { + // @arkts.xxx -> @ohos:arkts.xxx + return `@ohos:${systemModule}`; + } else { + return `@ohos:${systemKey}`; + }; + }); + } + if (REG_LIB_SO.test(moduleRequest.trim())) { + if (useNormalizedOHMUrl) { + const pkgInfo = config.pkgContextInfo[moduleRequest]; + if (pkgInfo === undefined) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_PKG_CONTENT_INFO, + ArkTSInternalErrorDescription, + `Can not get pkgContextInfo of package '${moduleRequest}' ` + + `which being imported by '${importerFile}'` + ); + logger?.printError(errInfo); + } + const isSo = pkgInfo.isSO ? 'Y' : 'N'; + return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${moduleRequest}&${pkgInfo.version}`; + } + return moduleRequest.replace(REG_LIB_SO, (_, libsoKey) => { + return `@app:${projectConfig.bundleName}/${projectConfig.moduleName}/${libsoKey}`; + }); + } + return undefined; +} + +export function genSourceMapFileName(temporaryFile: string): string { + let abcFile: string = temporaryFile; + if (temporaryFile.endsWith(EXTNAME_TS)) { + abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_TS_MAP); + } else { + abcFile = temporaryFile.replace(/\.js$/, EXTNAME_JS_MAP); + } + return abcFile; +} + +export function getBuildModeInLowerCase(projectConfig: Object): string { + return (compileToolIsRollUp() ? projectConfig.buildMode : projectConfig.buildArkMode).toLowerCase(); +} + +/** + * This Api only used by webpack compiling process - js-loader + * @param sourcePath The path in build cache dir + * @param sourceCode The intermediate js source code + */ +export function writeFileSyncByString(sourcePath: string, sourceCode: string, projectConfig: Object, logger: Object): void { + const filePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath, + projectConfig, undefined); + if (filePath.length === 0) { + return; + } + mkdirsSync(path.dirname(filePath)); + if (/\.js$/.test(sourcePath)) { + sourceCode = transformModuleSpecifier(sourcePath, sourceCode, projectConfig); + if (projectConfig.buildArkMode === 'debug') { + fs.writeFileSync(filePath, sourceCode); + return; + } + writeObfuscatedSourceCode({content: sourceCode, buildFilePath: filePath, relativeSourceFilePath: ''}, + logger, projectConfig); + } + if (/\.json$/.test(sourcePath)) { + fs.writeFileSync(filePath, sourceCode); + } +} + +export function transformModuleSpecifier(sourcePath: string, sourceCode: string, projectConfig: Object): string { + // replace relative moduleSpecifier with ohmURl + const REG_RELATIVE_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]((?:\.\/|\.\.\/)[^'"]+|(?:\.\/?|\.\.\/?))['"]/g; + const REG_HAR_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]([^\.\/][^'"]+)['"]/g; + // replace requireNapi and requireNativeModule with import + const REG_REQUIRE_NATIVE_MODULE: RegExp = /var (\S+) = globalThis.requireNativeModule\(['"](\S+)['"]\);/g; + const REG_REQUIRE_NAPI_APP: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"], true, ['"](\S+)['"]\);/g; + const REG_REQUIRE_NAPI_OHOS: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"]\);/g; + + return sourceCode.replace(REG_HAR_DEPENDENCY, (item, moduleRequest) => { + return replaceHarDependency(item, moduleRequest, projectConfig); + }).replace(REG_RELATIVE_DEPENDENCY, (item, moduleRequest) => { + return replaceRelativeDependency(item, moduleRequest, toUnixPath(sourcePath), projectConfig); + }).replace(REG_REQUIRE_NATIVE_MODULE, (_, moduleRequest, moduleName) => { + return `import ${moduleRequest} from '@native:${moduleName}';`; + }).replace(REG_REQUIRE_NAPI_APP, (_, moduleRequest, soName, moudlePath) => { + return `import ${moduleRequest} from '@app:${moudlePath}/${soName}';`; + }).replace(REG_REQUIRE_NAPI_OHOS, (_, moduleRequest, moduleName) => { + return `import ${moduleRequest} from '@ohos:${moduleName}';`; + }); +} + +function removeSuffix(filePath: string): string { + const SUFFIX_REG = /\.(?:d\.)?(ets|ts|mjs|cjs|js)$/; + return filePath.split(path.sep).join('/').replace(SUFFIX_REG, ''); +} + +export function getNormalizedOhmUrlByAliasName(aliasName: string, projectConfig: Object, + logger?: Object, filePath?: string): string { + let pkgName: string = aliasName; + const aliasPkgNameMap: Map = projectConfig.dependencyAliasMap; + if (aliasPkgNameMap.has(aliasName)) { + pkgName = aliasPkgNameMap.get(aliasName); + } + const pkgInfo: Object = projectConfig.pkgContextInfo[pkgName]; + if (!pkgInfo) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_PACKAGE_NOT_FOUND_IN_CONTEXT_INFO, + ArkTSInternalErrorDescription, + `Failed to find package '${pkgName}'. ` + + `Failed to obtain package '${pkgName}' from the package context information.` + ); + logger.printError(errInfo); + } + let normalizedPath: string = ''; + if (!filePath) { + if (!pkgInfo.entryPath) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_PACKAGE_ENTRY_FILE_NOT_FOUND, + ArkTSInternalErrorDescription, + `Failed to find entry file of '${pkgName}'. ` + + `Failed to obtain the entry file information of '${pkgName}' from the package context information.` + ); + logger.printError(errInfo); + } + normalizedPath = `${pkgName}/${toUnixPath(pkgInfo.entryPath)}`; + normalizedPath = removeSuffix(normalizedPath); + } else { + const relativePath = toUnixPath(filePath).replace(aliasName, ''); + normalizedPath = `${pkgName}${relativePath}`; + } + const isSo = pkgInfo.isSO ? 'Y' : 'N'; + return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${normalizedPath}&${pkgInfo.version}`; +} + +export function getOhmUrlByByteCodeHar(moduleRequest: string, projectConfig: Object, logger?: Object): + string | undefined { + if (projectConfig.byteCodeHarInfo) { + let aliasName: string = getAliasNameFromPackageMap(projectConfig.byteCodeHarInfo, moduleRequest); + if (aliasName) { + return getNormalizedOhmUrlByAliasName(aliasName, projectConfig, logger); + } + for (const byteCodeHarName in projectConfig.byteCodeHarInfo) { + if (moduleRequest.startsWith(byteCodeHarName + '/')) { + return getNormalizedOhmUrlByAliasName(byteCodeHarName, projectConfig, logger, moduleRequest); + } + } + } + return undefined; +} + +function getAliasNameFromPackageMap(externalPkgMap: Object, moduleRequest: string): string | undefined { + // Matches strings that contain zero or more `/` or `\` characters + const ONLY_SLASHES_REGEX: RegExp = /^\/*$|^\\*$/; + const keys: string[] = Object.keys(externalPkgMap); + for (const key of keys) { + if (moduleRequest === key) { + return key; + } + // In the following cases, the moduleRequest matches the aliasName field in the packageMap + // case1: key = "hsp", moduleRequest = "hsp/" + // case2: key = "hsp", moduleRequest = "hsp\\" + if (moduleRequest.length > key.length && moduleRequest.startsWith(key)) { + const remaining: string = moduleRequest.replace(key, ''); + if (ONLY_SLASHES_REGEX.test(remaining)) { + return key; + } + } + } + return undefined; +} + +export function getOhmUrlByExternalPackage(moduleRequest: string, projectConfig: Object, logger?: Object, + useNormalizedOHMUrl: boolean = false): string | undefined { + // The externalPkgMap store the ohmurl with the alias of hsp package and the hars depended on bytecode har. + let externalPkgMap: Object = Object.assign({}, projectConfig.hspNameOhmMap, projectConfig.harNameOhmMap); + if (Object.keys(externalPkgMap).length !== 0) { + let aliasName: string = getAliasNameFromPackageMap(externalPkgMap, moduleRequest); + if (aliasName) { + if (useNormalizedOHMUrl) { + return getNormalizedOhmUrlByAliasName(aliasName, projectConfig, logger); + } + // case1: "@ohos/lib" ---> "@bundle:bundleName/lib/ets/index" + return externalPkgMap[moduleRequest]; + } + + for (const externalPkgName in externalPkgMap) { + if (moduleRequest.startsWith(externalPkgName + '/')) { + if (useNormalizedOHMUrl) { + return getNormalizedOhmUrlByAliasName(externalPkgName, projectConfig, logger, moduleRequest); + } + // case2: "@ohos/lib/src/main/ets/pages/page1" ---> "@bundle:bundleName/lib/ets/pages/page1" + const idx: number = externalPkgMap[externalPkgName].split('/', 2).join('/').length; + const ohmName: string = externalPkgMap[externalPkgName].substring(0, idx); + if (moduleRequest.indexOf(externalPkgName + '/' + SRC_MAIN) === 0) { + return moduleRequest.replace(externalPkgName + '/' + SRC_MAIN, ohmName); + } else { + return moduleRequest.replace(externalPkgName, ohmName); + } + } + } + } + return undefined; +} + +function replaceHarDependency(item: string, moduleRequest: string, projectConfig: Object): string { + const hspOhmUrl: string | undefined = getOhmUrlByExternalPackage(moduleRequest, projectConfig); + if (hspOhmUrl !== undefined) { + return item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { + return quotation + hspOhmUrl + quotation; + }); + } + return item; +} + +function locateActualFilePathWithModuleRequest(absolutePath: string): string { + if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isDirectory()) { + return absolutePath; + } + + const filePath: string = absolutePath + getExtensionIfUnfullySpecifiedFilepath(absolutePath); + if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { + return absolutePath; + } + + return path.join(absolutePath, 'index'); +} + +function replaceRelativeDependency(item: string, moduleRequest: string, sourcePath: string, projectConfig: Object): string { + if (sourcePath && projectConfig.compileMode === ESMODULE) { + // remove file extension from moduleRequest + const SUFFIX_REG: RegExp = /\.(?:[cm]?js|[e]?ts|json)$/; + moduleRequest = moduleRequest.replace(SUFFIX_REG, ''); + + // normalize the moduleRequest + item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { + let normalizedModuleRequest: string = toUnixPath(path.normalize(moduleRequest)); + if (moduleRequest.startsWith('./')) { + normalizedModuleRequest = './' + normalizedModuleRequest; + } + return quotation + normalizedModuleRequest + quotation; + }); + + const filePath: string = + locateActualFilePathWithModuleRequest(path.resolve(path.dirname(sourcePath), moduleRequest)); + const result: RegExpMatchArray | null = + filePath.match(/(\S+)(\/|\\)src(\/|\\)(?:main|ohosTest)(\/|\\)(ets|js)(\/|\\)(\S+)/); + if (result && projectConfig.aceModuleJsonPath) { + const npmModuleIdx: number = result[1].search(/(\/|\\)node_modules(\/|\\)/); + const projectRootPath: string = projectConfig.projectRootPath; + if (npmModuleIdx === -1 || npmModuleIdx === projectRootPath.search(/(\/|\\)node_modules(\/|\\)/)) { + const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); + const bundleName: string = packageInfo[0]; + const moduleName: string = packageInfo[1]; + moduleRequest = `@bundle:${bundleName}/${moduleName}/${result[5]}/${toUnixPath(result[7])}`; + item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { + return quotation + moduleRequest + quotation; + }); + } + } + } + return item; +} + +/** + * Informantion of build files + */ +export interface ModuleInfo { + content: string, + /** + * the path in build cache dir + */ + buildFilePath: string, + /** + * the `originSourceFilePath` relative to project root dir. + */ + relativeSourceFilePath: string, + /** + * the origin source file path will be set with rollup moduleId when obfuscate intermediate js source code, + * whereas be set with tsc node.fileName when obfuscate intermediate ts source code. + */ + originSourceFilePath?: string, + rollupModuleId?: string +} + +export async function writeObfuscatedSourceCode(moduleInfo: ModuleInfo, logger: Function | Object, + projectConfig: Object, rollupNewSourceMaps: Object = {}): Promise { + if (compileToolIsRollUp() && projectConfig.arkObfuscator) { + startFilesEvent(moduleInfo.buildFilePath); + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.WRITE_OBFUSCATED_SOURCE_CODE); + const previousStageSourceMap: sourceMap.RawSourceMap | undefined = getPreviousStageSourceMap(moduleInfo, rollupNewSourceMaps); + collectObfuscationFileContent(moduleInfo, projectConfig, previousStageSourceMap); + // We should skip obfuscate here if we need reObfuscate all files, since they will all be obfuscated later in function 'reObfuscate'. + if (!projectConfig.arkObfuscator.shouldReObfuscate) { + MemoryUtils.tryGC(); + await writeArkguardObfuscatedSourceCode(moduleInfo, logger as Function, projectConfig, previousStageSourceMap); + MemoryUtils.tryGC(); + } + MemoryMonitor.stopRecordStage(recordInfo); + endFilesEvent(moduleInfo.buildFilePath, undefined, true); + return; + } + mkdirsSync(path.dirname(moduleInfo.buildFilePath)); + if (!compileToolIsRollUp()) { + await writeMinimizedSourceCode(moduleInfo.content, moduleInfo.buildFilePath, logger, projectConfig.compileHar); + return; + } + + if (moduleInfo.originSourceFilePath) { + const originSourceFilePath = toUnixPath(moduleInfo.originSourceFilePath); + let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originSourceFilePath); + + if (!genFileInHar) { + genFileInHar = { sourcePath: originSourceFilePath }; + } + if (!genFileInHar.sourceCachePath) { + genFileInHar.sourceCachePath = toUnixPath(moduleInfo.buildFilePath); + } + harFilesRecord.set(originSourceFilePath, genFileInHar); + } + + fs.writeFileSync(moduleInfo.buildFilePath, moduleInfo.content); +} + +export function getPreviousStageSourceMap(moduleInfo: ModuleInfo, rollupNewSourceMaps: Object = {}): sourceMap.RawSourceMap | undefined { + const isDeclaration = (/\.d\.e?ts$/).test(moduleInfo.buildFilePath); + const sourceMapGeneratorInstance = SourceMapGenerator.getInstance(); + let previousStageSourceMap: sourceMap.RawSourceMap | undefined = undefined; + if (moduleInfo.relativeSourceFilePath.length > 0 && !isDeclaration) { + const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath; + previousStageSourceMap = sourceMapGeneratorInstance.getSpecifySourceMap(rollupNewSourceMaps, selectedFilePath) as sourceMap.RawSourceMap; + } + return previousStageSourceMap; +} + +/** + * collect current obfuscated input content, used for re-obfuscate. + */ +export function collectObfuscationFileContent(moduleInfo: ModuleInfo, projectConfig: Object, + previousStageSourceMap: sourceMap.RawSourceMap | undefined): void { + const arkObfuscator = projectConfig.arkObfuscator; + const isDeclaration = (/\.d\.e?ts$/).test(moduleInfo.buildFilePath); + const isOriginalDeclaration = (/\.d\.e?ts$/).test(moduleInfo.originSourceFilePath); + + // We only collect non-declaration files, unless it's a user-written declaration file + if (arkObfuscator.filePathManager && (!isDeclaration || isOriginalDeclaration)) { + const fileContent: ProjectCollections.FileContent = { + moduleInfo: moduleInfo, + previousStageSourceMap: previousStageSourceMap as ts.RawSourceMap, + }; + arkObfuscator.fileContentManager.updateFileContent(fileContent); + } +} + +/** + * This Api only be used by rollup compiling process & only be + * exported for unit test. + */ +export async function writeArkguardObfuscatedSourceCode(moduleInfo: ModuleInfo, printObfLogger: Function, + projectConfig: Object, previousStageSourceMap: sourceMap.RawSourceMap | undefined): Promise { + const arkObfuscator = projectConfig.arkObfuscator; + const isDeclaration = (/\.d\.e?ts$/).test(moduleInfo.buildFilePath); + const packageDir = projectConfig.packageDir; + const projectRootPath = projectConfig.projectRootPath; + const useNormalized = projectConfig.useNormalizedOHMUrl; + const localPackageSet = projectConfig.localPackageSet; + const useTsHar = projectConfig.useTsHar; + const sourceMapGeneratorInstance = SourceMapGenerator.getInstance(); + + const historyNameCache: Map = getNameCacheByPath(moduleInfo, isDeclaration, projectRootPath); + + let mixedInfo: { content: string, sourceMap?: Object, nameCache?: Object, unobfuscationNameMap?: Map>}; + let projectInfo: { + packageDir: string, + projectRootPath: string, + localPackageSet: Set, + useNormalized: boolean, + useTsHar: boolean + } = { packageDir, projectRootPath, localPackageSet, useNormalized, useTsHar }; + let filePathObj = { buildFilePath: moduleInfo.buildFilePath, relativeFilePath: moduleInfo.relativeSourceFilePath }; + + try { + startSingleFileEvent(EventList.OBFUSCATE, performancePrinter.timeSumPrinter, filePathObj.buildFilePath); + mixedInfo = await arkObfuscator.obfuscate(moduleInfo.content, filePathObj, previousStageSourceMap, + historyNameCache, moduleInfo.originSourceFilePath, projectInfo); + endSingleFileEvent(EventList.OBFUSCATE, performancePrinter.timeSumPrinter); + } catch (err) { + const errorInfo: string = `ArkTS:INTERNAL ERROR: Failed to obfuscate file '${moduleInfo.relativeSourceFilePath}' with arkguard. ${err}`; + const errorCodeInfo: HvigorErrorInfo = { + code: '10810001', + description: 'ArkTS compiler Error', + cause: `ArkTS:INTERNAL ERROR: Failed to obfuscate file '${moduleInfo.relativeSourceFilePath}' with arkguard. ${err}`, + position: moduleInfo.relativeSourceFilePath, + solutions: [`Please modify the code based on the error information.`], + }; + printObfLogger(errorInfo, errorCodeInfo, 'error'); + } + + if (mixedInfo.sourceMap && !isDeclaration) { + const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath; + mixedInfo.sourceMap.sources = [moduleInfo.relativeSourceFilePath]; + sourceMapGeneratorInstance.fillSourceMapPackageInfo(moduleInfo.rollupModuleId!, mixedInfo.sourceMap); + sourceMapGeneratorInstance.updateSourceMap(selectedFilePath, mixedInfo.sourceMap); + } + + setNewNameCache(mixedInfo.nameCache, isDeclaration, moduleInfo, projectConfig); + + setUnobfuscationNames(mixedInfo.unobfuscationNameMap, moduleInfo.relativeSourceFilePath, isDeclaration); + + const newFilePath: string = tryMangleFileName(moduleInfo.buildFilePath, projectConfig, moduleInfo.originSourceFilePath); + if (newFilePath !== moduleInfo.buildFilePath && !isDeclaration) { + sourceMapGeneratorInstance.saveKeyMappingForObfFileName(moduleInfo.rollupModuleId!); + } + + writeObfuscatedFile(newFilePath, mixedInfo.content ?? ''); +} + +export function tryMangleFileName(filePath: string, projectConfig: Object, originalFilePath: string): string { + originalFilePath = toUnixPath(originalFilePath); + let isOhModule = isPackageModulesFile(originalFilePath, projectConfig); + let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originalFilePath); + if (!genFileInHar) { + genFileInHar = { sourcePath: originalFilePath }; + harFilesRecord.set(originalFilePath, genFileInHar); + } + + if (projectConfig.obfuscationMergedObConfig?.options?.enableFileNameObfuscation && !isOhModule) { + const mangledFilePath: string = mangleFilePath(filePath); + if ((/\.d\.e?ts$/).test(filePath)) { + genFileInHar.obfuscatedDeclarationCachePath = mangledFilePath; + } else { + genFileInHar.obfuscatedSourceCachePath = mangledFilePath; + } + filePath = mangledFilePath; + } else if (!(/\.d\.e?ts$/).test(filePath)) { + genFileInHar.sourceCachePath = filePath; + } + return filePath; +} + +export async function mangleDeclarationFileName(printObfLogger: Function, projectConfig: Object, + sourceFileBelongProject: Map): Promise { + for (const [sourcePath, genFilesInHar] of harFilesRecord) { + if (genFilesInHar.originalDeclarationCachePath && genFilesInHar.originalDeclarationContent) { + let filePath = genFilesInHar.originalDeclarationCachePath; + let relativeSourceFilePath = getRelativeSourcePath(filePath, + projectConfig.projectRootPath, sourceFileBelongProject.get(toUnixPath(filePath))); + await writeObfuscatedSourceCode({ + content: genFilesInHar.originalDeclarationContent, + buildFilePath: genFilesInHar.originalDeclarationCachePath, + relativeSourceFilePath: relativeSourceFilePath, + originSourceFilePath: sourcePath + }, printObfLogger, projectConfig, {}); + } + } +} + +export async function writeMinimizedSourceCode(content: string, filePath: string, logger: Object, + isHar: boolean = false): Promise { + let result: MinifyOutput; + try { + const minifyOptions = { + compress: { + join_vars: false, + sequences: 0, + directives: false + } + }; + if (!isHar) { + minifyOptions.format = { + semicolons: false, + beautify: true, + indent_level: 2 + }; + } + result = await minify(content, minifyOptions); + } catch { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_SOURCE_CODE_OBFUSCATION_FAILED, + ArkTSInternalErrorDescription, + `Failed to obfuscate source code for ${filePath}` + ); + logger.printError(errInfo); + } + + fs.writeFileSync(filePath, result.code); +} + +//Writes declaration files in debug mode +export function writeDeclarationFiles(compileMode: string): void { + if (compileToolIsRollUp() && compileMode === ESMODULE) { + for (const genFilesInHar of harFilesRecord.values()) { + if (genFilesInHar.originalDeclarationCachePath && genFilesInHar.originalDeclarationContent) { + mkdirsSync(path.dirname(genFilesInHar.originalDeclarationCachePath)); + fs.writeFileSync(genFilesInHar.originalDeclarationCachePath, genFilesInHar.originalDeclarationContent); + } + } + } +} + +export function genBuildPath(filePath: string, projectPath: string, buildPath: string, projectConfig: Object): string { + filePath = toUnixPath(filePath); + if (filePath.endsWith(EXTNAME_MJS)) { + filePath = filePath.replace(/\.mjs$/, EXTNAME_JS); + } + if (filePath.endsWith(EXTNAME_CJS)) { + filePath = filePath.replace(/\.cjs$/, EXTNAME_JS); + } + projectPath = toUnixPath(projectPath); + + if (isPackageModulesFile(filePath, projectConfig)) { + const packageDir: string = projectConfig.packageDir; + const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir)); + let output: string = ''; + if (filePath.indexOf(fakePkgModulesPath) === -1) { + const hapPath: string = toUnixPath(projectConfig.projectRootPath); + const tempFilePath: string = filePath.replace(hapPath, ''); + const sufStr: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1); + output = path.join(projectConfig.nodeModulesPath, ZERO, sufStr); + } else { + output = filePath.replace(fakePkgModulesPath, path.join(projectConfig.nodeModulesPath, ONE)); + } + return output; + } + + if (filePath.indexOf(projectPath) !== -1) { + const sufStr: string = filePath.replace(projectPath, ''); + const output: string = path.join(buildPath, sufStr); + return output; + } + + return ''; +} + +export function getPackageInfo(configFile: string): Array { + if (packageCollection.has(configFile)) { + return packageCollection.get(configFile); + } + const data: Object = JSON.parse(fs.readFileSync(configFile).toString()); + const bundleName: string = data.app.bundleName; + const moduleName: string = data.module.name; + packageCollection.set(configFile, [bundleName, moduleName]); + return [bundleName, moduleName]; +} + +/** + * This Api only used by webpack compiling process - result_process + * @param sourcePath The path in build cache dir + * @param sourceContent The intermediate js source code + */ +export function generateSourceFilesToTemporary(sourcePath: string, sourceContent: string, sourceMap: Object, + projectConfig: Object, logger: Object): void { + let jsFilePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath, + projectConfig, undefined); + if (jsFilePath.length === 0) { + return; + } + if (jsFilePath.endsWith(EXTNAME_ETS)) { + jsFilePath = jsFilePath.replace(/\.ets$/, EXTNAME_JS); + } else { + jsFilePath = jsFilePath.replace(/\.ts$/, EXTNAME_JS); + } + let sourceMapFile: string = genSourceMapFileName(jsFilePath); + if (sourceMapFile.length > 0 && projectConfig.buildArkMode === 'debug') { + let source = toUnixPath(sourcePath).replace(toUnixPath(projectConfig.projectRootPath) + '/', ''); + // adjust sourceMap info + sourceMap.sources = [source]; + sourceMap.file = path.basename(sourceMap.file); + delete sourceMap.sourcesContent; + newSourceMaps[source] = sourceMap; + } + sourceContent = transformModuleSpecifier(sourcePath, sourceContent, projectConfig); + + mkdirsSync(path.dirname(jsFilePath)); + if (projectConfig.buildArkMode === 'debug') { + fs.writeFileSync(jsFilePath, sourceContent); + return; + } + + writeObfuscatedSourceCode({content: sourceContent, buildFilePath: jsFilePath, relativeSourceFilePath: ''}, + logger, projectConfig); +} + +export function genAbcFileName(temporaryFile: string): string { + let abcFile: string = temporaryFile; + if (temporaryFile.endsWith(EXTNAME_TS)) { + abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_ABC); + } else { + abcFile = temporaryFile.replace(/\.js$/, EXTNAME_ABC); + } + return abcFile; +} + +export function isOhModules(projectConfig: Object): boolean { + return projectConfig.packageDir === OH_MODULES; +} + +export function isEs2Abc(projectConfig: Object): boolean { + return projectConfig.pandaMode === ES2ABC || projectConfig.pandaMode === 'undefined' || + projectConfig.pandaMode === undefined; +} + +export function isTs2Abc(projectConfig: Object): boolean { + return projectConfig.pandaMode === TS2ABC; +} + +export function genProtoFileName(temporaryFile: string): string { + return temporaryFile.replace(/\.(?:[tj]s|json)$/, EXTNAME_PROTO_BIN); +} + +export function genMergeProtoFileName(temporaryFile: string): string { + let protoTempPathArr: string[] = temporaryFile.split(TEMPORARY); + const sufStr: string = protoTempPathArr[protoTempPathArr.length - 1]; + let protoBuildPath: string = path.join(process.env.cachePath, 'protos', sufStr); + + return protoBuildPath; +} + +export function removeDuplicateInfo(moduleInfos: Array): Array { + const tempModuleInfos: any[] = Array(); + moduleInfos.forEach((item) => { + let check: boolean = tempModuleInfos.every((newItem) => { + return item.tempFilePath !== newItem.tempFilePath; + }); + if (check) { + tempModuleInfos.push(item); + } + }); + moduleInfos = tempModuleInfos; + + return moduleInfos; +} + +export function buildCachePath(tailName: string, projectConfig: Object, logger: Object): string { + let pathName: string = process.env.cachePath !== undefined ? + path.join(projectConfig.cachePath, tailName) : path.join(projectConfig.aceModuleBuild, tailName); + validateFilePathLength(pathName, logger); + return pathName; +} + +export function getArkBuildDir(arkDir: string): string { + if (isWindows()) { + return path.join(arkDir, 'build-win'); + } else if (isMac()) { + return path.join(arkDir, 'build-mac'); + } else { + return path.join(arkDir, 'build'); + } +} + +export function getBuildBinDir(arkDir: string): string { + return path.join(getArkBuildDir(arkDir), 'bin'); +} + +export function cleanUpUtilsObjects(): void { + newSourceMaps = {}; + nameCacheMap.clear(); + packageCollection.clear(); +} + +export function getHookEventFactory(share: Object, pluginName: string, hookName: string): Object { + if (typeof share.getHookEventFactory === 'function') { + return share.getHookEventFactory(pluginName, hookName); + } else { + return undefined; + } +} + +export function createAndStartEvent(eventOrEventFactory: Object, eventName: string, syncFlag = false): Object { + if (eventOrEventFactory === undefined) { + return undefined; + } + let event: Object; + if (typeof eventOrEventFactory.createSubEvent === 'function') { + event = eventOrEventFactory.createSubEvent(eventName); + } else { + event = eventOrEventFactory.createEvent(eventName); + } + if (typeof event.startAsyncEvent === 'function' && syncFlag) { + event.startAsyncEvent(); + } else { + event.start(); + } + return event; +} + +export function stopEvent(event: Object, syncFlag = false): void { + if (event !== undefined) { + if (typeof event.stopAsyncEvent === 'function' && syncFlag) { + event.stopAsyncEvent(); + } else { + event.stop(); + } + } +} + +export function compileToolIsRollUp(): boolean { + return process.env.compileTool === 'rollup'; +} + +export function transformOhmurlToRecordName(ohmurl: string): string { + // @normalized:N&&&/entry/ets/xxx/yyy& + // ----> &/entry/ets/xxx/yyy& + return ohmurl.split(SEPARATOR_BITWISE_AND).slice(2).join(SEPARATOR_BITWISE_AND); +} + +export function transformOhmurlToPkgName(ohmurl: string): string { + let normalizedPath: string = ohmurl.split(SEPARATOR_BITWISE_AND)[3]; + let paths: Array = normalizedPath.split(SEPARATOR_SLASH); + if (normalizedPath.startsWith(SEPARATOR_AT)) { + // Spec: If the normalized import path starts with '@', the package name is before the second '/' in the normalized + // import path, like: @aaa/bbb/ccc/ddd ---> package name is @aaa/bbb + return paths.slice(0, 2).join(SEPARATOR_SLASH); + } + return paths[0]; +} \ No newline at end of file diff --git a/compiler/src/interop/src/compile_info.ts b/compiler/src/interop/src/compile_info.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a52b299fae1b48d9f90f0a6a3b134cd34a096cc --- /dev/null +++ b/compiler/src/interop/src/compile_info.ts @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2021 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 ts from 'typescript'; +import Stats from 'webpack/lib/Stats'; +import Compiler from 'webpack/lib/Compiler'; +import Compilation from 'webpack/lib/Compilation'; +import JavascriptModulesPlugin from 'webpack/lib/javascript/JavascriptModulesPlugin'; +import { + configure, + getLogger +} from 'log4js'; +import path from 'path'; +import fs from 'fs'; +import CachedSource from 'webpack-sources/lib/CachedSource'; +import ConcatSource from 'webpack-sources/lib/ConcatSource'; + +import { transformLog } from './process_ui_syntax'; +import { + useOSFiles, + sourcemapNamesCollection +} from './validate_ui_syntax'; +import { + circularFile, + writeUseOSFiles, + writeFileSync, + parseErrorMessage, + genTemporaryPath, + shouldWriteChangedList, + getHotReloadFiles, + setChecker, +} from './utils'; +import { + MODULE_ETS_PATH, + MODULE_SHARE_PATH, + BUILD_SHARE_PATH, + EXTNAME_JS, + EXTNAME_JS_MAP +} from './pre_define'; +import { + serviceChecker, + createWatchCompilerHost, + hotReloadSupportFiles, + printDiagnostic, + checkerResult, + incrementWatchFile, + warnCheckerResult +} from './ets_checker'; +import { + globalProgram, + projectConfig +} from '../main'; +import cluster from 'cluster'; + +configure({ + appenders: { 'ETS': {type: 'stderr', layout: {type: 'messagePassThrough'}}}, + categories: {'default': {appenders: ['ETS'], level: 'info'}} +}); +export const logger = getLogger('ETS'); + +const checkErrorMessage: Set = new Set([]); + +interface Info { + message?: string; + issue?: { + message: string, + file: string, + location: { start?: { line: number, column: number } } + }; +} + +export interface CacheFileName { + mtimeMs: number, + children: string[], + parent: string[], + error: boolean +} + +interface hotReloadIncrementalTime { + hotReloadIncrementalStartTime: string; + hotReloadIncrementalEndTime: string; +} + +export class ResultStates { + private mStats: Stats; + private mErrorCount: number = 0; + private mPreErrorCount: number = 0; + private mWarningCount: number = 0; + private warningCount: number = 0; + private noteCount: number = 0; + private red: string = '\u001b[31m'; + private yellow: string = '\u001b[33m'; + private blue: string = '\u001b[34m'; + private reset: string = '\u001b[39m'; + private moduleSharePaths: Set = new Set([]); + private removedFiles: string[] = []; + private hotReloadIncrementalTime: hotReloadIncrementalTime = { + hotReloadIncrementalStartTime: '', + hotReloadIncrementalEndTime: '' + }; + private incrementalFileInHar: Map = new Map(); + + public apply(compiler: Compiler): void { + compiler.hooks.compilation.tap('SourcemapFixer', compilation => { + compilation.hooks.processAssets.tap('RemoveHar', (assets) => { + if (!projectConfig.compileHar) { + return; + } + Object.keys(compilation.assets).forEach(key => { + if (path.extname(key) === EXTNAME_JS || path.extname(key) === EXTNAME_JS_MAP) { + delete assets[key]; + } + }); + }); + + compilation.hooks.afterProcessAssets.tap('SourcemapFixer', assets => { + Reflect.ownKeys(assets).forEach(key => { + if (/\.map$/.test(key.toString()) && assets[key]._value) { + assets[key]._value = assets[key]._value.toString().replace('.ets?entry', '.ets'); + assets[key]._value = assets[key]._value.toString().replace('.ts?entry', '.ts'); + let absPath: string = path.resolve(projectConfig.projectPath, key.toString().replace('.js.map', '.js')); + if (sourcemapNamesCollection && absPath) { + let map: Map = sourcemapNamesCollection.get(absPath); + if (map && map.size !== 0) { + let names: Array = Array.from(map).flat(); + let sourcemapObj: any = JSON.parse(assets[key]._value); + sourcemapObj.nameMap = names; + assets[key]._value = JSON.stringify(sourcemapObj); + } + } + } + }); + } + ); + + compilation.hooks.succeedModule.tap('findModule', (module) => { + if (module && module.error) { + const errorLog: string = module.error.toString(); + if (module.resourceResolveData && module.resourceResolveData.path && + /Module parse failed/.test(errorLog) && /Invalid regular expression:/.test(errorLog)) { + this.mErrorCount++; + const errorInfos: string[] = errorLog.split('\n>')[1].split(';'); + if (errorInfos && errorInfos.length > 0 && errorInfos[0]) { + const errorInformation: string = `ERROR in ${module.resourceResolveData.path}\n The following syntax is incorrect.\n > ${errorInfos[0]}`; + this.printErrorMessage(parseErrorMessage(errorInformation), false, module.error); + } + } + } + }); + + compilation.hooks.buildModule.tap('findModule', (module) => { + if (module.context) { + if (module.context.indexOf(projectConfig.projectPath) >= 0) { + return; + } + const modulePath: string = path.join(module.context); + const srcIndex: number = modulePath.lastIndexOf(MODULE_ETS_PATH); + if (srcIndex < 0) { + return; + } + const moduleSharePath: string = path.resolve(modulePath.substring(0, srcIndex), MODULE_SHARE_PATH); + if (fs.existsSync(moduleSharePath)) { + this.moduleSharePaths.add(moduleSharePath); + } + } + }); + + compilation.hooks.finishModules.tap('finishModules', handleFinishModules.bind(this)); + }); + + compiler.hooks.afterCompile.tap('copyFindModule', () => { + this.moduleSharePaths.forEach(modulePath => { + circularFile(modulePath, path.resolve(projectConfig.buildPath, BUILD_SHARE_PATH)); + }); + }); + + compiler.hooks.compilation.tap('CommonAsset', compilation => { + compilation.hooks.processAssets.tap( + { + name: 'GLOBAL_COMMON_MODULE_CACHE', + stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS + }, + (assets) => { + const GLOBAL_COMMON_MODULE_CACHE = ` + globalThis["__common_module_cache__${projectConfig.hashProjectPath}"] =` + + ` globalThis["__common_module_cache__${projectConfig.hashProjectPath}"] || {};`; + if (assets['commons.js']) { + assets['commons.js'] = new CachedSource( + new ConcatSource(assets['commons.js'], GLOBAL_COMMON_MODULE_CACHE)); + } else if (assets['vendors.js']) { + assets['vendors.js'] = new CachedSource( + new ConcatSource(assets['vendors.js'], GLOBAL_COMMON_MODULE_CACHE)); + } + }); + }); + + compiler.hooks.compilation.tap('Require', compilation => { + JavascriptModulesPlugin.getCompilationHooks(compilation).renderRequire.tap('renderRequire', + (source) => { + return `var commonCachedModule = globalThis` + + `["__common_module_cache__${projectConfig.hashProjectPath}"] ? ` + + `globalThis["__common_module_cache__${projectConfig.hashProjectPath}"]` + + `[moduleId]: null;\n` + + `if (commonCachedModule) { return commonCachedModule.exports; }\n` + + source.replace('// Execute the module function', + `function isCommonModue(moduleId) { + if (globalThis["webpackChunk${projectConfig.hashProjectPath}"]) { + const length = globalThis["webpackChunk${projectConfig.hashProjectPath}"].length; + switch (length) { + case 1: + return globalThis["webpackChunk${projectConfig.hashProjectPath}"][0][1][moduleId]; + case 2: + return globalThis["webpackChunk${projectConfig.hashProjectPath}"][0][1][moduleId] || + globalThis["webpackChunk${projectConfig.hashProjectPath}"][1][1][moduleId]; + } + } + return undefined; + }\n` + + `if (globalThis["__common_module_cache__${projectConfig.hashProjectPath}"]` + + ` && String(moduleId).indexOf("?name=") < 0 && isCommonModue(moduleId)) {\n` + + ` globalThis["__common_module_cache__${projectConfig.hashProjectPath}"]` + + `[moduleId] = module;\n}`); + }); + }); + + compiler.hooks.entryOption.tap('beforeRun', () => { + const rootFileNames: string[] = []; + Object.values(projectConfig.entryObj).forEach((fileName: string) => { + rootFileNames.push(fileName.replace('?entry', '')); + }); + if (process.env.watchMode === 'true') { + globalProgram.watchProgram = ts.createWatchProgram( + createWatchCompilerHost(rootFileNames, printDiagnostic, + this.delayPrintLogCount.bind(this), this.resetTsErrorCount)); + } else { + serviceChecker(rootFileNames); + } + setChecker(); + }); + + compiler.hooks.watchRun.tap('WatchRun', (comp) => { + process.env.watchEts = 'start'; + checkErrorMessage.clear(); + this.clearCount(); + comp.modifiedFiles = comp.modifiedFiles || []; + comp.removedFiles = comp.removedFiles || []; + const watchModifiedFiles: string[] = [...comp.modifiedFiles]; + let watchRemovedFiles: string[] = [...comp.removedFiles]; + if (watchRemovedFiles.length) { + this.removedFiles = watchRemovedFiles; + } + if (watchModifiedFiles.length) { + watchModifiedFiles.some((item: string) => { + if (fs.statSync(item).isFile() && !/.(ts|ets)$/.test(item)) { + process.env.watchTs = 'end'; + return true; + } + return false; + }); + } + if (shouldWriteChangedList(watchModifiedFiles, watchRemovedFiles)) { + writeFileSync(projectConfig.changedFileList, JSON.stringify( + getHotReloadFiles(watchModifiedFiles, watchRemovedFiles, hotReloadSupportFiles))); + } + incrementWatchFile(watchModifiedFiles, watchRemovedFiles); + }); + + compiler.hooks.done.tap('Result States', (stats: Stats) => { + if (projectConfig.isPreview && projectConfig.aceSoPath && + useOSFiles && useOSFiles.size > 0) { + writeUseOSFiles(useOSFiles); + } + if (projectConfig.compileHar) { + this.incrementalFileInHar.forEach((jsBuildFilePath, jsCacheFilePath) => { + const sourceCode: string = fs.readFileSync(jsCacheFilePath, 'utf-8'); + writeFileSync(jsBuildFilePath, sourceCode); + }); + } + this.mStats = stats; + this.warningCount = 0; + this.noteCount = 0; + if (this.mStats.compilation.warnings) { + this.mWarningCount = this.mStats.compilation.warnings.length; + } + this.printResult(); + }); + } + + private resetTsErrorCount(): void { + checkerResult.count = 0; + warnCheckerResult.count = 0; + } + + private printResult(): void { + this.printWarning(); + this.printError(); + if (process.env.watchMode === 'true') { + process.env.watchEts = 'end'; + this.delayPrintLogCount(true); + } else { + this.printLogCount(); + } + } + + private delayPrintLogCount(isCompile: boolean = false) { + if (process.env.watchEts === 'end' && process.env.watchTs === 'end') { + this.printLogCount(); + process.env.watchTs = 'start'; + this.removedFiles = []; + } else if (isCompile && this.removedFiles.length && this.mErrorCount === 0 && this.mPreErrorCount > 0) { + this.printLogCount(); + } + this.mPreErrorCount = this.mErrorCount; + } + + private printLogCount(): void { + let errorCount: number = this.mErrorCount + checkerResult.count; + const warnCount: number = this.warningCount + warnCheckerResult.count; + if (errorCount + warnCount + this.noteCount > 0 || process.env.abcCompileSuccess === 'false') { + let result: string; + let resultInfo: string = ''; + if (errorCount > 0) { + resultInfo += `ERROR:${errorCount}`; + result = 'FAIL '; + process.exitCode = 1; + } else { + result = 'SUCCESS '; + } + if (process.env.abcCompileSuccess === 'false') { + result = 'FAIL '; + } + if (warnCount > 0) { + resultInfo += ` WARN:${warnCount}`; + } + if (this.noteCount > 0) { + resultInfo += ` NOTE:${this.noteCount}`; + } + if (result === 'SUCCESS ' && process.env.watchMode === 'true') { + this.printPreviewResult(resultInfo); + } else { + logger.info(this.blue, 'COMPILE RESULT:' + result + `{${resultInfo}}`, this.reset); + } + } else { + if (process.env.watchMode === 'true') { + this.printPreviewResult(); + } else { + console.info(this.blue, 'COMPILE RESULT:SUCCESS ', this.reset); + } + } + } + + private clearCount(): void { + this.mErrorCount = 0; + this.warningCount = 0; + this.noteCount = 0; + process.env.abcCompileSuccess = 'true'; + } + + private printPreviewResult(resultInfo: string = ''): void { + const workerNum: number = Object.keys(cluster.workers).length; + const blue: string = this.blue; + const reset: string = this.reset; + if (workerNum === 0) { + this.printSuccessInfo(blue, reset, resultInfo); + } + } + + private printSuccessInfo(blue: string, reset: string, resultInfo: string): void { + if (projectConfig.hotReload) { + this.hotReloadIncrementalTime.hotReloadIncrementalEndTime = new Date().getTime().toString(); + console.info(blue, 'Incremental build start: ' + this.hotReloadIncrementalTime.hotReloadIncrementalStartTime + + '\n' + 'Incremental build end: ' + this.hotReloadIncrementalTime.hotReloadIncrementalEndTime, reset); + } + if (resultInfo.length === 0) { + console.info(blue, 'COMPILE RESULT:SUCCESS ', reset); + } else { + console.info(blue, 'COMPILE RESULT:SUCCESS ' + `{${resultInfo}}`, reset); + } + } + + private printWarning(): void { + if (this.mWarningCount > 0) { + const warnings: Info[] = this.mStats.compilation.warnings; + const length: number = warnings.length; + for (let index = 0; index < length; index++) { + const message: string = warnings[index].message.replace(/^Module Warning\s*.*:\n/, '') + .replace(/\(Emitted value instead of an instance of Error\) BUILD/, ''); + if (/^NOTE/.test(message)) { + if (!checkErrorMessage.has(message)) { + this.noteCount++; + logger.info(this.blue, message.replace(/^NOTE/, 'ArkTS:NOTE'), this.reset, '\n'); + checkErrorMessage.add(message); + } + } else { + if (!checkErrorMessage.has(message)) { + this.warningCount++; + logger.warn(this.yellow, message.replace(/^WARN/, 'ArkTS:WARN'), this.reset, '\n'); + checkErrorMessage.add(message); + } + } + } + if (this.mWarningCount > length) { + this.warningCount = this.warningCount + this.mWarningCount - length; + } + } + } + + private printError(): void { + if (this.mStats.compilation.errors.length > 0) { + const errors: Info[] = [...this.mStats.compilation.errors]; + for (let index = 0; index < errors.length; index++) { + if (errors[index].issue) { + if (!checkErrorMessage.has(errors[index].issue)) { + this.mErrorCount++; + const position: string = errors[index].issue.location + ? `:${errors[index].issue.location.start.line}:${errors[index].issue.location.start.column}` + : ''; + const location: string = errors[index].issue.file.replace(/\\/g, '/') + position; + const detail: string = errors[index].issue.message; + logger.error(this.red, 'ArkTS:ERROR File: ' + location, this.reset); + logger.error(this.red, detail, this.reset, '\n'); + checkErrorMessage.add(errors[index].issue); + } + } else if (/BUILDERROR/.test(errors[index].message)) { + if (!checkErrorMessage.has(errors[index].message)) { + this.mErrorCount++; + const errorMessage: string = errors[index].message.replace(/^Module Error\s*.*:\n/, '') + .replace(/\(Emitted value instead of an instance of Error\) BUILD/, '') + .replace(/^ERROR/, 'ArkTS:ERROR'); + this.printErrorMessage(errorMessage, true, errors[index]); + checkErrorMessage.add(errors[index].message); + } + } else if (!/TS[0-9]+:/.test(errors[index].message.toString()) && + !/Module parse failed/.test(errors[index].message.toString())) { + this.mErrorCount++; + let errorMessage: string = `${errors[index].message.replace(/\[tsl\]\s*/, '') + .replace(/\u001b\[.*?m/g, '').replace(/\.ets\.ts/g, '.ets').trim()}\n`; + errorMessage = this.filterModuleError(errorMessage) + .replace(/^ERROR in /, 'ArkTS:ERROR File: ').replace(/\s{6}TS/g, ' TS') + .replace(/\(([0-9]+),([0-9]+)\)/, ':$1:$2'); + this.printErrorMessage(parseErrorMessage(errorMessage), false, errors[index]); + } + } + } + } + private printErrorMessage(errorMessage: string, lineFeed: boolean, errorInfo: Info): void { + const formatErrMsg = errorMessage.replace(/\\/g, '/'); + if (lineFeed) { + logger.error(this.red, formatErrMsg + '\n', this.reset); + } else { + logger.error(this.red, formatErrMsg, this.reset); + } + } + private filterModuleError(message: string): string { + if (/You may need an additional loader/.test(message) && transformLog && transformLog.sourceFile) { + const fileName: string = transformLog.sourceFile.fileName; + const errorInfos: string[] = message.split('You may need an additional loader to handle the result of these loaders.'); + if (errorInfos && errorInfos.length > 1 && errorInfos[1]) { + message = `ERROR in ${fileName}\n The following syntax is incorrect.${errorInfos[1]}`; + } + } + return message; + } +} + +function handleFinishModules(modules, callback) { + if (projectConfig.compileHar) { + modules.forEach(module => { + if (module !== undefined && module.resourceResolveData !== undefined) { + const filePath: string = module.resourceResolveData.path; + if (!filePath.match(/node_modules/)) { + const jsCacheFilePath: string = genTemporaryPath(filePath, projectConfig.moduleRootPath, process.env.cachePath, + projectConfig, undefined); + const jsBuildFilePath: string = genTemporaryPath(filePath, projectConfig.moduleRootPath, + projectConfig.buildPath, projectConfig, undefined, true); + if (filePath.match(/\.e?ts$/)) { + this.incrementalFileInHar.set(jsCacheFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts'), + jsBuildFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts')); + this.incrementalFileInHar.set(jsCacheFilePath.replace(/\.e?ts$/, '.js'), jsBuildFilePath.replace(/\.e?ts$/, '.js')); + } else { + this.incrementalFileInHar.set(jsCacheFilePath, jsBuildFilePath); + } + } + } + }); + } +} diff --git a/compiler/src/interop/src/component_map.ts b/compiler/src/interop/src/component_map.ts new file mode 100644 index 0000000000000000000000000000000000000000..01e732a2c69b510ec3f521b645964beb281b89ee --- /dev/null +++ b/compiler/src/interop/src/component_map.ts @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2021 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'); +import ts from 'typescript'; + +const COMPONENTS = 'components'; +const FORM_COMPONENTS = 'form_components'; +export const COMPONENT_MAP: any = {}; +export const FORM_MAP: any = {}; + +export let COMMON_ATTRS: Set = new Set([]); + +(function readComponents() { + const componentPath: Map = new Map([ + [`${COMPONENTS}`, `../${COMPONENTS}`], + [`${FORM_COMPONENTS}`, `../${FORM_COMPONENTS}`] + ]); + for (const [id, relPath] of componentPath.entries()) { + const componentsFile: string = path.join(__dirname, relPath); + const files: string[] = fs.readdirSync(componentsFile); + files.forEach(function(item) { + const fPath: string = path.join(componentsFile, item); + const json: any = require(fPath); + const stat: any = fs.statSync(fPath); + if (stat.isFile()) { + if (json.name) { + const compName: string = json.name; + delete json.name; + if (id === COMPONENTS) { + COMPONENT_MAP[compName] = json; + } else if (id === FORM_COMPONENTS) { + FORM_MAP[compName] = json; + } + } else { + if (id === COMPONENTS) { + COMMON_ATTRS = new Set(json.attrs); + } + } + } + }); + } +})(); + +const TRANSITION_COMMON_ATTRS: Set = new Set([ + 'slide', 'translate', 'scale', 'opacity' +]); +export const GESTURE_ATTRS: Set = new Set([ + 'gesture', 'parallelGesture', 'priorityGesture' +]); + +export const ID_ATTRS: Map> = new Map(); + +export const forbiddenUseStateType: Set = new Set(['Scroller', 'SwiperScroller', + 'VideoController', 'WebController', 'CustomDialogController', 'SwiperController', + 'TabsController', 'CalendarController', 'AbilityController', 'XComponentController', + 'CanvasRenderingContext2D', 'CanvasGradient', 'ImageBitmap', 'ImageData', 'Path2D', + 'RenderingContextSettings', 'OffscreenCanvasRenderingContext2D', 'PatternLockController', + 'TextAreaController', 'TextInputController', 'TextTimerController', 'SearchController', 'RichEditorController', + 'ArcSwiperController' +]); + +const FOREACH_ATTRIBUTE = ['onMove']; +export const INNER_COMPONENT_NAMES: Set = new Set(); +export const NO_DEBUG_LINE_COMPONENT: Set = new Set(); +export const BUILDIN_CONTAINER_COMPONENT: Set = new Set(); +export const COMPONENT_SYSTEMAPI_NAMES: Set = new Set(); +export const BUILDIN_STYLE_NAMES: Set = new Set([ + ...COMMON_ATTRS, ...GESTURE_ATTRS, ...TRANSITION_COMMON_ATTRS, ...FOREACH_ATTRIBUTE +]); +export const AUTOMIC_COMPONENT: Set = new Set(); +export const SINGLE_CHILD_COMPONENT: Set = new Set(); +export const SPECIFIC_CHILD_COMPONENT: Map> = new Map(); +export const SPECIFIC_PARENT_COMPONENT: Map> = new Map(); +export const GESTURE_TYPE_NAMES: Set = new Set([ + 'TapGesture', 'LongPressGesture', 'PanGesture', 'PinchGesture', 'RotationGesture', 'GestureGroup', + 'SwipeGesture' +]); +export const CUSTOM_BUILDER_METHOD: Set = new Set(); +export const INNER_CUSTOM_LOCALBUILDER_METHOD: Set = new Set(); +export const INNER_STYLE_FUNCTION: Map = new Map(); +export const GLOBAL_STYLE_FUNCTION: Map = new Map(); + +export interface ExtendParamterInterfance { + attribute: string, + parameterCount: number +} +export const EXTEND_ATTRIBUTE: Map> = new Map(); +export const STYLES_ATTRIBUTE: Set = new Set(); + +export const INTERFACE_NODE_SET: Set = new Set(); + +export const INNER_CUSTOM_BUILDER_METHOD: Set = new Set(); +export const GLOBAL_CUSTOM_BUILDER_METHOD: Set = new Set(); + +export const JS_BIND_COMPONENTS: Set = new Set([ + 'ForEach', 'LazyForEach', ...GESTURE_TYPE_NAMES, 'Gesture', + 'PanGestureOption', 'CustomDialogController', 'Storage', 'Scroller', 'SwiperController', + 'TabsController', 'CalendarController', 'AbilityController', 'VideoController', 'WebController', + 'XComponentController', 'CanvasRenderingContext2D', 'CanvasGradient', 'ImageBitmap', 'ImageData', + 'Path2D', 'RenderingContextSettings', 'OffscreenCanvasRenderingContext2D', 'DatePickerDialog', + 'TextPickerDialog', 'AlertDialog', 'ContextMenu', 'ActionSheet', 'PatternLockController', + 'TimePickerDialog', 'CalendarPickerDialog', 'ArcSwiperController' +]); + +export const NEEDPOP_COMPONENT: Set = new Set(['Blank', 'Search']); + +export const CUSTOM_BUILDER_PROPERTIES: Set = new Set(['background', 'bindPopup', 'bindMenu', 'bindContextMenu', 'title', + 'menus', 'toolBar', 'tabBar', 'onDragStart', 'onItemDragStart', 'swipeAction', 'bindContentCover', 'bindSheet', + 'navDestination', 'overlay', 'toolbarConfiguration', 'customKeyboard', 'bindSelectionMenu', 'description', + 'showUnit', 'customKeyboard', 'create', 'addBuilderSpan', 'dragPreview', 'accessibilityVirtualNode']); +export const CUSTOM_BUILDER_PROPERTIES_WITHOUTKEY: Set = new Set(['showUnit', 'create']); +export const CUSTOM_BUILDER_CONSTRUCTORS: Set = new Set(['MenuItem', 'MenuItemGroup', 'Refresh', 'WaterFlow', 'Radio', 'Checkbox']); + +(function initComponent() { + Object.keys(COMPONENT_MAP).forEach((componentName) => { + INNER_COMPONENT_NAMES.add(componentName); + JS_BIND_COMPONENTS.add(componentName); + if (!COMPONENT_MAP[componentName].atomic) { + BUILDIN_CONTAINER_COMPONENT.add(componentName); + } else { + AUTOMIC_COMPONENT.add(componentName); + } + if (COMPONENT_MAP[componentName].single) { + SINGLE_CHILD_COMPONENT.add(componentName); + } + if (COMPONENT_MAP[componentName].children) { + SPECIFIC_CHILD_COMPONENT.set(componentName, + new Set([...COMPONENT_MAP[componentName].children])); + } + if (COMPONENT_MAP[componentName].attrs && COMPONENT_MAP[componentName].attrs.length) { + COMPONENT_MAP[componentName].attrs.forEach((item: string) => { + BUILDIN_STYLE_NAMES.add(item); + }); + if (COMPONENT_MAP[componentName].systemApi) { + COMPONENT_MAP[componentName].attrs.forEach((item: string) => { + COMPONENT_SYSTEMAPI_NAMES.add(item); + }); + } + } + if (COMPONENT_MAP[componentName].noDebugLine) { + NO_DEBUG_LINE_COMPONENT.add(componentName); + } + if (COMPONENT_MAP[componentName].parents) { + SPECIFIC_PARENT_COMPONENT.set(componentName, + new Set([...COMPONENT_MAP[componentName].parents])); + } + }); +})(); + +export function resetComponentMap(): void { + ID_ATTRS.clear(); + EXTEND_ATTRIBUTE.clear(); + STYLES_ATTRIBUTE.clear(); +} diff --git a/compiler/src/interop/src/constant_define.ts b/compiler/src/interop/src/constant_define.ts new file mode 100644 index 0000000000000000000000000000000000000000..c518ca07dcf6fb17e7862ccba4bbc0ca07dada75 --- /dev/null +++ b/compiler/src/interop/src/constant_define.ts @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const NAVIGATION_BUILDER_REGISTER: string = 'NavigationBuilderRegister'; + +const MONITOR: string = 'Monitor'; +const COMPUTED: string = 'Computed'; +const REQUIRE: string = 'Require'; +const BUILDER_PARAM: string = 'BuilderParam'; +const DECORATOR_BUILDER_PARAM: string = '@BuilderParam'; +const MONITOR_DECORATOR: string = '@Monitor'; +const COMPUTED_DECORATOR: string = '@Computed'; +const EVENT_DECORATOR: string = '@Event'; + +const COMPONENT_MEMBER_DECORATOR_V1: string[] = [ + '@State', '@Prop', '@Link', '@Provide', '@Consume', '@Watch', '@StorageLink', '@StorageProp', + '@LocalStorageLink', '@LocalStorageProp', '@ObjectLink' +]; + +const COMPONENT_MEMBER_DECORATOR_V2: string[] = [ + '@Local', '@Param', '@Once', '@Event', '@Provider', '@Consumer' +]; +const STRUCT_CLASS_MEMBER_DECORATOR_V2: string[] = [ + '@Monitor', '@Computed' +]; +const DECORATOR_V2: string[] = [ + ...COMPONENT_MEMBER_DECORATOR_V2, ...STRUCT_CLASS_MEMBER_DECORATOR_V2 +]; + +const STRUCT_PARENT: string = 'ViewV2'; +const INIT_PARAM: string = 'initParam'; +const UPDATE_PARAM: string = 'updateParam'; +const RESET_PARAM: string = 'resetParam'; +const UPDATE_STATE_VARS: string = 'updateStateVars'; +const RESET_STATE_VARS_METHOD: string = 'resetStateVarsOnReuse'; +const RESET_CONSUMER: string = 'resetConsumer'; +const RESET_COMPUTED: string = 'resetComputed'; +const RESET_MONITORS_ON_REUSE: string = 'resetMonitorsOnReuse'; +const OBJECT_TYPE: string = 'Object'; +const REUSE_OR_CREATE_METHOD: string = 'reuseOrCreateNewComponent'; +const COMPONENT_CLASS: string = 'componentClass'; +const GET_PARAMS: string = 'getParams'; +const GET_REUSE_ID: string = 'getReuseId'; +const EXTRA_INFO: string = 'extraInfo'; + +const BYTE_CODE_HAR: string = 'byteCodeHar'; +const CLOSED_SOURCE_HAR: string = 'closedSourceHar'; +const SHARED_HSP: string = 'sharedHsp'; +const MODULE_TYPE: string = 'moduleType'; +const HAR_DEFAULT_PAGE_PATH: string = '__harDefaultPagePath__'; +const HAR_DEFAULT_INTEGRATED_HSP_TYPE: string = '__harDefaultIntegratedHspType__'; +const FOLLOW_WITH_HAP: string = 'followWithHap'; + +export default { + NAVIGATION_BUILDER_REGISTER, + MONITOR, + COMPUTED, + STRUCT_PARENT, + COMPONENT_MEMBER_DECORATOR_V1, + COMPONENT_MEMBER_DECORATOR_V2, + INIT_PARAM, + UPDATE_PARAM, + RESET_PARAM, + UPDATE_STATE_VARS, + REQUIRE, + MONITOR_DECORATOR, + COMPUTED_DECORATOR, + DECORATOR_V2, + BUILDER_PARAM, + DECORATOR_BUILDER_PARAM, + BYTE_CODE_HAR, + CLOSED_SOURCE_HAR, + SHARED_HSP, + MODULE_TYPE, + HAR_DEFAULT_PAGE_PATH, + HAR_DEFAULT_INTEGRATED_HSP_TYPE, + FOLLOW_WITH_HAP, + EVENT_DECORATOR, + RESET_STATE_VARS_METHOD, + RESET_CONSUMER, + RESET_COMPUTED, + RESET_MONITORS_ON_REUSE, + OBJECT_TYPE, + REUSE_OR_CREATE_METHOD, + COMPONENT_CLASS, + GET_PARAMS, + GET_REUSE_ID, + EXTRA_INFO +}; diff --git a/compiler/src/interop/src/create.ts b/compiler/src/interop/src/create.ts new file mode 100644 index 0000000000000000000000000000000000000000..aaf96297fda1cd8db889284ccf3647fa7775b4cd --- /dev/null +++ b/compiler/src/interop/src/create.ts @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2021 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 program = require('commander'); + +program.parse(process.argv); +let name:string = 'HelloAce'; +let appID:string = 'ace.helloworld'; +let appName:string = 'HelloAce'; +if (program.args && program.args[0]) { + name = program.args[0]; + appID = program.args[0]; + appName = program.args[0]; +} + +const regPath: RegExp = /[`~!@#$%^&*()_+<>?:"{},./;'[\]]/im; + +/* + * Create sample project and files. + * @param dist {String} + */ +function createProject(dist: string) { + const dist_ = dist.trim().split('/'); + if (dist_.length > 1 || regPath.test(dist)) { + return console.error( + 'ERROR: The project name cannot be a path nor contain any special symbol.\n' + + "NOTE: To create the template project, run 'npm run create' in the root directory.\n" + + "NOTE: To customize the project name, run 'npm run create '."); + } + const appPath:string = dist + '/app.ets'; + const manifestPath:string = dist + '/manifest.json'; + const indexPath:string = dist + '/pages/index.ets'; + + const app:string = `export default { + onCreate() { + console.info('Application onCreate') + }, + onDestroy() { + console.info('Application onDestroy') + }, +}`; + + const manifest:string = `{ + "appID": "com.example.` + appID + `", + "appName": "` + appName + `", + "versionName": "1.0.0", + "versionCode": 1, + "minPlatformVersion": "1.0.1", + "pages": [ + "pages/index" + ], + "window": { + "designWidth": 750, + "autoDesignWidth": false + } +}`; + + const index:string = `@Entry +@Component +struct MyComponent { + private value1: string = "hello world 1"; + private value2: string = "hello world 2"; + private value3: string = "hello world 3"; + + build() { + Column() { + Text(this.value1); + Text(this.value2); + Text(this.value3); + } + } +}`; + + fs.mkdir(dist + '/pages', { recursive: true }, (err) => { + if (err) { + return console.error('ERROR: Failed to create project directory.'); + } + fs.writeFile(appPath, app, (err) => { + if (err) { + return console.error('ERROR: Failed to write app.ets.'); + } + return undefined; + }); + fs.writeFile(manifestPath, manifest, (err) => { + if (err) { + return console.error('ERROR: Failed to write manifest.json.'); + } + return undefined; + }); + fs.writeFile(indexPath, index, (err) => { + if (err) { + return console.error('ERROR: Failed to write index.ets.'); + } + return undefined; + }); + return undefined; + }); + return undefined; +} + +createProject(name); diff --git a/compiler/src/interop/src/create_ast_node_utils.ts b/compiler/src/interop/src/create_ast_node_utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..8df804d7a18c3d5c3c5a9fb749b2aa4f865d32fa --- /dev/null +++ b/compiler/src/interop/src/create_ast_node_utils.ts @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ts from 'typescript'; + +import { + ELMTID, + FINALIZE_CONSTRUCTION +} from './pre_define'; + +import { + IFileLog, + LogInfo +} from './utils'; + +class FileLog implements IFileLog { + private _sourceFile: ts.SourceFile | undefined; + private _errors: LogInfo[] = []; + + public get sourceFile(): ts.SourceFile | undefined { + return this._sourceFile; + } + + public set sourceFile(newValue: ts.SourceFile) { + this._sourceFile = newValue; + } + + public get errors(): LogInfo[] { + return this._errors; + } + + public set errors(newValue: LogInfo[]) { + this._errors = newValue; + } + + public cleanUp(): void { + this._sourceFile = undefined; + this._errors = []; + } +} + + +function createParameterDeclaration(name: string): ts.ParameterDeclaration { + let initializer: ts.Expression; + if (name === ELMTID) { + initializer = ts.factory.createPrefixUnaryExpression( + ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral('1')); + } + return ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(name), undefined, undefined, initializer); +} + +function createFinalizeConstruction(freezeParamNode: ts.Expression): ts.Statement { + const params: ts.Expression[] = []; + if (freezeParamNode) { + params.push(freezeParamNode); + } + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(FINALIZE_CONSTRUCTION) + ), + undefined, + params + )); +} + +function createImportNodeForModuleInfo(): ts.ImportDeclaration { + return ts.factory.createImportDeclaration( + undefined, ts.factory.createImportClause(false, undefined, + ts.factory.createNamedImports([ts.factory.createImportSpecifier( + false, undefined, ts.factory.createIdentifier('__MODULE_NAME__') + ), ts.factory.createImportSpecifier(false, undefined, + ts.factory.createIdentifier('__BUNDLE_NAME__'))]) + ), ts.factory.createStringLiteral('ModuleInfo'), undefined + ); +} + +export default { + FileLog: FileLog, + createParameterDeclaration: createParameterDeclaration, + createFinalizeConstruction: createFinalizeConstruction, + createImportNodeForModuleInfo: createImportNodeForModuleInfo +}; diff --git a/compiler/src/interop/src/do_arkTS_linter.ts b/compiler/src/interop/src/do_arkTS_linter.ts new file mode 100644 index 0000000000000000000000000000000000000000..fbf62788f12e5d28e276a23b5e0b2a35b51bb2f6 --- /dev/null +++ b/compiler/src/interop/src/do_arkTS_linter.ts @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2023 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 fs from 'fs'; +import path from 'path'; +import * as ts from 'typescript'; +import { projectConfig } from '../main'; +import { toUnixPath } from './utils'; +import { + ERROR_DESCRIPTION, + LINTER_SUBSYSTEM_CODE, + ERROR_TYPE_CODE, + EXTENSION_CODE, + HvigorErrorInfo, +} from './hvigor_error_code/hvigor_error_info'; + +const arkTSDir: string = 'ArkTS'; +const arkTSLinterOutputFileName: string = 'ArkTSLinter_output.json'; +const spaceNumBeforeJsonLine = 2; +const complementSize: number = 3; +const complementCode: string = '0'; + +interface OutputInfo { + categoryInfo: string | undefined; + fileName: string | undefined; + line: number | undefined; + character: number | undefined; + messageText: string | ts.DiagnosticMessageChain; +} + +export enum ArkTSLinterMode { + NOT_USE = 0, + COMPATIBLE_MODE = 1, + STANDARD_MODE = 2 +} + +export enum ArkTSVersion { + ArkTS_1_0, + ArkTS_1_1, +} + +export type ProcessDiagnosticsFunc = (diagnostics: ts.Diagnostic, errorCodeLogger?: Object | undefined) => void; + +function getArkTSVersionString(arkTSVersion: ArkTSVersion): string { + return arkTSVersion === ArkTSVersion.ArkTS_1_0 ? 'ArkTS_1_0' : 'ArkTS_1_1'; +} + +export function doArkTSLinter(arkTSVersion: ArkTSVersion, arkTSMode: ArkTSLinterMode, + builderProgram: ts.BuilderProgram, printDiagnostic: ProcessDiagnosticsFunc, shouldWriteFile: boolean = true, + buildInfoWriteFile?: ts.WriteFileCallback, errorCodeLogger?: Object | undefined): ts.Diagnostic[] { + if (arkTSMode === ArkTSLinterMode.NOT_USE) { + return []; + } + + let diagnostics: ts.Diagnostic[] = []; + + if (arkTSVersion === ArkTSVersion.ArkTS_1_0) { + diagnostics = ts.ArkTSLinter_1_0.runArkTSLinter(builderProgram, /*srcFile*/ undefined, buildInfoWriteFile, + getArkTSVersionString(arkTSVersion)); + } else { + diagnostics = ts.ArkTSLinter_1_1.runArkTSLinter(builderProgram, /*srcFile*/ undefined, buildInfoWriteFile, + getArkTSVersionString(arkTSVersion)); + } + + removeOutputFile(); + if (diagnostics.length === 0) { + return []; + } + + if (arkTSMode === ArkTSLinterMode.COMPATIBLE_MODE) { + processArkTSLinterReportAsWarning(diagnostics, printDiagnostic, shouldWriteFile); + } else { + processArkTSLinterReportAsError(diagnostics, printDiagnostic, errorCodeLogger); + } + + return diagnostics; +} + +function processArkTSLinterReportAsError(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc, errorCodeLogger?: Object | undefined): void { + diagnostics.forEach((diagnostic: ts.Diagnostic) => { + printDiagnostic(diagnostic, errorCodeLogger); + }); + printArkTSLinterFAQ(diagnostics, printDiagnostic); +} + +function processArkTSLinterReportAsWarning(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc, + shouldWriteFile: boolean): void { + const filePath = shouldWriteFile ? writeOutputFile(diagnostics) : undefined; + if (filePath === undefined) { + diagnostics.forEach((diagnostic: ts.Diagnostic) => { + const originalCategory = diagnostic.category; + diagnostic.category = ts.DiagnosticCategory.Warning; + printDiagnostic(diagnostic); + diagnostic.category = originalCategory; + }); + printArkTSLinterFAQ(diagnostics, printDiagnostic); + return; + } + const logMessage = `Has ${diagnostics.length} ArkTS Linter Error. You can get the output in ${filePath}`; + const arkTSDiagnostic: ts.Diagnostic = { + file: undefined, + start: undefined, + length: undefined, + messageText: logMessage, + category: ts.DiagnosticCategory.Warning, + code: -1, + reportsUnnecessary: undefined, + reportsDeprecated: undefined + }; + printDiagnostic(arkTSDiagnostic); + + printArkTSLinterFAQ(diagnostics, printDiagnostic); +} + +function writeOutputFile(diagnostics: ts.Diagnostic[]): string | undefined { + let filePath: string = toUnixPath(projectConfig.cachePath); + if (!fs.existsSync(filePath)) { + return undefined; + } + filePath = toUnixPath(path.join(filePath, arkTSDir)); + if (!fs.existsSync(filePath)) { + fs.mkdirSync(filePath); + } + filePath = toUnixPath((path.join(filePath, arkTSLinterOutputFileName))); + const outputInfo: OutputInfo[] = []; + diagnostics.forEach((diagnostic: ts.Diagnostic) => { + const { line, character }: ts.LineAndCharacter = + diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); + outputInfo.push({ + categoryInfo: diagnostic.category === ts.DiagnosticCategory.Error ? 'Error' : 'Warning', + fileName: diagnostic.file?.fileName, + line: line + 1, + character: character + 1, + messageText: diagnostic.messageText + }); + }); + let output: string | undefined = filePath; + try { + fs.writeFileSync(filePath, JSON.stringify(outputInfo, undefined, spaceNumBeforeJsonLine)); + } catch { + output = undefined; + } + return output; +} + +function removeOutputFile(): void { + let filePath: string = toUnixPath(projectConfig.cachePath); + if (!fs.existsSync(filePath)) { + return; + } + filePath = toUnixPath(path.join(filePath, arkTSDir)); + if (!fs.existsSync(filePath)) { + return; + } + filePath = toUnixPath((path.join(filePath, arkTSLinterOutputFileName))); + if (fs.existsSync(filePath)) { + fs.rmSync(filePath); + } +} + +function printArkTSLinterFAQ(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc): void { + if (diagnostics === undefined || diagnostics.length === undefined || diagnostics.length <= 0) { + return; + } + + const logMessageFAQ = 'For details about ArkTS syntax errors, see FAQs'; + const arkTSFAQDiagnostic: ts.Diagnostic = { + file: undefined, + start: undefined, + length: undefined, + messageText: logMessageFAQ, + category: ts.DiagnosticCategory.Warning, + code: -1, + reportsUnnecessary: undefined, + reportsDeprecated: undefined + }; + printDiagnostic(arkTSFAQDiagnostic); +} + +export function transfromErrorCode(code: number, positionMessage: string, message: string): HvigorErrorInfo { + return new HvigorErrorInfo() + .setCode(formatNumber(code)) + .setDescription(ERROR_DESCRIPTION) + .setCause(message) + .setPosition(positionMessage) + .setSolutions([]); +} + +function formatNumber(num: number): string { + // The minimum code in strict mode starts from 1000 + const extendedCode = num > 1000 ? EXTENSION_CODE : num.toString().padStart(complementSize, complementCode); + return LINTER_SUBSYSTEM_CODE + ERROR_TYPE_CODE + extendedCode; +} diff --git a/compiler/src/interop/src/ets_checker.ts b/compiler/src/interop/src/ets_checker.ts new file mode 100644 index 0000000000000000000000000000000000000000..bccf6fd45db9eb14d4c3f57462f2689c0e264bba --- /dev/null +++ b/compiler/src/interop/src/ets_checker.ts @@ -0,0 +1,1857 @@ +/* + * Copyright (c) 2022 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 fs from 'fs'; +import path from 'path'; +import * as ts from 'typescript'; +import * as crypto from 'crypto'; +const fse = require('fs-extra'); + +import { + projectConfig, + systemModules, + globalProgram, + sdkConfigs, + sdkConfigPrefix, + partialUpdateConfig, + resetProjectConfig, + resetGlobalProgram +} from '../main'; +import { + preprocessExtend, + preprocessNewExtend +} from './validate_ui_syntax'; +import { + INNER_COMPONENT_MEMBER_DECORATORS, + COMPONENT_DECORATORS_PARAMS, + COMPONENT_BUILD_FUNCTION, + STYLE_ADD_DOUBLE_DOLLAR, + $$, + PROPERTIES_ADD_DOUBLE_DOLLAR, + DOLLAR_BLOCK_INTERFACE, + COMPONENT_EXTEND_DECORATOR, + COMPONENT_BUILDER_DECORATOR, + ESMODULE, + EXTNAME_D_ETS, + EXTNAME_JS, + EXTNAME_ETS, + FOREACH_LAZYFOREACH, + COMPONENT_IF, + TS_WATCH_END_MSG, + TS_BUILD_INFO_SUFFIX, + HOT_RELOAD_BUILD_INFO_SUFFIX, + WATCH_COMPILER_BUILD_INFO_SUFFIX, + COMPONENT_STYLES_DECORATOR +} from './pre_define'; +import { + INNER_COMPONENT_NAMES, + JS_BIND_COMPONENTS, + BUILDIN_STYLE_NAMES +} from './component_map'; +import { logger } from './compile_info'; +import { + hasDecorator, + isString, + generateSourceFilesInHar, + startTimeStatisticsLocation, + stopTimeStatisticsLocation, + resolveModuleNamesTime, + CompilationTimeStatistics, + storedFileInfo, + toUnixPath, + isWindows, + isMac, + tryToLowerCasePath, + getRollupCache, + setRollupCache +} from './utils'; +import { + isExtendFunction, + isOriginalExtend +} from './process_ui_syntax'; +import { visualTransform } from './process_visual'; +import { tsWatchEmitter } from './fast_build/ets_ui/rollup-plugin-ets-checker'; +import { + doArkTSLinter, + ArkTSLinterMode, + ArkTSVersion, + transfromErrorCode, +} from './do_arkTS_linter'; +import { + getJsDocNodeCheckConfig, + isCardFile, + getRealModulePath, + getJsDocNodeConditionCheckResult +} from './fast_build/system_api/api_check_utils'; +import { sourceFileDependencies } from './fast_build/ark_compiler/common/ob_config_resolver'; +import { MemoryMonitor } from './fast_build/meomry_monitor/rollup-plugin-memory-monitor'; +import { MemoryDefine } from './fast_build/meomry_monitor/memory_define'; +import { + LINTER_SUBSYSTEM_CODE, + HvigorErrorInfo +} from './hvigor_error_code/hvigor_error_info'; +import { ErrorCodeModule } from './hvigor_error_code/const/error_code_module'; +import { buildErrorInfoFromDiagnostic } from './hvigor_error_code/utils'; +import { + getArkTSEvoDeclFilePath, + redirectToDeclFileForInterop +} from './fast_build/ark_compiler/interop/process_arkts_evolution'; +import { + FileManager, + getApiPathForInterop, + isMixCompile +} from './fast_build/ark_compiler/interop/interop_manager'; +import { + ARKTS_1_1, + ARKTS_1_2 +} from './fast_build/ark_compiler/interop/pre_define'; + +export interface LanguageServiceCache { + service?: ts.LanguageService; + pkgJsonFileHash?: string; + targetESVersion?: ts.ScriptTarget; + maxFlowDepth?: number; + preTsImportSendable?: boolean; +} + +export const SOURCE_FILES: Map = new Map(); +export let localPackageSet: Set = new Set(); +export const TSC_SYSTEM_CODE = '105'; + +export const MAX_FLOW_DEPTH_DEFAULT_VALUE = 2000; +export const MAX_FLOW_DEPTH_MAXIMUM_VALUE = 65535; + +export function readDeaclareFiles(): string[] { + const declarationsFileNames: string[] = []; + fs.readdirSync(path.resolve(__dirname, '../../../declarations')) + .forEach((fileName: string) => { + if (/\.d\.ts$/.test(fileName)) { + declarationsFileNames.push(path.resolve(__dirname, '../../../declarations', fileName)); + } + }); + return declarationsFileNames; +} + +const buildInfoWriteFile: ts.WriteFileCallback = (fileName: string, data: string) => { + if (fileName.includes(TS_BUILD_INFO_SUFFIX)) { + const fd: number = fs.openSync(fileName, 'w'); + fs.writeSync(fd, data, undefined, 'utf8'); + fs.closeSync(fd); + } +}; +// The collection records the file name and the corresponding version, where the version is the hash value of the text in last compilation. +const filesBuildInfo: Map = new Map(); + +export const compilerOptions: ts.CompilerOptions = ts.readConfigFile( + path.resolve(__dirname, '../../../tsconfig.json'), ts.sys.readFile).config.compilerOptions; +function setCompilerOptions(resolveModulePaths: string[]): void { + const allPath: Array = ['*']; + const basePath: string = path.resolve(projectConfig.projectPath); + if (process.env.compileTool === 'rollup' && resolveModulePaths && resolveModulePaths.length) { + resolveModulePaths.forEach((item: string) => { + if (!(/oh_modules$/.test(item) || /node_modules$/.test(item))) { + allPath.push(path.join(path.relative(basePath, item), '*')); + } + }); + } else { + if (!projectConfig.aceModuleJsonPath) { + allPath.push('../../../../../*'); //??? + allPath.push('../../*'); + } else { + allPath.push('../../../../*'); + allPath.push('../*'); + } + } + const suffix: string = projectConfig.hotReload ? HOT_RELOAD_BUILD_INFO_SUFFIX : TS_BUILD_INFO_SUFFIX; + const buildInfoPath: string = path.resolve(projectConfig.cachePath, '..', suffix); //??? + checkArkTSVersion(); + Object.assign(compilerOptions, { + 'allowJs': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE ? true : false, + 'checkJs': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE ? false : undefined, + 'emitNodeModulesFiles': true, + 'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Preserve, + 'module': ts.ModuleKind.CommonJS, + 'moduleResolution': ts.ModuleResolutionKind.NodeJs, + 'noEmit': true, + 'target': convertConfigTarget(getTargetESVersion()), + 'maxFlowDepth': getMaxFlowDepth(), + 'baseUrl': basePath, + 'paths': { + '*': allPath + }, + 'lib': convertConfigLib(getTargetESVersionLib()), + 'types': projectConfig.compilerTypes, + 'etsAnnotationsEnable': projectConfig.allowEtsAnnotations, + 'etsLoaderPath': projectConfig.etsLoaderPath, + 'needDoArkTsLinter': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE, + 'isCompatibleVersion': getArkTSLinterMode() === ArkTSLinterMode.COMPATIBLE_MODE, + 'skipTscOhModuleCheck': partialUpdateConfig.skipTscOhModuleCheck, + 'skipArkTSStaticBlocksCheck': partialUpdateConfig.skipArkTSStaticBlocksCheck, + // options incremental && tsBuildInfoFile are required for applying incremental ability of typescript + 'incremental': true, + 'tsBuildInfoFile': buildInfoPath, + 'tsImportSendableEnable': tsImportSendable, + 'skipPathsInKeyForCompilationSettings': reuseLanguageServiceForDepChange, + 'compatibleSdkVersionStage': projectConfig.compatibleSdkVersionStage, + 'compatibleSdkVersion': projectConfig.compatibleSdkVersion + }); + if (projectConfig.compileMode === ESMODULE) { + Object.assign(compilerOptions, { + 'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Remove, + 'module': ts.ModuleKind.ES2020 + }); + } + if (projectConfig.packageDir === 'oh_modules') { + Object.assign(compilerOptions, {'packageManagerType': 'ohpm'}); + } + readTsBuildInfoFileInCrementalMode(buildInfoPath, projectConfig); +} + +function checkArkTSVersion(): void { + const etsCheckerLogger = fastBuildLogger || logger; + if (getArkTSVersion() === ArkTSVersion.ArkTS_1_0 && tsImportSendable) { + const logMessage: string = 'ArkTS: ArkTSVersion1.0 does not support tsImportSendable in any condition'; + etsCheckerLogger.error('\u001b[31m' + logMessage); + tsImportSendable = false; + } +} + +// Change target to enum's value,e.g: "es2021" => ts.ScriptTarget.ES2021 +function convertConfigTarget(target: number | string): number | string { + if ((typeof target === 'number') && (target in ts.ScriptTarget)) { + return target; + } + return ts.convertCompilerOptionsFromJson({ 'target': target }, '').options.target; +} + +// Change lib to libMap's value,e.g: "es2021" => "lib.es2021.d.ts" +function convertConfigLib(libs: string[]): string[] { + let converted: boolean = true; + let libMapValues: string[] = Array.from(ts.libMap.values()); + for (let i = 0; i < libs.length; i++) { + if (!libMapValues.includes(libs[i])) { + converted = false; + break; + } + } + if (converted) { + return libs; + } + return ts.convertCompilerOptionsFromJson({ 'lib': libs }, '').options.lib; +} + +/** + * Read the source code information in the project of the last compilation process, and then use it + * to determine whether the file has been modified during this compilation process. + */ +function readTsBuildInfoFileInCrementalMode(buildInfoPath: string, projectConfig: Object): void { + if (!fs.existsSync(buildInfoPath) || !(projectConfig.compileHar || projectConfig.compileShared)) { + return; + } + + type FileInfoType = { + version: string; + affectsGlobalScope: boolean; + }; + type ProgramType = { + fileNames: string[]; + fileInfos: (FileInfoType | string)[]; + }; + let buildInfoProgram: ProgramType; + try { + const content: {program: ProgramType} = JSON.parse(fs.readFileSync(buildInfoPath, 'utf-8')); + buildInfoProgram = content.program; + if (!buildInfoProgram || !buildInfoProgram.fileNames || !buildInfoProgram.fileInfos) { + throw new Error('.tsbuildinfo content is invalid'); + } + } catch (err) { + fastBuildLogger.warn('\u001b[33m' + 'ArkTS: Failed to parse .tsbuildinfo file. Error message: ' + err.message.toString()); + return; + } + const buildInfoDirectory: string = path.dirname(buildInfoPath); + /** + * For the windos and mac platform, the file path in tsbuildinfo is in lowercase, while buildInfoDirectory is the original path (including uppercase). + * Therefore, the path needs to be converted to lowercase, and then perform path comparison. + */ + const isMacOrWin = isWindows() || isMac(); + const fileNames: string[] = buildInfoProgram.fileNames; + const fileInfos: (FileInfoType | string)[] = buildInfoProgram.fileInfos; + fileInfos.forEach((fileInfo, index) => { + const version: string = typeof fileInfo === 'string' ? fileInfo : fileInfo.version; + const absPath: string = path.resolve(buildInfoDirectory, fileNames[index]); + filesBuildInfo.set(isMacOrWin ? tryToLowerCasePath(absPath) : absPath, version); + }); +} + +interface extendInfo { + start: number, + end: number, + compName: string +} + +function createHash(str: string): string { + const hash = crypto.createHash('sha256'); + hash.update(str); + return hash.digest('hex'); +} + +export const fileHashScriptVersion: (fileName: string) => string = (fileName: string) => { + if (!fs.existsSync(fileName)) { + return '0'; + } + + let fileContent: string = fs.readFileSync(fileName).toString(); + let cacheInfo: CacheFileName = cache[path.resolve(fileName)]; + + // Error code corresponding to message `Cannot find module xx or its corresponding type declarations` + const errorCodeRequireRecheck: number = 2307; + + if (cacheInfo && cacheInfo.error === true && cacheInfo.errorCodes && cacheInfo.errorCodes.includes(errorCodeRequireRecheck)) { + // If this file had errors that require recheck in the last compilation, + // mark the file as modified by modifying its hash value, thereby triggering tsc to recheck. + fileContent += Date.now().toString(); + } + return createHash(fileContent); +}; + +// Reuse the last language service when dependency in oh-package.json5 changes to enhance performance in incremental building. +// Setting this to false will create a new language service on dependency changes, like a full rebuild. +const reuseLanguageServiceForDepChange: boolean = true; +// When dependency changes and reusing the last language service, enable this flag to recheck code dependent on those dependencies. +export let needReCheckForChangedDepUsers: boolean = false; + +export function createLanguageService(rootFileNames: string[], resolveModulePaths: string[], + compilationTime: CompilationTimeStatistics = null, rollupShareObject?: any): ts.LanguageService { + setCompilerOptions(resolveModulePaths); + const servicesHost: ts.LanguageServiceHost = { + getScriptFileNames: () => [...rootFileNames, ...readDeaclareFiles()], + getScriptVersion: fileHashScriptVersion, + getScriptSnapshot: function(fileName) { + if (!fs.existsSync(fileName)) { + return undefined; + } + if (/(? process.cwd(), + getCompilationSettings: () => compilerOptions, + getDefaultLibFileName: options => ts.getDefaultLibFilePath(options), + fileExists: ts.sys.fileExists, + readFile: ts.sys.readFile, + readDirectory: ts.sys.readDirectory, + resolveModuleNames: resolveModuleNames, + resolveTypeReferenceDirectives: resolveTypeReferenceDirectives, + directoryExists: ts.sys.directoryExists, + getDirectories: ts.sys.getDirectories, + getJsDocNodeCheckedConfig: (fileCheckedInfo: ts.FileCheckModuleInfo, sourceFileName: string) => { + return getJsDocNodeCheckConfig(fileCheckedInfo.currentFileName, sourceFileName); + }, + getFileCheckedModuleInfo: (containFilePath: string) => { + return { + fileNeedCheck: true, + checkPayload: undefined, + currentFileName: containFilePath + }; + }, + getJsDocNodeConditionCheckedResult: (jsDocFileCheckedInfo: ts.FileCheckModuleInfo, jsDocs: ts.JsDocTagInfo[]) => { + return getJsDocNodeConditionCheckResult(jsDocFileCheckedInfo, jsDocs); + }, + uiProps: [], + clearProps: function() { + dollarCollection.clear(); + extendCollection.clear(); + this.uiProps.length = 0; + }, + // TSC will re-do resolution if this callback return true. + hasInvalidatedResolutions: (filePath: string): boolean => { + return reuseLanguageServiceForDepChange && needReCheckForChangedDepUsers; + }, + isStaticSourceFile: (fileName: string): boolean => { + const languageVersion = FileManager.getInstance().getLanguageVersionByFilePath(fileName); + return languageVersion?.languageVersion === ARKTS_1_2; + }, + }; + + if (process.env.watchMode === 'true') { + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.ETS_CHECKER_CREATE_LANGUAGE_SERVICE); + const tsLanguageService = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); + MemoryMonitor.stopRecordStage(recordInfo); + return tsLanguageService; + } + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.ETS_CHECKER_CREATE_LANGUAGE_SERVICE); + const tsLanguageService = getOrCreateLanguageService(servicesHost, rootFileNames, rollupShareObject); + MemoryMonitor.stopRecordStage(recordInfo); + return tsLanguageService; +} + +export let targetESVersionChanged: boolean = false; + +function getOrCreateLanguageService(servicesHost: ts.LanguageServiceHost, rootFileNames: string[], + rollupShareObject?: any): ts.LanguageService { + let cacheKey: string = 'service'; + let cache: LanguageServiceCache | undefined = getRollupCache(rollupShareObject, projectConfig, cacheKey); + + let service: ts.LanguageService | undefined = cache?.service; + const currentHash: string | undefined = rollupShareObject?.projectConfig?.pkgJsonFileHash; + const currentTargetESVersion: ts.ScriptTarget = compilerOptions.target; + const currentMaxFlowDepth: number | undefined = compilerOptions.maxFlowDepth; + const lastHash: string | undefined = cache?.pkgJsonFileHash; + const lastTargetESVersion: ts.ScriptTarget | undefined = cache?.targetESVersion; + const lastMaxFlowDepth: number | undefined = cache?.maxFlowDepth; + const hashDiffers: boolean | undefined = currentHash && lastHash && currentHash !== lastHash; + const shouldRebuildForDepDiffers: boolean | undefined = reuseLanguageServiceForDepChange ? + (hashDiffers && !rollupShareObject?.depInfo?.enableIncre) : hashDiffers; + const targetESVersionDiffers: boolean | undefined = lastTargetESVersion && currentTargetESVersion && lastTargetESVersion !== currentTargetESVersion; + const maxFlowDepthDiffers: boolean | undefined = lastMaxFlowDepth && currentMaxFlowDepth && lastMaxFlowDepth !== currentMaxFlowDepth; + const tsImportSendableDiff: boolean = (cache?.preTsImportSendable === undefined && !tsImportSendable) ? + false : + cache?.preTsImportSendable !== tsImportSendable; + const shouldRebuild: boolean | undefined = shouldRebuildForDepDiffers || targetESVersionDiffers || tsImportSendableDiff || maxFlowDepthDiffers; + if (reuseLanguageServiceForDepChange && hashDiffers && rollupShareObject?.depInfo?.enableIncre) { + needReCheckForChangedDepUsers = true; + } + + if (!service || shouldRebuild) { + rebuildProgram(targetESVersionDiffers, tsImportSendableDiff, maxFlowDepthDiffers); + service = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); + } else { + // Found language service from cache, update root files + const updateRootFileNames = [...rootFileNames, ...readDeaclareFiles()]; + service.updateRootFiles(updateRootFileNames); + } + + const newCache: LanguageServiceCache = { + service: service, + pkgJsonFileHash: currentHash, + targetESVersion: currentTargetESVersion, + maxFlowDepth: currentMaxFlowDepth, + preTsImportSendable: tsImportSendable + }; + setRollupCache(rollupShareObject, projectConfig, cacheKey, newCache); + return service; +} + +function rebuildProgram(targetESVersionDiffers: boolean | undefined, tsImportSendableDiff: boolean, maxFlowDepthDiffers: boolean | undefined): void { + if (targetESVersionDiffers) { + // If the targetESVersion is changed, we need to delete the build info cahce files + deleteBuildInfoCache(compilerOptions.tsBuildInfoFile); + targetESVersionChanged = true; + } else if (tsImportSendableDiff || maxFlowDepthDiffers) { + // When tsImportSendable or maxFlowDepth is changed, we need to delete the build info cahce files + deleteBuildInfoCache(compilerOptions.tsBuildInfoFile); + } +} + +function deleteBuildInfoCache(tsBuildInfoFilePath: string): void { + // The file name of tsBuildInfoLinterFile is '.tsbuildinfo.linter', so we need to add '.linter' after tsBuildInfoFilePath + const tsBuildInfoLinterFilePath: string = tsBuildInfoFilePath + '.linter'; + deleteFile(tsBuildInfoFilePath); + deleteFile(tsBuildInfoLinterFilePath); +} + +function deleteFile(filePath: string): void { + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } +} + +interface CacheFileName { + mtimeMs: number, + children: string[], + parent: string[], + error: boolean, + errorCodes?: number[] +} +interface NeedUpdateFlag { + flag: boolean; +} +interface CheckerResult { + count: number +} + +interface WarnCheckerResult { + count: number +} + +interface WholeCache { + runtimeOS: string, + sdkInfo: string, + fileList: Cache +} +type Cache = Record; +export let cache: Cache = {}; +export const hotReloadSupportFiles: Set = new Set(); +export const shouldResolvedFiles: Set = new Set(); +export const appComponentCollection: Map> = new Map(); +const allResolvedModules: Set = new Set(); +// all files of tsc and rollup for obfuscation scanning. +export const allSourceFilePaths: Set = new Set(); +// Used to collect file paths that have not been converted toUnixPath. +export const allModuleIds: Set = new Set(); +export let props: string[] = []; + +export let fastBuildLogger = null; + +export const checkerResult: CheckerResult = { count: 0 }; +export const warnCheckerResult: WarnCheckerResult = { count: 0 }; +export let languageService: ts.LanguageService = null; +let tsImportSendable: boolean = false; +export function serviceChecker(rootFileNames: string[], newLogger: Object = null, resolveModulePaths: string[] = null, + compilationTime: CompilationTimeStatistics = null, rollupShareObject?: Object): void { + fastBuildLogger = newLogger; + let cacheFile: string = null; + tsImportSendable = rollupShareObject?.projectConfig.tsImportSendable; + if (projectConfig.xtsMode || process.env.watchMode === 'true') { + if (projectConfig.hotReload) { + rootFileNames.forEach(fileName => { + hotReloadSupportFiles.add(fileName); + }); + } + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.CREATE_LANGUAGE_SERVICE); + languageService = createLanguageService(rootFileNames, resolveModulePaths, compilationTime); + MemoryMonitor.stopRecordStage(recordInfo); + props = languageService.getProps(); + } else { + cacheFile = path.resolve(projectConfig.cachePath, '../.ts_checker_cache'); //??? + const [isJsonObject, cacheJsonObject]: [boolean, WholeCache | undefined] = isJsonString(cacheFile); + const wholeCache: WholeCache = isJsonObject ? cacheJsonObject : { 'runtimeOS': projectConfig.runtimeOS, 'sdkInfo': projectConfig.sdkInfo, 'fileList': {} }; + if (wholeCache.runtimeOS === projectConfig.runtimeOS && wholeCache.sdkInfo === projectConfig.sdkInfo) { + cache = wholeCache.fileList; + } else { + cache = {}; + } + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.CREATE_LANGUAGE_SERVICE); + languageService = createLanguageService(rootFileNames, resolveModulePaths, compilationTime, rollupShareObject); + MemoryMonitor.stopRecordStage(recordInfo); + } + + const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance(); + timePrinterInstance.setArkTSTimePrintSwitch(false); + timePrinterInstance.appendTime(ts.TimePhase.START); + startTimeStatisticsLocation(compilationTime ? compilationTime.createProgramTime : undefined); + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.GET_BUILDER_PROGRAM); + + globalProgram.builderProgram = languageService.getBuilderProgram(/*withLinterProgram*/ true); + globalProgram.program = globalProgram.builderProgram.getProgram(); + traverseProgramSourceFiles(languageService.getProps()); + props = languageService.getProps(); + timePrinterInstance.appendTime(ts.TimePhase.GET_PROGRAM); + MemoryMonitor.stopRecordStage(recordInfo); + stopTimeStatisticsLocation(compilationTime ? compilationTime.createProgramTime : undefined); + + collectAllFiles(globalProgram.program, undefined, undefined, rollupShareObject); + collectFileToIgnoreDiagnostics(rootFileNames); + startTimeStatisticsLocation(compilationTime ? compilationTime.runArkTSLinterTime : undefined); + const runArkTSLinterRecordInfo = MemoryMonitor.recordStage(MemoryDefine.RUN_ARK_TS_LINTER); + const errorCodeLogger: Object | undefined = !!rollupShareObject?.getHvigorConsoleLogger ? + rollupShareObject?.getHvigorConsoleLogger(LINTER_SUBSYSTEM_CODE) : undefined; + runArkTSLinter(errorCodeLogger); + MemoryMonitor.stopRecordStage(runArkTSLinterRecordInfo); + stopTimeStatisticsLocation(compilationTime ? compilationTime.runArkTSLinterTime : undefined); + + if (process.env.watchMode !== 'true') { + const processBuildHaprrecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP); + processBuildHap(cacheFile, rootFileNames, compilationTime, rollupShareObject); + MemoryMonitor.stopRecordStage(processBuildHaprrecordInfo); + } + if (globalProgram.program && + (process.env.watchMode !== 'true' && !projectConfig.isPreview && + !projectConfig.hotReload && !projectConfig.coldReload)) { + globalProgram.program.releaseTypeChecker(); + const allowGC: boolean = global && global.gc && typeof global.gc === 'function'; + if (allowGC) { + global.gc(); + } + } +} + +function traverseProgramSourceFiles(props: string[]): void { + globalProgram.program.getSourceFiles().forEach((sourceFile: ts.SourceFile) => { + checkUISyntax(sourceFile, sourceFile.fileName, [], props); + }) +} + +function isJsonString(cacheFile: string): [boolean, WholeCache | undefined] { + if (fs.existsSync(cacheFile)) { + try { + return [true, JSON.parse(fs.readFileSync(cacheFile).toString())]; + } catch (e) { + return [false, undefined]; + } + } else { + return [false, undefined]; + } +} + +// collect the compiled files of tsc and rollup for obfuscation scanning. +export function collectAllFiles(program?: ts.Program, rollupFileList?: IterableIterator, + rollupObject?: Object, rollupShareObject: Object = null): void { + if (program) { + collectTscFiles(program, rollupShareObject); + return; + } + mergeRollUpFiles(rollupFileList, rollupObject); +} + +export function collectTscFiles(program: ts.Program, rollupShareObject: Object = null): void { + const programAllFiles: readonly ts.SourceFile[] = program.getSourceFiles(); + let projectRootPath: string = projectConfig.projectRootPath; + if (!projectRootPath) { + return; + } + projectRootPath = toUnixPath(projectRootPath); + const isMacOrWin = isWindows() || isMac(); + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.COLLECT_TSC_FILES_ALL_RESOLVED_MODULES); + programAllFiles.forEach(sourceFile => { + const fileName = toUnixPath(sourceFile.fileName); + // @ts-ignore + sourceFileDependencies.set(fileName, sourceFile.resolvedModules); + if (!(fileName.startsWith(projectRootPath + '/') || isOtherProjectResolvedModulesFilePaths(rollupShareObject, fileName))) { + return; + } + allSourceFilePaths.add(fileName); + allModuleIds.add(sourceFile.fileName); + // For the windos and mac platform, the file path in filesBuildInfo is in lowercase, + // while fileName of sourceFile is the original path (including uppercase). + if (filesBuildInfo.size > 0 && + Reflect.get(sourceFile, 'version') !== filesBuildInfo.get(isMacOrWin ? tryToLowerCasePath(fileName) : fileName)) { + allResolvedModules.add(fileName); + } + }); + MemoryMonitor.stopRecordStage(recordInfo); +} + +function isOtherProjectResolvedModulesFilePaths(rollupShareObject: Object, fileName: string): boolean { + if (!!rollupShareObject && rollupShareObject.projectConfig && !!rollupShareObject.projectConfig.rootPathSet) { + const rootPathSet: string[] | Set = rollupShareObject.projectConfig.rootPathSet; + if (Array.isArray(rootPathSet)) { + for (let i = 0; i < rootPathSet.length; i++) { + const pathNormalization: string = toUnixPath(rootPathSet[i]) + '/'; + if (fileName.startsWith(pathNormalization)) { + return true; + } + } + } else { + return false; + } + } + return false; +} + +export function mergeRollUpFiles(rollupFileList: IterableIterator, rollupObject: Object) { + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.MERGE_ROLL_UP_FILES_LOCAL_PACKAGE_SET); + for (const moduleId of rollupFileList) { + if (fs.existsSync(moduleId)) { + allSourceFilePaths.add(toUnixPath(moduleId)); + allModuleIds.add(moduleId); + addLocalPackageSet(moduleId, rollupObject); + } + } + MemoryMonitor.stopRecordStage(recordInfo); +} + +// collect the modulename or pkgname of all local modules. +export function addLocalPackageSet(moduleId: string, rollupObject: Object): void { + const moduleInfo: Object = rollupObject.getModuleInfo(moduleId); + const metaInfo: Object = moduleInfo.meta; + if (metaInfo.isLocalDependency) { + if (projectConfig.useNormalizedOHMUrl && metaInfo.pkgName) { + localPackageSet.add(metaInfo.pkgName); + } + if (!projectConfig.useNormalizedOHMUrl && metaInfo.moduleName) { + localPackageSet.add(metaInfo.moduleName); + } + } +} + +export function emitBuildInfo(): void { + globalProgram.builderProgram.emitBuildInfo(buildInfoWriteFile); +} + +function processBuildHap(cacheFile: string, rootFileNames: string[], compilationTime: CompilationTimeStatistics, + rollupShareObject: Object): void { + startTimeStatisticsLocation(compilationTime ? compilationTime.diagnosticTime : undefined); + const semanticRecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP_GET_SEMANTIC_DIAGNOSTICS); + const allDiagnostics: ts.Diagnostic[] = globalProgram.builderProgram + .getSyntacticDiagnostics() + .concat(globalProgram.builderProgram.getSemanticDiagnostics()); + MemoryMonitor.stopRecordStage(semanticRecordInfo); + stopTimeStatisticsLocation(compilationTime ? compilationTime.diagnosticTime : undefined); + const emitBuildRecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP_EMIT_BUILD_INFO); + emitBuildInfo(); + let errorCodeLogger: Object | undefined = rollupShareObject?.getHvigorConsoleLogger ? + rollupShareObject?.getHvigorConsoleLogger(TSC_SYSTEM_CODE) : undefined; + + allDiagnostics.forEach((diagnostic: ts.Diagnostic) => { + printDiagnostic(diagnostic, ErrorCodeModule.TSC, errorCodeLogger); + }); + MemoryMonitor.stopRecordStage(emitBuildRecordInfo); + if (!projectConfig.xtsMode) { + fse.ensureDirSync(projectConfig.cachePath); + fs.writeFileSync(cacheFile, JSON.stringify({ + 'runtimeOS': projectConfig.runtimeOS, + 'sdkInfo': projectConfig.sdkInfo, + 'fileList': cache + }, null, 2)); + } + if (projectConfig.compileHar || projectConfig.compileShared) { + let emit: string | undefined = undefined; + let writeFile = (fileName: string, text: string, writeByteOrderMark: boolean): void => { + emit = text; + }; + [...allResolvedModules, ...rootFileNames].forEach(moduleFile => { + if (!(moduleFile.match(new RegExp(projectConfig.packageDir)) && projectConfig.compileHar)) { + try { + if ((/\.d\.e?ts$/).test(moduleFile)) { + generateSourceFilesInHar(moduleFile, fs.readFileSync(moduleFile, 'utf-8'), path.extname(moduleFile), + projectConfig, projectConfig.modulePathMap); + } else if ((/\.e?ts$/).test(moduleFile)) { + emit = undefined; + let sourcefile = globalProgram.program.getSourceFile(moduleFile); + if (sourcefile) { + globalProgram.program.emit(sourcefile, writeFile, undefined, true, undefined, true); + } + if (emit) { + generateSourceFilesInHar(moduleFile, emit, '.d' + path.extname(moduleFile), projectConfig, projectConfig.modulePathMap); + } + } + } catch (err) { } + } + }); + printDeclarationDiagnostics(errorCodeLogger); + } +} + +function printDeclarationDiagnostics(errorCodeLogger?: Object | undefined): void { + globalProgram.builderProgram.getDeclarationDiagnostics().forEach((diagnostic: ts.Diagnostic) => { + printDiagnostic(diagnostic, ErrorCodeModule.TSC, errorCodeLogger); + }); +} + +function containFormError(message: string): boolean { + if (/can't support form application./.test(message)) { + return true; + } + return false; +} + +let fileToIgnoreDiagnostics: Set | undefined = undefined; + +function collectFileToThrowDiagnostics(file: string, fileToThrowDiagnostics: Set): void { + const normalizedFilePath: string = path.resolve(file); + const unixFilePath: string = toUnixPath(file); + if (fileToThrowDiagnostics.has(unixFilePath)) { + return; + } + + fileToThrowDiagnostics.add(unixFilePath); + // Although the cache object filters JavaScript files when collecting dependency relationships, we still include the + // filtering of JavaScript files here to avoid potential omissions. + if ((/\.(c|m)?js$/).test(file) || + !cache[normalizedFilePath] || cache[normalizedFilePath].children.length === 0) { + return; + } + cache[normalizedFilePath].children.forEach(file => { + collectFileToThrowDiagnostics(file, fileToThrowDiagnostics); + }); +} + +export function collectFileToIgnoreDiagnostics(rootFileNames: string[]): void { + if (getArkTSLinterMode() === ArkTSLinterMode.NOT_USE) { + return; + } + + // In watch mode, the `beforeBuild` phase will clear the parent and children fields in the cache. For files that have + // not been modified, the information needs to be restored using the `resolvedModuleNames` variable. + if (process.env.watchMode === 'true') { + for (let [file, resolvedModules] of resolvedModulesCache) { + createOrUpdateCache(resolvedModules, file); + } + } + + // With arkts linter enabled, `allowJs` option is set to true, resulting JavaScript files themselves and + // JavaScript-referenced files are included in the tsc program and checking process, + // potentially introducing new errors. For instance, in scenarios where an ets file imports js file imports ts file, + // it’s necessary to filter out errors from ts files. + let fileToThrowDiagnostics: Set = new Set(); + rootFileNames.forEach(file => { + if (!(/\.(c|m)?js$/).test(file)) { + collectFileToThrowDiagnostics(file, fileToThrowDiagnostics); + } + }); + + let resolvedTypeReferenceDirectivesFiles: Set = new Set(); + globalProgram.program.getResolvedTypeReferenceDirectives().forEach( + (elem: ts.ResolvedTypeReferenceDirective | undefined) => { + elem && elem.resolvedFileName && resolvedTypeReferenceDirectivesFiles.add(elem.resolvedFileName); + }); + + const ignoreDiagnosticsRecordInfo = MemoryMonitor.recordStage(MemoryDefine.FILE_TO_IGNORE_DIAGNOSTICS); + fileToIgnoreDiagnostics = new Set(); + globalProgram.program.getSourceFiles().forEach(sourceFile => { + // Previous projects had js libraries that were available through SDK, so need to filter js-file in SDK, + // like: hypium library + sourceFile.fileName && + (!isInSDK(sourceFile.fileName) || (/\.(c|m)?js$/).test(sourceFile.fileName)) && + !resolvedTypeReferenceDirectivesFiles.has(sourceFile.fileName) && + fileToIgnoreDiagnostics.add(toUnixPath(sourceFile.fileName)); + }); + + fileToThrowDiagnostics.forEach(file => { + fileToIgnoreDiagnostics.delete(file); + }); + MemoryMonitor.stopRecordStage(ignoreDiagnosticsRecordInfo); +} + +interface MessageCollection { + positionMessage: string, + message: string, + logMessage: string +} + +export function printDiagnostic(diagnostic: ts.Diagnostic, flag?: ErrorCodeModule, errorCodeLogger?: Object | undefined): void { + if (projectConfig.ignoreWarning) { + return; + } + + if (fileToIgnoreDiagnostics && diagnostic.file && diagnostic.file.fileName && + fileToIgnoreDiagnostics.has(toUnixPath(diagnostic.file.fileName))) { + return; + } + + const message: string = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); + if (validateError(message)) { + if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) { + updateErrorFileCache(diagnostic); + } + + if (containFormError(message) && !isCardFile(diagnostic.file.fileName)) { + return; + } + + const logPrefix: string = diagnostic.category === ts.DiagnosticCategory.Error ? 'ERROR' : 'WARN'; + const etsCheckerLogger = fastBuildLogger || logger; + let logMessage: string; + if (logPrefix === 'ERROR') { + checkerResult.count += 1; + } else { + warnCheckerResult.count += 1; + } + let positionMessage: string = ''; + if (diagnostic.file) { + const { line, character }: ts.LineAndCharacter = + diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); + positionMessage = `File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}`; + logMessage = `ArkTS:${logPrefix} ${positionMessage}\n ${message}\n`; + } else { + logMessage = `ArkTS:${logPrefix}: ${message}`; + } + + if (errorCodeLogger) { + const msgCollection: MessageCollection = { positionMessage, message, logMessage }; + printErrorCode(diagnostic, etsCheckerLogger, msgCollection, errorCodeLogger, flag); + } else { + if (diagnostic.category === ts.DiagnosticCategory.Error) { + etsCheckerLogger.error('\u001b[31m' + logMessage); + } else { + etsCheckerLogger.warn('\u001b[33m' + logMessage); + } + } + } +} + +function printErrorCode(diagnostic: ts.Diagnostic, etsCheckerLogger: Object, + msgCollection: MessageCollection, errorCodeLogger: Object, flag: ErrorCodeModule | undefined): void { + const { positionMessage, message, logMessage } = msgCollection; + // If the diagnostic is not an error, log a warning and return early. + if (diagnostic.category !== ts.DiagnosticCategory.Error) { + etsCheckerLogger.warn('\u001b[33m' + logMessage); + return; + } + + // Check for TSC error codes + if (flag === ErrorCodeModule.TSC && + validateUseErrorCodeLogger(ErrorCodeModule.TSC, diagnostic.code)) { + const errorCode = ts.getErrorCode(diagnostic); + errorCodeLogger.printError(errorCode); + return; + } + + // Check for LINTER error codes + if (flag === ErrorCodeModule.LINTER || (flag === ErrorCodeModule.TSC && + validateUseErrorCodeLogger(ErrorCodeModule.LINTER, diagnostic.code))) { + const linterErrorInfo: HvigorErrorInfo = transfromErrorCode(diagnostic.code, positionMessage, message); + errorCodeLogger.printError(linterErrorInfo); + return; + } + + // Check for ArkUI error codes + if (flag === ErrorCodeModule.UI || (flag === ErrorCodeModule.TSC && + validateUseErrorCodeLogger(ErrorCodeModule.UI, diagnostic.code))) { + const uiErrorInfo: HvigorErrorInfo | undefined = buildErrorInfoFromDiagnostic( + diagnostic.code, positionMessage, message); + if (!uiErrorInfo) { + etsCheckerLogger.error('\u001b[31m' + logMessage); + } else { + errorCodeLogger.printError(uiErrorInfo); + } + return; + } + + // If the error is not a TSC/Linter/ArkUI error, log using etsCheckerLogger + etsCheckerLogger.error('\u001b[31m' + logMessage); +} + +function validateUseErrorCodeLogger(flag: ErrorCodeModule, code: number): boolean { + if (!ts.getErrorCodeArea || !ts.getErrorCode) { + return false; + } + if (flag === ErrorCodeModule.TSC) { + return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.TSC; + } else if (flag === ErrorCodeModule.LINTER) { + return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.LINTER; + } else if (flag === ErrorCodeModule.UI) { + return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.UI; + } + return false; +} + +function validateError(message: string): boolean { + const propInfoReg: RegExp = /Cannot find name\s*'(\$?\$?[_a-zA-Z0-9]+)'/; + const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/; + if (matchMessage(message, props, propInfoReg) || + matchMessage(message, props, stateInfoReg)) { + return false; + } + return true; +} +function matchMessage(message: string, nameArr: any, reg: RegExp): boolean { + if (reg.test(message)) { + const match: string[] = message.match(reg); + if (match[1] && nameArr.includes(match[1])) { + return true; + } + } + return false; +} + +function updateErrorFileCache(diagnostic: ts.Diagnostic): void { + if (!diagnostic.file) { + return; + } + + let cacheInfo: CacheFileName = cache[path.resolve(diagnostic.file.fileName)]; + if (cacheInfo) { + cacheInfo.error = true; + if (!cacheInfo.errorCodes) { + cacheInfo.errorCodes = []; + } + cacheInfo.errorCodes.includes(diagnostic.code) || cacheInfo.errorCodes.push(diagnostic.code); + } +} + +function filterInput(rootFileNames: string[]): string[] { + return rootFileNames.filter((file: string) => { + const needUpdate: NeedUpdateFlag = { flag: false }; + const alreadyCheckedFiles: Set = new Set(); + checkNeedUpdateFiles(path.resolve(file), needUpdate, alreadyCheckedFiles); + if (!needUpdate.flag) { + storedFileInfo.changeFiles.push(path.resolve(file)); + } + return needUpdate.flag; + }); +} + +function checkNeedUpdateFiles(file: string, needUpdate: NeedUpdateFlag, alreadyCheckedFiles: Set): void { + if (alreadyCheckedFiles.has(file)) { + return; + } else { + alreadyCheckedFiles.add(file); + } + + if (needUpdate.flag) { + return; + } + + const value: CacheFileName = cache[file]; + const mtimeMs: number = fs.statSync(file).mtimeMs; + if (value) { + if (value.error || value.mtimeMs !== mtimeMs) { + needUpdate.flag = true; + return; + } + for (let i = 0; i < value.children.length; ++i) { + if (fs.existsSync(value.children[i])) { + checkNeedUpdateFiles(value.children[i], needUpdate, alreadyCheckedFiles); + } else { + needUpdate.flag = true; + } + } + } else { + cache[file] = { mtimeMs, children: [], parent: [], error: false }; + needUpdate.flag = true; + } +} + +const fileExistsCache: Map = new Map(); +const dirExistsCache: Map = new Map(); +const moduleResolutionHost: ts.ModuleResolutionHost = { + fileExists: (fileName: string): boolean => { + let exists = fileExistsCache.get(fileName); + if (exists === undefined) { + exists = ts.sys.fileExists(fileName); + fileExistsCache.set(fileName, exists); + } + return exists; + }, + directoryExists: (directoryName: string): boolean => { + let exists = dirExistsCache.get(directoryName); + if (exists === undefined) { + exists = ts.sys.directoryExists(directoryName); + dirExistsCache.set(directoryName, exists); + } + return exists; + }, + readFile(fileName: string): string | undefined { + return ts.sys.readFile(fileName); + }, + realpath(path: string): string { + return ts.sys.realpath(path); + }, + trace(s: string): void { + console.info(s); + } +}; + +//This is only for test +export const moduleResolutionHostTest = moduleResolutionHost; + +export function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | ts.FileReference[]): ts.ResolvedTypeReferenceDirective[] { + if (typeDirectiveNames.length === 0) { + return []; + } + + const resolvedTypeReferenceCache: ts.ResolvedTypeReferenceDirective[] = []; + const cache: Map = new Map(); + const containingFile: string = path.join(projectConfig.modulePath, 'build-profile.json5'); + + for (const entry of typeDirectiveNames) { + const typeName = isString(entry) ? entry : entry.fileName.toLowerCase(); + if (!cache.has(typeName)) { + const resolvedFile = ts.resolveTypeReferenceDirective(typeName, containingFile, compilerOptions, moduleResolutionHost); + if (!resolvedFile || !resolvedFile.resolvedTypeReferenceDirective) { + logger.error('\u001b[31m', `ArkTS:Cannot find type definition file for: ${typeName}\n`); + } + const result: ts.ResolvedTypeReferenceDirective = resolvedFile.resolvedTypeReferenceDirective; + cache.set(typeName, result); + resolvedTypeReferenceCache.push(result); + } + } + return resolvedTypeReferenceCache; +} + +// resolvedModulesCache records the files and their dependencies of program. +export const resolvedModulesCache: Map = new Map(); + +export function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] { + const languageVersion = FileManager.mixCompile ? FileManager.getInstance().getLanguageVersionByFilePath(containingFile).languageVersion : ARKTS_1_1; + startTimeStatisticsLocation(resolveModuleNamesTime); + const resolvedModules: ts.ResolvedModuleFull[] = []; + const cacheFileContent: ts.ResolvedModuleFull[] = resolvedModulesCache.get(path.resolve(containingFile)); + if (![...shouldResolvedFiles].length || shouldResolvedFiles.has(path.resolve(containingFile)) || + !(cacheFileContent && cacheFileContent.length === moduleNames.length)) { + for (const moduleName of moduleNames) { + const result = ts.resolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost); + if (result.resolvedModule) { + if (result.resolvedModule.resolvedFileName && + path.extname(result.resolvedModule.resolvedFileName) === EXTNAME_JS) { + const resultDETSPath: string = + result.resolvedModule.resolvedFileName.replace(EXTNAME_JS, EXTNAME_D_ETS); + if (ts.sys.fileExists(resultDETSPath)) { + resolvedModules.push(getResolveModule(resultDETSPath, EXTNAME_D_ETS)); + } else { + resolvedModules.push(result.resolvedModule); + } + } else if (isMixCompile() && result.resolvedModule.resolvedFileName && /\.ets$/.test(result.resolvedModule.resolvedFileName) && + !/\.d\.ets$/.test(result.resolvedModule.resolvedFileName)) { + // When result has a value and the path parsed is the source code file path of module 1.2, + // the parsing result needs to be modified to the glue code path of module 1.2 + const queryResult = redirectToDeclFileForInterop(result.resolvedModule.resolvedFileName); + if (queryResult) { + resolvedModules.push(queryResult); + } else { + resolvedModules.push(result.resolvedModule); + } + } else { + resolvedModules.push(result.resolvedModule); + } + } else if (new RegExp(`^@(${sdkConfigPrefix})\\.`, 'i').test(moduleName.trim())) { + const apiPaths = sdkConfigs.flatMap(config => config.apiPath); + isMixCompile() && getApiPathForInterop(apiPaths, languageVersion); + const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(apiPaths, moduleName, ['.d.ts', '.d.ets']); + const modulePath = resolveModuleInfo.modulePath; + const extension = resolveModuleInfo.isEts ? '.d.ets' : '.d.ts'; + const fullModuleName = moduleName + extension; + + if (systemModules.includes(fullModuleName) && ts.sys.fileExists(modulePath)) { + resolvedModules.push(getResolveModule(modulePath, extension)); + } else if (languageVersion === ARKTS_1_2) { + resolvedModules.push(getResolveModule(modulePath, extension)); + } else { + resolvedModules.push(null); + } + } else if (/\.ets$/.test(moduleName) && !/\.d\.ets$/.test(moduleName)) { + const modulePath: string = path.resolve(path.dirname(containingFile), moduleName); + if (ts.sys.fileExists(modulePath)) { + resolvedModules.push(getResolveModule(modulePath, '.ets')); + } else { + resolvedModules.push(null); + } + } else if (/\.ts$/.test(moduleName)) { + const modulePath: string = path.resolve(path.dirname(containingFile), moduleName); + if (ts.sys.fileExists(modulePath)) { + resolvedModules.push(getResolveModule(modulePath, '.ts')); + } else { + resolvedModules.push(null); + } + } else { + const aliasConfig = FileManager.getInstance().queryOriginApiName(moduleName, containingFile); + if (aliasConfig) { + const searchPaths = aliasConfig.isStatic + ? Array.from(FileManager.staticSDKDeclPath) + : [...new Set(sdkConfigs.flatMap(config => config.apiPath))]; + const resolveModuleInfo = getRealModulePath(searchPaths, aliasConfig.originalAPIName, ['.d.ts', '.d.ets']); + const modulePath = resolveModuleInfo.modulePath; + const extension = resolveModuleInfo.isEts ? '.d.ets' : '.d.ts'; + resolvedModules.push(getResolveModule(modulePath, extension)); + } else { + const modulePath: string = path.resolve(__dirname, '../../../../../api', moduleName + '.d.ts'); //??? + const systemDETSModulePath: string = path.resolve(__dirname, '../../../../../api', moduleName + '.d.ets'); + const kitModulePath: string = path.resolve(__dirname, '../../../../../kits', moduleName + '.d.ts'); + const kitSystemDETSModulePath: string = path.resolve(__dirname, '../../../../../kits', moduleName + '.d.ets'); + const suffix: string = /\.js$/.test(moduleName) ? '' : '.js'; + const jsModulePath: string = path.resolve(__dirname, '../../../node_modules', moduleName + suffix); + const fileModulePath: string = + path.resolve(__dirname, '../../../node_modules', moduleName + '/index.js'); + const DETSModulePath: string = path.resolve(path.dirname(containingFile), + /\.d\.ets$/.test(moduleName) ? moduleName : moduleName + EXTNAME_D_ETS); + const arktsEvoDeclFilePath: string = isMixCompile() ? + getArkTSEvoDeclFilePath({ moduleRequest: moduleName, resolvedFileName: '' }) : ''; + if (ts.sys.fileExists(modulePath)) { + resolvedModules.push(getResolveModule(modulePath, '.d.ts')); + } else if (ts.sys.fileExists(systemDETSModulePath)) { + resolvedModules.push(getResolveModule(systemDETSModulePath, '.d.ets')); + } else if (ts.sys.fileExists(kitModulePath)) { + resolvedModules.push(getResolveModule(kitModulePath, '.d.ts')); + } else if (ts.sys.fileExists(kitSystemDETSModulePath)) { + resolvedModules.push(getResolveModule(kitSystemDETSModulePath, '.d.ets')); + } else if (ts.sys.fileExists(jsModulePath)) { + resolvedModules.push(getResolveModule(jsModulePath, '.js')); + } else if (ts.sys.fileExists(fileModulePath)) { + resolvedModules.push(getResolveModule(fileModulePath, '.js')); + } else if (ts.sys.fileExists(DETSModulePath)) { + resolvedModules.push(getResolveModule(DETSModulePath, '.d.ets')); + } else if (isMixCompile() && ts.sys.fileExists(arktsEvoDeclFilePath)) { + resolvedModules.push(getResolveModule(arktsEvoDeclFilePath, '.d.ets')); + } else { + const srcIndex: number = projectConfig.projectPath.indexOf('src' + path.sep + 'main'); + let DETSModulePathFromModule: string; + if (srcIndex > 0) { + DETSModulePathFromModule = path.resolve( + projectConfig.projectPath.substring(0, srcIndex), moduleName + path.sep + 'index' + EXTNAME_D_ETS); + if (DETSModulePathFromModule && ts.sys.fileExists(DETSModulePathFromModule)) { + resolvedModules.push(getResolveModule(DETSModulePathFromModule, '.d.ets')); + } else { + resolvedModules.push(null); + } + } else { + resolvedModules.push(null); + } + } + } + } + if (projectConfig.hotReload && resolvedModules.length && + resolvedModules[resolvedModules.length - 1]) { + hotReloadSupportFiles.add(path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName)); + } + if (collectShouldPackedFiles(resolvedModules)) { + allResolvedModules.add(resolvedModules[resolvedModules.length - 1].resolvedFileName); + } + } + if (!projectConfig.xtsMode) { + createOrUpdateCache(resolvedModules, path.resolve(containingFile)); + } + resolvedModulesCache.set(path.resolve(containingFile), resolvedModules); + stopTimeStatisticsLocation(resolveModuleNamesTime); + return resolvedModules; + + } + stopTimeStatisticsLocation(resolveModuleNamesTime); + return resolvedModulesCache.get(path.resolve(containingFile)); +} + +export interface ResolveModuleInfo { + modulePath: string; + isEts: boolean; +} + +function collectShouldPackedFiles(resolvedModules: ts.ResolvedModuleFull[]): boolean | RegExpMatchArray { + return (projectConfig.compileHar || projectConfig.compileShared) && resolvedModules[resolvedModules.length - 1] && + resolvedModules[resolvedModules.length - 1].resolvedFileName && + (path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/(\.[^d]|[^\.]d|[^\.][^d])\.e?ts$/) || + path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/\.d\.e?ts$/) && + path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match( + new RegExp('\\' + path.sep + 'src' + '\\' + path.sep + 'main' + '\\' + path.sep))); +} + +function createOrUpdateCache(resolvedModules: ts.ResolvedModuleFull[], containingFile: string): void { + const children: string[] = []; + const error: boolean = false; + resolvedModules.forEach(moduleObj => { + if (moduleObj && moduleObj.resolvedFileName && /\.(ets|ts)$/.test(moduleObj.resolvedFileName)) { + const file: string = path.resolve(moduleObj.resolvedFileName); + const mtimeMs: number = fs.statSync(file).mtimeMs; + children.push(file); + const value: CacheFileName = cache[file]; + if (value) { + value.mtimeMs = mtimeMs; + value.error = error; + value.parent = value.parent || []; + value.parent.push(path.resolve(containingFile)); + value.parent = [...new Set(value.parent)]; + } else { + cache[file] = { mtimeMs, children: [], parent: [containingFile], error }; + } + } + }); + cache[path.resolve(containingFile)] = { + mtimeMs: fs.statSync(containingFile).mtimeMs, children, + parent: cache[path.resolve(containingFile)] && cache[path.resolve(containingFile)].parent ? + cache[path.resolve(containingFile)].parent : [], error + }; +} + +export function createWatchCompilerHost(rootFileNames: string[], + reportDiagnostic: ts.DiagnosticReporter, delayPrintLogCount: Function, resetErrorCount: Function, + isPipe: boolean = false, resolveModulePaths: string[] = null): ts.WatchCompilerHostOfFilesAndCompilerOptions { + if (projectConfig.hotReload) { + rootFileNames.forEach(fileName => { + hotReloadSupportFiles.add(fileName); + }); + } + if (!(isPipe && process.env.compileTool === 'rollup')) { + setCompilerOptions(resolveModulePaths); + } + // Change the buildInfo file path, or it will cover the buildInfo file created before. + const buildInfoPath: string = path.resolve(projectConfig.cachePath, '..', WATCH_COMPILER_BUILD_INFO_SUFFIX);//??? + const watchCompilerOptions = {...compilerOptions, tsBuildInfoFile: buildInfoPath}; + const createProgram = ts.createSemanticDiagnosticsBuilderProgram; + const host = ts.createWatchCompilerHost( + [...rootFileNames, ...readDeaclareFiles()], watchCompilerOptions, + ts.sys, createProgram, reportDiagnostic, + (diagnostic: ts.Diagnostic) => { + if ([6031, 6032].includes(diagnostic.code)) { + if (!isPipe) { + process.env.watchTs = 'start'; + resetErrorCount(); + } + } + // End of compilation in watch mode flag. + if ([6193, 6194].includes(diagnostic.code)) { + if (!isPipe) { + process.env.watchTs = 'end'; + if (fastBuildLogger) { + fastBuildLogger.debug(TS_WATCH_END_MSG); + tsWatchEmitter.emit(TS_WATCH_END_MSG); + } + } + delayPrintLogCount(); + } + }); + host.readFile = (fileName: string) => { + if (!fs.existsSync(fileName)) { + return undefined; + } + if (/(? { }, () => { }, false, resolveModulePaths)); +} + +export function instanceInsteadThis(content: string, fileName: string, extendFunctionInfo: extendInfo[], + props: string[]): string { + extendFunctionInfo.reverse().forEach((item) => { + const subStr: string = content.substring(item.start, item.end); + const insert: string = subStr.replace(/(\s)\$(\.)/g, (origin, item1, item2) => { + return item1 + item.compName + 'Instance' + item2; + }); + content = content.slice(0, item.start) + insert + content.slice(item.end); + }); + return content; +} + +export function getResolveModule(modulePath: string, type): ts.ResolvedModuleFull { + return { + resolvedFileName: modulePath, + isExternalLibraryImport: false, + extension: type + }; +} + +export const dollarCollection: Set = new Set(); +export const extendCollection: Set = new Set(); +export const importModuleCollection: Set = new Set(); + +function checkUISyntax(sourceFile: ts.SourceFile, fileName: string, extendFunctionInfo: extendInfo[], props: string[]): void { + if (/\.ets$/.test(fileName) && !/\.d.ets$/.test(fileName)) { + if (process.env.compileMode === 'moduleJson' || + path.resolve(fileName) !== path.resolve(projectConfig.projectPath, 'app.ets')) { + collectComponents(sourceFile); + collectionCustomizeStyles(sourceFile); + parseAllNode(sourceFile, sourceFile, extendFunctionInfo); + props.push(...dollarCollection, ...extendCollection); + } + } +} + +function collectionCustomizeStyles(node: ts.Node): void { + if ((ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) && + (isUIDecorator(node, COMPONENT_STYLES_DECORATOR) || isUIDecorator(node, COMPONENT_EXTEND_DECORATOR)) && + node.name && ts.isIdentifier(node.name)) { + BUILDIN_STYLE_NAMES.add(node.name.escapedText.toString()); + } + if (ts.isSourceFile(node)) { + node.statements.forEach((item: ts.Node) => { + return collectionCustomizeStyles(item); + }); + } else if (ts.isStructDeclaration(node)) { + node.members.forEach((item: ts.Node) => { + return collectionCustomizeStyles(item); + }); + } +} + +function isUIDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | + ts.StructDeclaration | ts.ClassDeclaration, decortorName: string): boolean { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (decorators && decorators.length) { + for (let i = 0; i < decorators.length; i++) { + const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); + if (originalDecortor === decortorName) { + return true; + } else { + return false; + } + } + } + return false; +} +function collectComponents(node: ts.SourceFile): void { + // @ts-ignore + if (process.env.watchMode !== 'true' && node.identifiers && node.identifiers.size) { + // @ts-ignore + for (const key of node.identifiers.keys()) { + if (JS_BIND_COMPONENTS.has(key)) { + appComponentCollection.get(path.join(node.fileName)).add(key); + } + } + } +} + +function parseAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, extendFunctionInfo: extendInfo[]): void { + if (ts.isStructDeclaration(node)) { + if (node.members) { + node.members.forEach(item => { + if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) { + const propertyName: string = item.name.getText(); + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item); + if (decorators && decorators.length) { + for (let i = 0; i < decorators.length; i++) { + const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); + if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { + dollarCollection.add('$' + propertyName); + } + } + } + } + }); + } + } + if (process.env.watchMode !== 'true' && ts.isIfStatement(node)) { + appComponentCollection.get(path.join(sourceFileNode.fileName)).add(COMPONENT_IF); + } + if (ts.isMethodDeclaration(node) && node.name.getText() === COMPONENT_BUILD_FUNCTION || + (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) && + hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { + if (node.body && node.body.statements && node.body.statements.length) { + const checkProp: ts.NodeArray = node.body.statements; + checkProp.forEach((item, index) => { + traverseBuild(item, index); + }); + } + } + if (ts.isFunctionDeclaration(node) && hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) { + if (node.body && node.body.statements && node.body.statements.length && + !isOriginalExtend(node.body)) { + extendFunctionInfo.push({ + start: node.pos, + end: node.end, + compName: isExtendFunction(node, { decoratorName: '', componentName: '' }) + }); + } + } + ts.forEachChild(node, (child: ts.Node) => parseAllNode(child, sourceFileNode, extendFunctionInfo)); +} + +function isForeachAndLzayForEach(node: ts.Node): boolean { + return ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && + FOREACH_LAZYFOREACH.has(node.expression.escapedText.toString()) && node.arguments && node.arguments[1] && + ts.isArrowFunction(node.arguments[1]) && node.arguments[1].body && ts.isBlock(node.arguments[1].body); +} + +function getComponentName(node: ts.Node): string { + let temp = node.expression; + let name: string; + while (temp) { + if (ts.isIdentifier(temp) && temp.parent && (ts.isCallExpression(temp.parent) || + ts.isEtsComponentExpression(temp.parent))) { + name = temp.escapedText.toString(); + break; + } + temp = temp.expression; + } + return name; +} + +function traverseBuild(node: ts.Node, index: number): void { + if (ts.isExpressionStatement(node)) { + const parentComponentName: string = getComponentName(node); + node = node.expression; + while (node) { + if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && + ts.isIdentifier(node.expression) && !DOLLAR_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) { + node.body.statements.forEach((item: ts.Statement, indexBlock: number) => { + traverseBuild(item, indexBlock); + }); + break; + } else if (isForeachAndLzayForEach(node)) { + node.arguments[1].body.statements.forEach((item: ts.Statement, indexBlock: number) => { + traverseBuild(item, indexBlock); + }); + break; + } else { + loopNodeFindDoubleDollar(node, parentComponentName); + if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && + ts.isIdentifier(node.expression)) { + node.body.statements.forEach((item: ts.Statement, indexBlock: number) => { + traverseBuild(item, indexBlock); + }); + break; + } + } + node = node.expression; + } + } else if (ts.isIfStatement(node)) { + ifInnerDollarAttribute(node); + } +} + +function ifInnerDollarAttribute(node: ts.IfStatement): void { + if (node.thenStatement && ts.isBlock(node.thenStatement) && node.thenStatement.statements) { + node.thenStatement.statements.forEach((item, indexIfBlock) => { + traverseBuild(item, indexIfBlock); + }); + } + if (node.elseStatement) { + elseInnerDollarAttribute(node); + } +} + +function elseInnerDollarAttribute(node: ts.IfStatement): void { + if (ts.isIfStatement(node.elseStatement) && node.elseStatement.thenStatement && ts.isBlock(node.elseStatement.thenStatement)) { + traverseBuild(node.elseStatement, 0); + } else if (ts.isBlock(node.elseStatement) && node.elseStatement.statements) { + node.elseStatement.statements.forEach((item, indexElseBlock) => { + traverseBuild(item, indexElseBlock); + }); + } +} + +function isPropertiesAddDoubleDollar(node: ts.Node): boolean { + if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments && node.arguments.length) { + return true; + } else if (ts.isEtsComponentExpression(node) && ts.isIdentifier(node.expression) && + DOLLAR_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) { + return true; + } else { + return false; + } +} +function loopNodeFindDoubleDollar(node: ts.Node, parentComponentName: string): void { + if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { + const argument: ts.NodeArray = node.arguments; + const propertyName: ts.Identifier | ts.PrivateIdentifier = node.expression.name; + if (isCanAddDoubleDollar(propertyName.getText(), parentComponentName)) { + argument.forEach((item: ts.Node) => { + doubleDollarCollection(item); + }); + } + } else if (isPropertiesAddDoubleDollar(node)) { + node.arguments.forEach((item: ts.Node) => { + if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) { + item.properties.forEach((param: ts.Node) => { + if (isObjectPram(param, parentComponentName)) { + doubleDollarCollection(param.initializer); + } + }); + } else if (ts.isPropertyAccessExpression(item) && (handleComponentDollarBlock(node as ts.CallExpression, parentComponentName) || + STYLE_ADD_DOUBLE_DOLLAR.has(node.expression.getText()))) { + doubleDollarCollection(item); + } + }); + } +} + +function handleComponentDollarBlock(node: ts.CallExpression, parentComponentName: string): boolean { + return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && + DOLLAR_BLOCK_INTERFACE.has(parentComponentName) && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && + PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(node.expression.escapedText.toString()); +} + +function doubleDollarCollection(item: ts.Node): void { + if (item.getText().startsWith($$)) { + while (item.expression) { + item = item.expression; + } + dollarCollection.add(item.getText()); + } +} + +function isObjectPram(param: ts.Node, parentComponentName: string): boolean { + return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) && + param.initializer && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && + PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(param.name.getText()); +} + +function isCanAddDoubleDollar(propertyName: string, parentComponentName: string): boolean { + return PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && + PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(propertyName) || + STYLE_ADD_DOUBLE_DOLLAR.has(propertyName); +} + +function processContent(source: string, id: string): string { + if (fastBuildLogger) { + source = visualTransform(source, id, fastBuildLogger); + } + source = preprocessExtend(source, extendCollection); + source = preprocessNewExtend(source, extendCollection); + return source; +} + +function judgeFileShouldResolved(file: string, shouldResolvedFiles: Set): void { + if (shouldResolvedFiles.has(file)) { + return; + } + shouldResolvedFiles.add(file); + if (cache && cache[file] && cache[file].parent) { + cache[file].parent.forEach((item) => { + judgeFileShouldResolved(item, shouldResolvedFiles); + }); + cache[file].parent = []; + } + if (cache && cache[file] && cache[file].children) { + cache[file].children.forEach((item) => { + judgeFileShouldResolved(item, shouldResolvedFiles); + }); + cache[file].children = []; + } +} + +export function incrementWatchFile(watchModifiedFiles: string[], + watchRemovedFiles: string[]): void { + const changedFiles: string[] = [...watchModifiedFiles, ...watchRemovedFiles]; + if (changedFiles.length) { + shouldResolvedFiles.clear(); + } + changedFiles.forEach((file) => { + judgeFileShouldResolved(file, shouldResolvedFiles); + }); +} + +export function runArkTSLinter(errorCodeLogger?: Object | undefined): void { + const originProgram: ts.BuilderProgram = globalProgram.builderProgram; + + const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance(); + + const arkTSLinterDiagnostics = doArkTSLinter(getArkTSVersion(), + getArkTSLinterMode(), + originProgram, + printArkTSLinterDiagnostic, + !projectConfig.xtsMode, + buildInfoWriteFile, + errorCodeLogger); + + if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) { + arkTSLinterDiagnostics.forEach((diagnostic: ts.Diagnostic) => { + updateErrorFileCache(diagnostic); + }); + timePrinterInstance.appendTime(ts.TimePhase.UPDATE_ERROR_FILE); + } + timePrinterInstance.printTimes(); + ts.ArkTSLinterTimePrinter.destroyInstance(); +} + +function printArkTSLinterDiagnostic(diagnostic: ts.Diagnostic, errorCodeLogger?: Object | undefined): void { + if (diagnostic.category === ts.DiagnosticCategory.Error && (isInOhModuleFile(diagnostic) || isEtsDeclFileInSdk(diagnostic))) { + const originalCategory = diagnostic.category; + diagnostic.category = ts.DiagnosticCategory.Warning; + printDiagnostic(diagnostic); + diagnostic.category = originalCategory; + return; + } + printDiagnostic(diagnostic, ErrorCodeModule.LINTER, errorCodeLogger); +} + +function isEtsDeclFileInSdk(diagnostics: ts.Diagnostic): boolean { + if (diagnostics.file?.fileName === undefined) { + return false; + } + return isInSDK(diagnostics.file.fileName) && diagnostics.file.fileName.endsWith('.ets'); +} + +function isInOhModuleFile(diagnostics: ts.Diagnostic): boolean { + return (diagnostics.file !== undefined) && + ((diagnostics.file.fileName.indexOf('/oh_modules/') !== -1) || diagnostics.file.fileName.indexOf('\\oh_modules\\') !== -1); +} + +function isInSDK(fileName: string | undefined): boolean { + if (projectConfig.etsLoaderPath === undefined || fileName === undefined) { + return false; + } + const sdkPath = path.resolve(projectConfig.etsLoaderPath, '../../../'); //??? + return path.resolve(fileName).startsWith(sdkPath); +} + +export function getArkTSLinterMode(): ArkTSLinterMode { + if (!partialUpdateConfig.executeArkTSLinter) { + return ArkTSLinterMode.NOT_USE; + } + + if (!partialUpdateConfig.standardArkTSLinter) { + return ArkTSLinterMode.COMPATIBLE_MODE; + } + + if (isStandardMode()) { + return ArkTSLinterMode.STANDARD_MODE; + } + return ArkTSLinterMode.COMPATIBLE_MODE; +} + +export function isStandardMode(): boolean { + const STANDARD_MODE_COMPATIBLE_SDK_VERSION = 10; + if (projectConfig && + projectConfig.compatibleSdkVersion && + projectConfig.compatibleSdkVersion >= STANDARD_MODE_COMPATIBLE_SDK_VERSION) { + return true; + } + return false; +} + +function getArkTSVersion(): ArkTSVersion { + if (projectConfig.arkTSVersion === '1.0') { + return ArkTSVersion.ArkTS_1_0; + } else if (projectConfig.arkTSVersion === '1.1') { + return ArkTSVersion.ArkTS_1_1; + } else if (projectConfig.arkTSVersion !== undefined) { + const arkTSVersionLogger = fastBuildLogger || logger; + arkTSVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid ArkTS version\n'); + } + + if (partialUpdateConfig.arkTSVersion === '1.0') { + return ArkTSVersion.ArkTS_1_0; + } else if (partialUpdateConfig.arkTSVersion === '1.1') { + return ArkTSVersion.ArkTS_1_1; + } else if (partialUpdateConfig.arkTSVersion !== undefined) { + const arkTSVersionLogger = fastBuildLogger || logger; + arkTSVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid ArkTS version in metadata\n'); + } + + return ArkTSVersion.ArkTS_1_1; +} + +enum TargetESVersion { + ES2017 = 'ES2017', + ES2021 = 'ES2021', +} + +function getTargetESVersion(): TargetESVersion { + const targetESVersion = projectConfig?.projectArkOption?.tscConfig?.targetESVersion; + if (targetESVersion === 'ES2017') { + return TargetESVersion.ES2017; + } else if (targetESVersion === 'ES2021') { + return TargetESVersion.ES2021; + } else if (targetESVersion !== undefined) { + const targetESVersionLogger = fastBuildLogger || logger; + targetESVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid Target ES version\n'); + } + return TargetESVersion.ES2021; +} + +export function getMaxFlowDepth(): number { + // The value of maxFlowDepth ranges from 2000 to 65535. + let maxFlowDepth: number | undefined = projectConfig?.projectArkOption?.tscConfig?.maxFlowDepth; + + if (maxFlowDepth === undefined) { + maxFlowDepth = MAX_FLOW_DEPTH_DEFAULT_VALUE; + } else if (maxFlowDepth < MAX_FLOW_DEPTH_DEFAULT_VALUE || maxFlowDepth > MAX_FLOW_DEPTH_MAXIMUM_VALUE) { + const maxFlowDepthLogger = fastBuildLogger || logger; + maxFlowDepth = MAX_FLOW_DEPTH_DEFAULT_VALUE; + maxFlowDepthLogger.warn('\u001b[33m' + 'ArkTS: Invalid maxFlowDepth for control flow analysis.' + + `The value of maxFlowDepth ranges from ${MAX_FLOW_DEPTH_DEFAULT_VALUE} to ${MAX_FLOW_DEPTH_MAXIMUM_VALUE}.\n` + + 'If the modification does not take effect, set maxFlowDepth to the default value.'); + } + return maxFlowDepth; +} + +interface TargetESVersionLib { + ES2017: string[], + ES2021: string[], +} + +const targetESVersionLib: TargetESVersionLib = { + // When target is es2017, the lib is es2020. + ES2017: ['ES2020'], + ES2021: ['ES2021'], +}; + +function getTargetESVersionLib(): string[] { + const targetESVersion = projectConfig?.projectArkOption?.tscConfig?.targetESVersion; + if (targetESVersion === 'ES2017') { + return targetESVersionLib.ES2017; + } else if (targetESVersion === 'ES2021') { + return targetESVersionLib.ES2021; + } else if (targetESVersion !== undefined) { + const targetESVersionLogger = fastBuildLogger || logger; + targetESVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid Target ES version\n'); + } + return targetESVersionLib.ES2021; +} + +function initEtsStandaloneCheckerConfig(logger, config): void { + fastBuildLogger = logger; + if (config.packageManagerType === 'ohpm') { + config.packageDir = 'oh_modules'; + config.packageJson = 'oh-package.json5'; + } else { + config.packageDir = 'node_modules'; + config.packageJson = 'package.json'; + } + if (config.aceModuleJsonPath && fs.existsSync(config.aceModuleJsonPath)) { + process.env.compileMode = 'moduleJson'; + } + Object.assign(projectConfig, config); +} + +function resetEtsStandaloneCheckerConfig(beforeInitFastBuildLogger, beforeInitCompileMode: string): void { + resetProjectConfig(); + resetEtsCheck(); + fastBuildLogger = beforeInitFastBuildLogger; + process.env.compileMode = beforeInitCompileMode; +} + +export function etsStandaloneChecker(entryObj, logger, projectConfig): void { + const beforeInitFastBuildLogger = fastBuildLogger; + const beforeInitCompileMode = process.env.compileMode; + initEtsStandaloneCheckerConfig(logger, projectConfig); + const rootFileNames: string[] = []; + const resolveModulePaths: string[] = []; + Object.values(entryObj).forEach((fileName: string) => { + rootFileNames.push(path.resolve(fileName)); + }); + if (projectConfig.resolveModulePaths && Array.isArray(projectConfig.resolveModulePaths)) { + resolveModulePaths.push(...projectConfig.resolveModulePaths); + } + const filterFiles: string[] = filterInput(rootFileNames); + languageService = createLanguageService(filterFiles, resolveModulePaths); + const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance(); + timePrinterInstance.setArkTSTimePrintSwitch(false); + timePrinterInstance.appendTime(ts.TimePhase.START); + globalProgram.builderProgram = languageService.getBuilderProgram(/*withLinterProgram*/ true); + globalProgram.program = globalProgram.builderProgram.getProgram(); + props = languageService.getProps(); + timePrinterInstance.appendTime(ts.TimePhase.GET_PROGRAM); + collectFileToIgnoreDiagnostics(filterFiles); + runArkTSLinter(); + const allDiagnostics: ts.Diagnostic[] = globalProgram.builderProgram + .getSyntacticDiagnostics() + .concat(globalProgram.builderProgram.getSemanticDiagnostics()); + globalProgram.builderProgram.emitBuildInfo(buildInfoWriteFile); + + allDiagnostics.forEach((diagnostic: ts.Diagnostic) => { + printDiagnostic(diagnostic); + }); + resetEtsStandaloneCheckerConfig(beforeInitFastBuildLogger, beforeInitCompileMode); +} + +export function resetEtsCheckTypeScript(): void { + if (globalProgram.program) { + globalProgram.program.releaseTypeChecker(); + } else if (languageService) { + languageService.getProgram().releaseTypeChecker(); + } + resetGlobalProgram(); + languageService = null; +} + +export function resetEtsCheck(): void { + cache = {}; + props = []; + needReCheckForChangedDepUsers = false; + resetEtsCheckTypeScript(); + allResolvedModules.clear(); + checkerResult.count = 0; + warnCheckerResult.count = 0; + resolvedModulesCache.clear(); + dollarCollection.clear(); + extendCollection.clear(); + allSourceFilePaths.clear(); + allModuleIds.clear(); + filesBuildInfo.clear(); + fileExistsCache.clear(); + dirExistsCache.clear(); + targetESVersionChanged = false; + fileToIgnoreDiagnostics = undefined; +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/babel-plugin.ts b/compiler/src/interop/src/fast_build/ark_compiler/babel-plugin.ts new file mode 100644 index 0000000000000000000000000000000000000000..f82306d20ae6191368febed494222cbb2ecc7c36 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/babel-plugin.ts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 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 { getBabelInputPlugin } from '@rollup/plugin-babel'; +import { + NODE_MODULES, + OH_MODULES, + OHPM +} from './common/ark_define'; + +export const babelPlugin = (projectConfig: Object): Object => + getBabelInputPlugin({ + extensions: ['.js'], + exclude: [projectConfig.packageManagerType === OHPM ? OH_MODULES : NODE_MODULES], + skipPreflightCheck: true, + babelHelpers: 'runtime', + plugins: [ + require('@babel/plugin-proposal-class-properties') + ], + compact: false + }); \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/bundle/bundle_build_mode.ts b/compiler/src/interop/src/fast_build/ark_compiler/bundle/bundle_build_mode.ts new file mode 100644 index 0000000000000000000000000000000000000000..c242cbc9be2f955f5bde04cc5bcd55b1edf1a08d --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/bundle/bundle_build_mode.ts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 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 { BundleMode } from './bundle_mode'; + +export class BundleBuildMode extends BundleMode { + constructor(rollupObject: Object, rollupBundleFileSet: Object) { + super(rollupObject, rollupBundleFileSet); + } + + generateAbc() { + if (this.filterIntermediateJsBundle.length === 0) { + super.afterCompilationProcess(); + return; + } + super.executeArkCompiler(); + } +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/bundle/bundle_mode.ts b/compiler/src/interop/src/fast_build/ark_compiler/bundle/bundle_mode.ts new file mode 100644 index 0000000000000000000000000000000000000000..9d3bc1e4dca7ce5201bbf4cdf4457ce87cea5d03 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/bundle/bundle_mode.ts @@ -0,0 +1,470 @@ +/* + * Copyright (c) 2023 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 path from 'path'; +import fs from 'fs'; +import cluster from 'cluster'; +import childProcess from 'child_process'; + +import { CommonMode } from '../common/common_mode'; +import { + changeFileExtension, + genCachePath, + getEs2abcFileThreadNumber, + genTemporaryModuleCacheDirectoryForBundle, + isMasterOrPrimary, + isSpecifiedExt, + isDebug +} from '../utils'; +import { + ES2ABC, + EXTNAME_ABC, + EXTNAME_JS, + FILESINFO_TXT, + JSBUNDLE, + MAX_WORKER_NUMBER, + TEMP_JS, + TS2ABC, + red, + blue, + FAIL, + SUCCESS, + reset +} from '../common/ark_define'; +import { + mkDir, + toHashData, + toUnixPath, + unlinkSync, + validateFilePathLength +} from '../../../utils'; +import { + isEs2Abc, + isTs2Abc +} from '../../../ark_utils'; +import { + ArkTSErrorDescription, + ArkTSInternalErrorDescription, + ErrorCode +} from '../error_code'; +import { + LogData, + LogDataFactory +} from '../logger'; + +interface File { + filePath: string; + cacheFilePath: string; + sourceFile: string; + size: number; +} + +export class BundleMode extends CommonMode { + intermediateJsBundle: Map; + filterIntermediateJsBundle: Array; + hashJsonObject: Object; + filesInfoPath: string; + + constructor(rollupObject: Object, rollupBundleFileSet: Object) { + super(rollupObject); + this.intermediateJsBundle = new Map(); + this.filterIntermediateJsBundle = []; + this.hashJsonObject = {}; + this.filesInfoPath = ''; + this.prepareForCompilation(rollupObject, rollupBundleFileSet); + } + + prepareForCompilation(rollupObject: Object, rollupBundleFileSet: Object): void { + this.collectBundleFileList(rollupBundleFileSet); + this.removeCacheInfo(rollupObject); + this.filterBundleFileListWithHashJson(); + } + + collectBundleFileList(rollupBundleFileSet: Object): void { + Object.keys(rollupBundleFileSet).forEach((fileName) => { + // choose *.js + if (this.projectConfig.aceModuleBuild && isSpecifiedExt(fileName, EXTNAME_JS)) { + const tempFilePath: string = changeFileExtension(fileName, TEMP_JS); + const outputPath: string = path.resolve(this.projectConfig.aceModuleBuild, tempFilePath); + const cacheOutputPath: string = this.genCacheBundleFilePath(outputPath, tempFilePath); + let rollupBundleSourceCode: string = ''; + if (rollupBundleFileSet[fileName].type === 'asset') { + rollupBundleSourceCode = rollupBundleFileSet[fileName].source; + } else if (rollupBundleFileSet[fileName].type === 'chunk') { + rollupBundleSourceCode = rollupBundleFileSet[fileName].code; + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_RETRIEVE_SOURCE_CODE_FROM_SUMMARY, + ArkTSInternalErrorDescription, + `Failed to retrieve source code for ${fileName} from rollup file set.` + ); + this.logger.printErrorAndExit(errInfo); + } + fs.writeFileSync(cacheOutputPath, rollupBundleSourceCode, 'utf-8'); + if (!fs.existsSync(cacheOutputPath)) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GENERATE_CACHE_SOURCE_FILE, + ArkTSInternalErrorDescription, + `Failed to generate cached source file: ${fileName}.` + ); + this.logger.printErrorAndExit(errInfo); + } + this.collectIntermediateJsBundle(outputPath, cacheOutputPath); + } + }); + } + + filterBundleFileListWithHashJson() { + if (this.intermediateJsBundle.size === 0) { + return; + } + if (!fs.existsSync(this.hashJsonFilePath) || this.hashJsonFilePath.length === 0) { + this.intermediateJsBundle.forEach((value) => { + this.filterIntermediateJsBundle.push(value); + }); + return; + } + let updatedJsonObject: Object = {}; + let jsonObject: Object = {}; + let jsonFile: string = ''; + jsonFile = fs.readFileSync(this.hashJsonFilePath).toString(); + jsonObject = JSON.parse(jsonFile); + this.filterIntermediateJsBundle = []; + for (const value of this.intermediateJsBundle.values()) { + const cacheFilePath: string = value.cacheFilePath; + const cacheAbcFilePath: string = changeFileExtension(cacheFilePath, EXTNAME_ABC); + if (!fs.existsSync(cacheFilePath)) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_RETRIEVE_PACKAGE_CACHE_IN_INCREMENTAL_BUILD, + ArkTSInternalErrorDescription, + `Failed to get bundle cached abc from ${cacheFilePath} in incremental build.` + ); + this.logger.printErrorAndExit(errInfo); + } + if (fs.existsSync(cacheAbcFilePath)) { + const hashCacheFileContentData: string = toHashData(cacheFilePath); + const hashAbcContentData: string = toHashData(cacheAbcFilePath); + if (jsonObject[cacheFilePath] === hashCacheFileContentData && + jsonObject[cacheAbcFilePath] === hashAbcContentData) { + updatedJsonObject[cacheFilePath] = hashCacheFileContentData; + updatedJsonObject[cacheAbcFilePath] = hashAbcContentData; + continue; + } + } + this.filterIntermediateJsBundle.push(value); + } + + this.hashJsonObject = updatedJsonObject; + } + + executeArkCompiler() { + if (isEs2Abc(this.projectConfig)) { + this.filesInfoPath = this.generateFileInfoOfBundle(); + this.generateEs2AbcCmd(this.filesInfoPath); + this.executeEs2AbcCmd(); + } else if (isTs2Abc(this.projectConfig)) { + const splittedBundles: any[] = this.getSplittedBundles(); + this.invokeTs2AbcWorkersToGenAbc(splittedBundles); + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_INVALID_COMPILE_MODE, + ArkTSInternalErrorDescription, + 'Invalid compilation mode. ' + + `ProjectConfig.pandaMode should be either ${TS2ABC} or ${ES2ABC}.` + ); + this.logger.printErrorAndExit(errInfo); + } + } + + afterCompilationProcess() { + this.writeHashJson(); + this.copyFileFromCachePathToOutputPath(); + this.cleanTempCacheFiles(); + } + + private generateEs2AbcCmd(filesInfoPath: string) { + const fileThreads: number = getEs2abcFileThreadNumber(); + this.cmdArgs.push( + `"@${filesInfoPath}"`, + '--file-threads', + `"${fileThreads}"`, + `"--target-api-version=${this.projectConfig.compatibleSdkVersion}"`, + '--opt-try-catch-func=false' + ); + if (this.projectConfig.compatibleSdkReleaseType) { + this.cmdArgs.push(`"--target-api-sub-version=${this.projectConfig.compatibleSdkReleaseType}"`); + } + } + + private generateFileInfoOfBundle(): string { + const filesInfoPath: string = genCachePath(FILESINFO_TXT, this.projectConfig, this.logger); + let filesInfo: string = ''; + this.filterIntermediateJsBundle.forEach((info) => { + const cacheFilePath: string = info.cacheFilePath; + const recordName: string = 'null_recordName'; + const moduleType: string = 'script'; + // In release mode, there are '.temp.js' and '.js' file in cache path, no js file in output path. + // In debug mode, '.temp.js' file is in cache path, and '.js' file is in output path. + // '.temp.js' file is the input of es2abc, and should be uesd as sourceFile here. However,in debug mode , + // using '.temp.js' file as sourceFile needs IDE to adapt, so use '.js' file in output path instead temporarily. + const sourceFile: string = (isDebug(this.projectConfig) ? info.sourceFile.replace(/(.*)_/, '$1') : + cacheFilePath).replace(toUnixPath(this.projectConfig.projectRootPath) + '/', ''); + const abcFilePath: string = changeFileExtension(cacheFilePath, EXTNAME_ABC); + filesInfo += `${cacheFilePath};${recordName};${moduleType};${sourceFile};${abcFilePath}\n`; + }); + fs.writeFileSync(filesInfoPath, filesInfo, 'utf-8'); + + return filesInfoPath; + } + + private executeEs2AbcCmd() { + // collect data error from subprocess + let logDataList: Object[] = []; + let errMsg: string = ''; + const genAbcCmd: string = this.cmdArgs.join(' '); + try { + const child = this.triggerAsync(() => { + return childProcess.exec(genAbcCmd, { windowsHide: true }); + }); + child.on('close', (code: number) => { + if (code === SUCCESS) { + this.afterCompilationProcess(); + this.triggerEndSignal(); + return; + } + for (const logData of logDataList) { + this.logger.printError(logData); + } + if (errMsg !== '') { + this.logger.error(`Error Message: ${errMsg}`); + } + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_ES2ABC_EXECUTION_FAILED, + ArkTSErrorDescription, + 'Failed to execute es2abc.', + '', + ["Please refer to es2abc's error codes."] + ); + this.logger.printErrorAndExit(errInfo); + }); + + child.on('error', (err: any) => { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_ES2ABC_SUBPROCESS_START_FAILED, + ArkTSInternalErrorDescription, + `Failed to initialize or launch the es2abc process. ${err.toString()}` + ); + this.logger.printErrorAndExit(errInfo); + }); + + child.stderr.on('data', (data: any) => { + const logData = LogDataFactory.newInstanceFromEs2AbcError(data.toString()); + if (logData) { + logDataList.push(logData); + } else { + errMsg += data.toString(); + } + }); + } catch (e) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_EXECUTE_ES2ABC_WITH_ASYNC_HANDLER_FAILED, + ArkTSInternalErrorDescription, + `Failed to execute es2abc with async handler. ${e.toString()}` + ); + this.logger.printErrorAndExit(errInfo); + } + } + + private genCacheBundleFilePath(outputPath: string, tempFilePath: string): string { + let cacheOutputPath: string = ''; + if (this.projectConfig.cachePath) { + cacheOutputPath = path.join(genTemporaryModuleCacheDirectoryForBundle(this.projectConfig), tempFilePath); + } else { + cacheOutputPath = outputPath; + } + validateFilePathLength(cacheOutputPath, this.logger); + const parentDir: string = path.join(cacheOutputPath, '..'); //??? + if (!(fs.existsSync(parentDir) && fs.statSync(parentDir).isDirectory())) { + mkDir(parentDir); + } + + return cacheOutputPath; + } + + private collectIntermediateJsBundle(filePath: string, cacheFilePath: string) { + const fileSize: number = fs.statSync(cacheFilePath).size; + let sourceFile: string = changeFileExtension(filePath, '_.js', TEMP_JS); + if (!this.arkConfig.isDebug && this.projectConfig.projectRootPath) { + sourceFile = sourceFile.replace(this.projectConfig.projectRootPath + path.sep, ''); + } + + filePath = toUnixPath(filePath); + cacheFilePath = toUnixPath(cacheFilePath); + sourceFile = toUnixPath(sourceFile); + const bundleFile: File = { + filePath: filePath, + cacheFilePath: cacheFilePath, + sourceFile: sourceFile, + size: fileSize + }; + this.intermediateJsBundle.set(filePath, bundleFile); + } + + private getSplittedBundles(): any[] { + const splittedBundles: any[] = this.splitJsBundlesBySize(this.filterIntermediateJsBundle, MAX_WORKER_NUMBER); + return splittedBundles; + } + + private invokeTs2AbcWorkersToGenAbc(splittedBundles) { + if (isMasterOrPrimary()) { + this.setupCluster(cluster); + const workerNumber: number = splittedBundles.length < MAX_WORKER_NUMBER ? splittedBundles.length : MAX_WORKER_NUMBER; + for (let i = 0; i < workerNumber; ++i) { + const workerData: Object = { + inputs: JSON.stringify(splittedBundles[i]), + cmd: this.cmdArgs.join(' '), + mode: JSBUNDLE + }; + this.triggerAsync(() => { + const worker: Object = cluster.fork(workerData); + worker.on('message', (errorMsg) => { + this.logger.error(red, errorMsg.data.toString(), reset); + this.logger.throwArkTsCompilerError('ArkTS:ERROR Failed to execute ts2abc'); + }); + }); + } + + let workerCount: number = 0; + cluster.on('exit', (worker, code, signal) => { + if (code === FAIL) { + this.logger.throwArkTsCompilerError('ArkTS:ERROR Failed to execute ts2abc, exit code non-zero'); + } + workerCount++; + if (workerCount === workerNumber) { + this.afterCompilationProcess(); + } + this.triggerEndSignal(); + }); + } + } + + private getSmallestSizeGroup(groupSize: Map): any { + const groupSizeArray: any = Array.from(groupSize); + groupSizeArray.sort(function(g1, g2) { + return g1[1] - g2[1]; // sort by size + }); + return groupSizeArray[0][0]; + } + + private splitJsBundlesBySize(bundleArray: Array, groupNumber: number): any { + const result: any = []; + if (bundleArray.length < groupNumber) { + for (const value of bundleArray) { + result.push([value]); + } + return result; + } + + bundleArray.sort(function(f1: File, f2: File) { + return f2.size - f1.size; + }); + const groupFileSize: any = new Map(); + for (let i = 0; i < groupNumber; ++i) { + result.push([]); + groupFileSize.set(i, 0); + } + + let index: number = 0; + while (index < bundleArray.length) { + const smallestGroup: any = this.getSmallestSizeGroup(groupFileSize); + result[smallestGroup].push(bundleArray[index]); + const sizeUpdate: any = groupFileSize.get(smallestGroup) + bundleArray[index].size; + groupFileSize.set(smallestGroup, sizeUpdate); + index++; + } + return result; + } + + private writeHashJson() { + if (this.hashJsonFilePath.length === 0) { + return; + } + + for (let i = 0; i < this.filterIntermediateJsBundle.length; ++i) { + const cacheFilePath: string = this.filterIntermediateJsBundle[i].cacheFilePath; + const cacheAbcFilePath: string = changeFileExtension(cacheFilePath, EXTNAME_ABC); + if (!fs.existsSync(cacheFilePath) || !fs.existsSync(cacheAbcFilePath)) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_HASH_JSON_FILE_GENERATION_MISSING_PATHS, + ArkTSInternalErrorDescription, + `During hash JSON file generation, ${cacheFilePath} or ${cacheAbcFilePath} is not found.` + ); + this.logger.printErrorAndExit(errInfo); + } + const hashCacheFileContentData: string = toHashData(cacheFilePath); + const hashCacheAbcContentData: string = toHashData(cacheAbcFilePath); + this.hashJsonObject[cacheFilePath] = hashCacheFileContentData; + this.hashJsonObject[cacheAbcFilePath] = hashCacheAbcContentData; + } + + fs.writeFileSync(this.hashJsonFilePath, JSON.stringify(this.hashJsonObject), 'utf-8'); + } + + private copyFileFromCachePathToOutputPath() { + for (const value of this.intermediateJsBundle.values()) { + const abcFilePath: string = changeFileExtension(value.filePath, EXTNAME_ABC, TEMP_JS); + const cacheAbcFilePath: string = changeFileExtension(value.cacheFilePath, EXTNAME_ABC); + if (!fs.existsSync(cacheAbcFilePath)) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_INCREMENTAL_BUILD_MISSING_CACHE_ABC_FILE_PATH, + ArkTSInternalErrorDescription, + `${cacheAbcFilePath} not found during incremental build.` + ); + this.logger.printErrorAndExit(errInfo); + } + const parent: string = path.join(abcFilePath, '..'); + if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { + mkDir(parent); + } + // for preview mode, cache path and old abc file both exist, should copy abc file for updating + if (this.projectConfig.cachePath !== undefined) { + fs.copyFileSync(cacheAbcFilePath, abcFilePath); + } + } + } + + private cleanTempCacheFiles() { + // in xts mode, as cache path is not provided, cache files are located in output path, clear them + if (this.projectConfig.cachePath !== undefined) { + return; + } + + for (const value of this.intermediateJsBundle.values()) { + if (fs.existsSync(value.cacheFilePath)) { + fs.unlinkSync(value.cacheFilePath); + } + } + + if (isEs2Abc(this.projectConfig) && fs.existsSync(this.filesInfoPath)) { + unlinkSync(this.filesInfoPath); + } + } + + private removeCompilationCache(): void { + if (fs.existsSync(this.hashJsonFilePath)) { + fs.unlinkSync(this.hashJsonFilePath); + } + } +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/bundle/bundle_preview_mode.ts b/compiler/src/interop/src/fast_build/ark_compiler/bundle/bundle_preview_mode.ts new file mode 100644 index 0000000000000000000000000000000000000000..645868049e2b0773a306856e922bbb75ea300ae8 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/bundle/bundle_preview_mode.ts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 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 { BundleMode } from './bundle_mode'; + +export class BundlePreviewMode extends BundleMode { + constructor(rollupObject: Object, rollupBundleFileSet: Object) { + super(rollupObject, rollupBundleFileSet); + } + + generateAbc() { + if (this.filterIntermediateJsBundle.length === 0) { + return; + } + super.executeArkCompiler(); + } +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/cache.ts b/compiler/src/interop/src/fast_build/ark_compiler/cache.ts new file mode 100644 index 0000000000000000000000000000000000000000..222fb4cfdf780784d4a3f40ae34c060847b9913c --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/cache.ts @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2023 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 fs from 'fs'; +import path from 'path'; +import { + ARK_COMPILER_META_INFO, + ESMODULE, + IS_CACHE_INVALID +} from './common/ark_define'; +import { + TRANSFORMED_MOCK_CONFIG, + USER_DEFINE_MOCK_CONFIG +} from '../../pre_define'; + +let disableCache: boolean = false; +export function checkArkCompilerCacheInfo(rollupObject: Object): void { + disableCache = false; + const metaInfo: string = getMetaInfo(rollupObject.share.projectConfig, rollupObject.share.arkProjectConfig); + const lastMetaInfo: string = rollupObject.cache.get(ARK_COMPILER_META_INFO); + if (!lastMetaInfo || metaInfo !== lastMetaInfo || isMockConfigModified(rollupObject)) { + rollupObject.cache.set(IS_CACHE_INVALID, true); + disableCache = true; + } + rollupObject.cache.set(ARK_COMPILER_META_INFO, metaInfo); +} + +function getMetaInfo(projectConfig: Object, arkProjectConfig: Object): string { + let metaInfoArr: string[] = []; + // user selects the compiled API version information + const compileSdkVersion: string = projectConfig.compileSdkVersion ? + projectConfig.compileSdkVersion : 'null_compileSdkVersion'; + // user selects the compatible API version information + const compatibleSdkVersion: string = projectConfig.compatibleSdkVersion ? + projectConfig.compatibleSdkVersion : 'null_compatibleSdkVersion'; + const compatibleSdkVersionStage: string = projectConfig.compatibleSdkVersionStage ? + projectConfig.compatibleSdkVersionStage : 'null_compatibleSdkVersionStage'; + const runtimeOS: string = projectConfig.runtimeOS ? projectConfig.runtimeOS : 'null_runtimeOS'; + const sdkPath: string = projectConfig.etsLoaderPath ? + projectConfig.etsLoaderPath : 'null_sdkPath'; + // version information for loading SDKs in the IDE + const sdkVersion: string = projectConfig.etsLoaderVersion ? + projectConfig.etsLoaderVersion : 'null_sdkVersion'; + const sdkReleaseType: string = projectConfig.etsLoaderReleaseType ? + projectConfig.etsLoaderReleaseType : 'null_sdkReleaseType'; + metaInfoArr.push( + compileSdkVersion, compatibleSdkVersion, compatibleSdkVersionStage, runtimeOS, sdkPath, sdkVersion, sdkReleaseType); + + if (projectConfig.compileHar) { + const useTsHar: string = arkProjectConfig.useTsHar; + metaInfoArr.push(useTsHar); + } + + if (projectConfig.compileMode === ESMODULE) { + const bundleName: string = projectConfig.bundleName ? projectConfig.bundleName : 'null_bundleName'; + const allModuleNameHash: string = projectConfig.allModuleNameHash ? projectConfig.allModuleNameHash : + 'null_allModuleNameHash'; + const aotCompileMode: string = projectConfig.aotCompileMode ? projectConfig.aotCompileMode : 'null_aotCompileMode'; + const apPath: string = projectConfig.apPath ? projectConfig.apPath : 'null_apPath'; + metaInfoArr.push(bundleName, allModuleNameHash, aotCompileMode, apPath); + } + + return metaInfoArr.join(':'); +} + +function isMockConfigModified(rollupObject): boolean { + // mock is only enabled in the following two modes + if (!rollupObject.share.projectConfig.isPreview && !rollupObject.share.projectConfig.isOhosTest) { + return false; + } + + if (!rollupObject.share.projectConfig.mockParams || !rollupObject.share.projectConfig.mockParams.mockConfigPath) { + return false; + } + + const userDefinedMockConfigCache: string = + path.resolve(rollupObject.share.projectConfig.cachePath, `./${USER_DEFINE_MOCK_CONFIG}`); + const transformedMockConfigCache: string = + path.resolve(rollupObject.share.projectConfig.cachePath, `./${TRANSFORMED_MOCK_CONFIG}`); + if (!fs.existsSync(userDefinedMockConfigCache) || !fs.existsSync(transformedMockConfigCache)) { + return true; + } + + const mockConfigInfo: Object = + require('json5').parse(fs.readFileSync(rollupObject.share.projectConfig.mockParams.mockConfigPath, 'utf-8')); + const cachedMockConfigInfo: Object = + require('json5').parse(fs.readFileSync(userDefinedMockConfigCache, 'utf-8')); + return JSON.stringify(mockConfigInfo) !== JSON.stringify(cachedMockConfigInfo); +} + +/** + * rollup shouldInvalidCache hook + * @param {rollup OutputOptions} options + */ +export function shouldInvalidCache(): boolean { + return disableCache; +} +export const utCache = { + getMetaInfo +}; diff --git a/compiler/src/interop/src/fast_build/ark_compiler/check_import_module.ts b/compiler/src/interop/src/fast_build/ark_compiler/check_import_module.ts new file mode 100644 index 0000000000000000000000000000000000000000..2ed3ca4d116841c9a6cf064dd496f7f27faf7189 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/check_import_module.ts @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 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 { ModuleSourceFile } from './module/module_source_file'; +import { isJsSourceFile } from './utils'; +import { toUnixPath } from '../../utils'; +import { compilerOptions } from '../../ets_checker'; +import { + EXTNAME_D_ETS, + EXTNAME_ETS, + GEN_ABC_PLUGIN_NAME, + red, + yellow +} from './common/ark_define'; +import { + CommonLogger, + LogData, + LogDataFactory +} from './logger'; +import { + ArkTSErrorDescription, + ErrorCode +} from './error_code'; + +export function checkIfJsImportingArkts(rollupObject: Object, moduleSourceFile?: ModuleSourceFile): void { + if (rollupObject.share.projectConfig.singleFileEmit && moduleSourceFile) { + checkAndLogArkTsFileImports(rollupObject, moduleSourceFile); + } else { + ModuleSourceFile.getSourceFiles().forEach((sourceFile: ModuleSourceFile) => { + checkAndLogArkTsFileImports(rollupObject, sourceFile); + }); + } +} + +function checkAndLogArkTsFileImports(rollupObject: Object, sourceFile: ModuleSourceFile): void { + const id: string = sourceFile.getModuleId(); + const unixId: string = toUnixPath(id); + const logger: CommonLogger = CommonLogger.getInstance(rollupObject); + if (isJsSourceFile(id) && unixId.indexOf('/oh_modules/') === -1) { + const importMap = rollupObject.getModuleInfo(id).importedIdMaps; + Object.values(importMap).forEach((requestFile: string) => { + logIfFileEndsWithArkts(requestFile, id, logger); + } + ); + } +} + +function logIfFileEndsWithArkts(requestFile: string, id: string, logger: CommonLogger): void { + if (requestFile.endsWith(EXTNAME_ETS) || requestFile.endsWith(EXTNAME_D_ETS)) { + if (compilerOptions.isCompatibleVersion) { + const warnMsg: string = `ArkTS:WARN File: ${id}\n` + + `Importing ArkTS files in JS and TS files is about to be forbidden.\n`; + logger.warn(yellow + warnMsg); + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_FORBIDDEN_IMPORT_ARKTS_FILE, + ArkTSErrorDescription, + 'Importing ArkTS files in JS and TS files is forbidden.', + '', + [`Please remove the import statement of the ArkTS file in ${id}.`] + ); + logger.printError(errInfo); + } + } +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/check_shared_module.ts b/compiler/src/interop/src/fast_build/ark_compiler/check_shared_module.ts new file mode 100644 index 0000000000000000000000000000000000000000..d5b8d4f805cd3b5da4c62ffef7cc7ae27b299d35 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/check_shared_module.ts @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ts from 'typescript'; + +import { + USE_SHARED, + USE_SHARED_COMMENT +} from './common/ark_define'; +import { + GeneratedFileInHar, + harFilesRecord, + toUnixPath +} from '../../utils'; +import { + projectConfig +} from '../../../main'; + +export const sharedModuleSet: Set = new Set(); + +export function collectSharedModule(source: string, filePath: string, sourceFile: ts.SourceFile | null): void { + if (!sourceFile) { + sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); + } + + // "use shared" will only be effective when used after imports, before other statements + for (const statement of sourceFile.statements) { + if (ts.isImportDeclaration(statement)) { + continue; + } + if (ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression) && + statement.expression.text === USE_SHARED) { + sharedModuleSet.add(toUnixPath(filePath)); + processDeclarationFile(filePath); + } + return; + } +} + +// For hsp and close-source har, we need add comment '// use shared' for shared modules to inform developers. +function processDeclarationFile(filePath: string): void { + if (!projectConfig.compileHar && !projectConfig.compileShared) { + return; + } + + const generatedDetsFilesInHar: GeneratedFileInHar = harFilesRecord.get(toUnixPath(filePath)); + if (generatedDetsFilesInHar) { + generatedDetsFilesInHar.originalDeclarationContent = USE_SHARED_COMMENT + '\n' + + generatedDetsFilesInHar.originalDeclarationContent; + } +} + +export function cleanSharedModuleSet(): void { + sharedModuleSet.clear(); +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/common/ark_define.ts b/compiler/src/interop/src/fast_build/ark_compiler/common/ark_define.ts new file mode 100644 index 0000000000000000000000000000000000000000..78f167b62de4b6b7724f8f598ff4ee2472fba794 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/common/ark_define.ts @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023 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 ENTRY_TXT: string = 'entry.txt'; +export const FILESINFO_TXT: string = 'filesInfo.txt'; +export const FILESINFO: string = 'filesInfo'; +export const NPMENTRIES_TXT: string = 'npmEntries.txt'; +export const MODULES_CACHE: string = 'modules.cache'; +export const MODULES_ABC: string = 'modules.abc'; +export const WIDGETS_ABC: string = 'widgets.abc'; +export const MODULELIST_JSON: string = 'moduleList.json'; +export const PREBUILDMODE_JSON: string = 'preBuildMode.json'; +export const SOURCEMAPS_JSON: string = 'sourceMaps.json'; +export const SOURCEMAPS: string = 'sourceMaps.map'; +export const SYMBOLMAP: string = 'symbolMap.map'; +export const PROTO_FILESINFO_TXT: string = 'protoFilesInfo.txt'; +export const AOT_FULL: string = 'full'; +export const AOT_TYPE: string = 'type'; +export const AOT_PARTIAL: string = 'partial'; +export const AOT_PROFILE_SUFFIX: string = '.ap'; +export const NPM_ENTRIES_PROTO_BIN: string = 'npm_entries.protoBin'; +export const PACKAGE_JSON: string = 'package.json'; +export const FAKE_JS: string = 'fake.js'; +export const COMPILE_CONTEXT_INFO_JSON: string = 'compileContextInfo.json'; + +export const ESMODULE: string = 'esmodule'; +export const JSBUNDLE: string = 'jsbundle'; +export const ARK: string = 'ark'; +export const TEMPORARY: string = 'temporary'; +export const MAIN: string = 'main'; +export const AUXILIARY: string = 'auxiliary'; +export const HAP_PACKAGE: string = '0'; +export const PROJECT_PACKAGE: string = '1'; +export const EXTNAME_ETS: string = '.ets'; +export const EXTNAME_D_ETS: string = '.d.ets'; +export const EXTNAME_JS: string = '.js'; +export const EXTNAME_TS: string = '.ts'; +export const EXTNAME_JS_MAP: string = '.js.map'; +export const EXTNAME_TS_MAP: string = '.ts.map'; +export const EXTNAME_MJS: string = '.mjs'; +export const EXTNAME_CJS: string = '.cjs'; +export const EXTNAME_D_TS: string = '.d.ts'; +export const EXTNAME_ABC: string = '.abc'; +export const EXTNAME_JSON: string = '.json'; +export const EXTNAME_PROTO_BIN: string = '.protoBin'; +export const PATCH_SYMBOL_TABLE: string = 'symbol.txt'; +export const TEMP_JS: string = '.temp.js'; +export const HASH_FILE_NAME: string = 'gen_hash.json'; +export const EXTNAME_TXT: string = '.txt'; +export const PROTOS: string = 'protos'; + +export const TS2ABC: string = 'ts2abc'; +export const ES2ABC: string = 'es2abc'; + +export const JS: string = 'js'; +export const TS: string = 'ts'; +export const ETS: string = 'ets'; + +export const MAX_WORKER_NUMBER: number = 3; + +export const GEN_ABC_SCRIPT: string = 'gen_abc.js'; + +export const NODE_MODULES: string = 'node_modules'; +export const OH_MODULES: string = 'oh_modules'; +export const PACKAGES: string = 'pkg_modules'; +export const OHPM: string = 'ohpm'; + +export const TS_NOCHECK: string = '// @ts-nocheck'; + +export const WINDOWS: string = 'Windows_NT'; +export const LINUX: string = 'Linux'; +export const MAC: string = 'Darwin'; + +export const COMMONJS: string = 'commonjs'; +export const ESM: string = 'esm'; +export const SCRIPT: string = 'script'; + +export const SRC_MAIN: string = 'src/main'; +export const GEN_ABC_PLUGIN_NAME: string = 'Gen_Abc_Plugin'; +export const OBFUSCATION_TOOL: string = 'Obfuscation_Tool'; + +export const SUCCESS: number = 0; +export const FAIL: number = 1; + +export const red: string = '\u001b[31m'; +export const yellow: string = '\u001b[33m'; +export const blue: string = '\u001b[34m'; +export const reset: string = '\u001b[39m'; + +export const DEBUG: string = 'debug'; +export const RELEASE: string = 'release'; + +export const TRUE: string = 'true'; +export const FALSE: string = 'false'; + +export const IS_CACHE_INVALID: string = 'is_cache_invalid'; +export const ARK_COMPILER_META_INFO: string = 'ark_compiler_meta_info'; + +// The following strings are used to specify 'ISendable' interface. +// 'ISendable' interface is in the 'lang' namespace of '@arkts.lang.d.ets' file. +export const ARKTS_LANG_D_ETS = '@arkts.lang.d.ets'; +export const LANG_NAMESPACE = 'lang'; +export const ISENDABLE_TYPE = 'ISendable'; + +export const USE_SHARED: string = 'use shared'; +export const USE_SHARED_COMMENT: string = '// "use shared"'; + +export const SEPARATOR_BITWISE_AND: string = '&'; +export const SEPARATOR_AT: string = '@'; +export const SEPARATOR_SLASH: string = '/'; + diff --git a/compiler/src/interop/src/fast_build/ark_compiler/common/common_mode.ts b/compiler/src/interop/src/fast_build/ark_compiler/common/common_mode.ts new file mode 100644 index 0000000000000000000000000000000000000000..bbff82a71e964816584c8586a23bd999892b8786 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/common/common_mode.ts @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2023 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 fs from 'fs'; +import path from 'path'; + +import { + HASH_FILE_NAME, + IS_CACHE_INVALID, + GEN_ABC_PLUGIN_NAME, + GEN_ABC_SCRIPT, + blue, + reset, + ES2ABC, + TS2ABC +} from './ark_define'; +import { initArkConfig } from './process_ark_config'; +import { + nodeLargeOrEqualTargetVersion, + mkdirsSync, + validateFilePathLength +} from '../../../utils'; +import { + isEs2Abc, + isOhModules, + isTs2Abc +} from '../../../ark_utils'; +import { + genTemporaryModuleCacheDirectoryForBundle +} from '../utils'; +import { + CommonLogger, + LogData, + LogDataFactory +} from '../logger'; +import { + ArkTSInternalErrorDescription, + ErrorCode +} from '../error_code'; + +export abstract class CommonMode { + projectConfig: Object; + arkConfig: Object; + cmdArgs: string[] = []; + logger: CommonLogger; + hashJsonFilePath: string; + genAbcScriptPath: string; + triggerAsync: Object; + triggerEndSignal: Object; + + constructor(rollupObject: Object) { + this.projectConfig = Object.assign(rollupObject.share.arkProjectConfig, rollupObject.share.projectConfig); + this.arkConfig = initArkConfig(this.projectConfig); + this.logger = CommonLogger.getInstance(rollupObject); + this.cmdArgs = this.initCmdEnv(); + this.hashJsonFilePath = this.genHashJsonFilePath(); + this.genAbcScriptPath = path.resolve(__dirname, GEN_ABC_SCRIPT); //??? + // Each time triggerAsync() was called, IDE will wait for asynchronous operation to finish before continue excuting. + // The finish signal was passed to IDE by calling triggerEndSignal() in the child process created by triggerAsync() + // When multiple workers were invoked by triggerAsync(), IDE will wait until the times of calling + // triggerEndSignal() matches the number of workers invoked. + // If the child process throws an error by calling throwArkTsCompilerError(), IDE will reset the counting state. + this.triggerAsync = rollupObject.async; + this.triggerEndSignal = rollupObject.signal; + } + + initCmdEnv() { + let args: string[] = []; + + if (isTs2Abc(this.projectConfig)) { + let ts2abc: string = this.arkConfig.ts2abcPath; + validateFilePathLength(ts2abc, this.logger); + + ts2abc = '"' + ts2abc + '"'; + args = [`${this.arkConfig.nodePath}`, '--expose-gc', ts2abc]; + if (this.arkConfig.isDebug) { + args.push('--debug'); + } + if (isOhModules(this.projectConfig)) { + args.push('--oh-modules'); + } + } else if (isEs2Abc(this.projectConfig)) { + const es2abc: string = this.arkConfig.es2abcPath; + validateFilePathLength(es2abc, this.logger); + + args = ['"' + es2abc + '"']; + // compile options added here affect both bundle mode and module mode + if (this.arkConfig.isDebug) { + args.push('--debug-info'); + } + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_INVALID_COMPILE_MODE, + ArkTSInternalErrorDescription, + 'Invalid compilation mode. ' + + `ProjectConfig.pandaMode should be either ${TS2ABC} or ${ES2ABC}.` + ); + this.logger.printErrorAndExit(errInfo); + } + + return args; + } + + private genHashJsonFilePath() { + if (this.projectConfig.cachePath) { + if (!fs.existsSync(this.projectConfig.cachePath) || !fs.statSync(this.projectConfig.cachePath).isDirectory()) { + this.logger.debug(blue, `ArkTS:WARN cache path does bit exist or is not directory`, reset); + return ''; + } + const hashJsonPath: string = path.join(genTemporaryModuleCacheDirectoryForBundle(this.projectConfig), HASH_FILE_NAME); + validateFilePathLength(hashJsonPath, this.logger); + mkdirsSync(path.dirname(hashJsonPath)); + return hashJsonPath; + } else { + this.logger.debug(blue, `ArkTS:WARN cache path not specified`, reset); + return ''; + } + } + + setupCluster(cluster: Object): void { + cluster.removeAllListeners('exit'); + if (nodeLargeOrEqualTargetVersion(16)) { + cluster.setupPrimary({ + exec: this.genAbcScriptPath, + windowsHide: true + }); + } else { + cluster.setupMaster({ + exec: this.genAbcScriptPath, + windowsHide: true + }); + } + } + + protected needRemoveCacheInfo(rollupObject: Object): boolean { + return rollupObject.cache.get(IS_CACHE_INVALID) || rollupObject.cache.get(IS_CACHE_INVALID) === undefined; + } + + protected removeCacheInfo(rollupObject: Object): void { + if (this.needRemoveCacheInfo(rollupObject)) { + this.removeCompilationCache(); + } + rollupObject.cache.set(IS_CACHE_INVALID, false); + } + + abstract removeCompilationCache(): void; +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/common/gen_abc.ts b/compiler/src/interop/src/fast_build/ark_compiler/common/gen_abc.ts new file mode 100644 index 0000000000000000000000000000000000000000..248d66522dac0ed6f10679668755e8349f0115a8 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/common/gen_abc.ts @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023 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 childProcess from 'child_process'; +import cluster from 'cluster'; +import fs from 'fs'; +import path from 'path'; +import process from 'process'; + +import { + COMMONJS, + ESM, + ESMODULE, + EXTNAME_ABC, + JSBUNDLE, + FAIL, + SUCCESS +} from './ark_define'; +import { changeFileExtension} from '../utils'; + +function genAbcByWorkersOfBundleMode(jsonInput: string, cmd: string): Promise { + const inputPaths: any = JSON.parse(jsonInput); + for (let i = 0; i < inputPaths.length; ++i) { + const cacheFilePath: string = inputPaths[i].cacheFilePath; + const cacheAbcFilePath: string = changeFileExtension(cacheFilePath, EXTNAME_ABC); + const sourceFile: string = inputPaths[i].sourceFile; + const singleCmd: string = `${cmd} "${cacheFilePath}" -o "${cacheAbcFilePath}" --source-file "${sourceFile}"`; + try { + childProcess.execSync(singleCmd, { windowsHide: true }); + } catch (e) { + process.send({ data: e.toString() }); + process.exit(FAIL); + } + } + + return; +} + +function genAbcByWorkersOfModuleMode(jsonInput: string, cmd: string, workerFileName: string, + cachePath: string): Promise { + const inputPaths: any = JSON.parse(jsonInput); + const filePath: string = path.join(cachePath, workerFileName); + let content: string = ''; + for (let i = 0; i < inputPaths.length; ++i) { + const info: any = inputPaths[i]; + const moduleType: string = info.isCommonJs ? COMMONJS : ESM; + content += `${info.cacheFilePath};${info.recordName};${moduleType};${info.sourceFile};${info.packageName}`; + if (i < inputPaths.length - 1) { + content += '\n'; + } + } + fs.writeFileSync(filePath, content, 'utf-8'); + const singleCmd: string = `${cmd} "${filePath}"`; + try { + childProcess.execSync(singleCmd, { windowsHide: true }); + } catch (e) { + process.send({ data: e.toString() }); + process.exit(FAIL); + } + + return; +} + +process.stderr.write = function(chunk) { + const message = chunk.toString(); + if (message.length !== 0) { + // send only non-empty message. sometimes there will be empty stderr, + // if processed by parent process's logger.error, the gen_abc process will fail + process.send({ + data: message + }); + } + return true; +}; + +if (cluster.isWorker && process.env.inputs !== undefined && process.env.cmd !== undefined && + process.env.mode !== undefined) { + if (process.env.mode === JSBUNDLE) { + genAbcByWorkersOfBundleMode(process.env.inputs, process.env.cmd); + process.exit(SUCCESS); + } else if (process.env.mode === ESMODULE && process.env.workerFileName && process.env.cachePath) { + genAbcByWorkersOfModuleMode(process.env.inputs, process.env.cmd, process.env.workerFileName, + process.env.cachePath); + process.exit(SUCCESS); + } else { + process.exit(FAIL); + } +} else { + process.send({ + data: 'Failed to invoke worker with uncomplete inputs' + }); + process.exit(FAIL); +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/common/ob_config_resolver.ts b/compiler/src/interop/src/fast_build/ark_compiler/common/ob_config_resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..883ca9071d2f9f36250e4063b6f5b0f1c041df34 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/common/ob_config_resolver.ts @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2023 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 fs from 'fs'; +import path from 'path'; +import type * as ts from 'typescript'; +import { + ApiExtractor, + clearGlobalCaches, + clearNameCache, + deleteLineInfoForNameString, + endFilesEvent, + endSingleFileEvent, + EventList, + getRelativeSourcePath, + handleUniversalPathInObf, + mangleFilePath, + MemoryUtils, + nameCacheMap, + performancePrinter, + printTimeSumData, + printTimeSumInfo, + ProjectCollections, + startFilesEvent, + startSingleFileEvent, + unobfuscationNamesObj, + writeObfuscationNameCache, + writeUnobfuscationContent, +} from 'arkguard'; +import type { + ArkObfuscator, + HvigorErrorInfo +} from 'arkguard'; + +import { GeneratedFileInHar, harFilesRecord, isPackageModulesFile, mkdirsSync, toUnixPath } from '../../../utils'; +import { allSourceFilePaths, collectAllFiles, localPackageSet } from '../../../ets_checker'; +import { isCurrentProjectFiles } from '../utils'; +import { sourceFileBelongProject } from '../module/module_source_file'; +import { + compileToolIsRollUp, + createAndStartEvent, + mangleDeclarationFileName, + ModuleInfo, + stopEvent, + writeArkguardObfuscatedSourceCode +} from '../../../ark_utils'; +import { OBFUSCATION_TOOL, red, yellow } from './ark_define'; +import { logger } from '../../../compile_info'; +import { MergedConfig } from '../common/ob_config_resolver'; +import { ModuleSourceFile } from '../module/module_source_file'; +import { readProjectAndLibsSource } from './process_ark_config'; +import { CommonLogger } from '../logger'; +import { MemoryMonitor } from '../../meomry_monitor/rollup-plugin-memory-monitor'; +import { MemoryDefine } from '../../meomry_monitor/memory_define'; +import { ESMODULE } from '../../../pre_define'; + +export { + collectResevedFileNameInIDEConfig, // For running unit test. + collectReservedNameForObf, + enableObfuscatedFilePathConfig, + enableObfuscateFileName, + generateConsumerObConfigFile, + getRelativeSourcePath, + handleObfuscatedFilePath, + handleUniversalPathInObf, + mangleFilePath, + MergedConfig, + nameCacheMap, + ObConfigResolver, + writeObfuscationNameCache, + writeUnobfuscationContent, +} from 'arkguard'; //??? + +export function resetObfuscation(): void { + clearGlobalCaches(); + sourceFileBelongProject.clear(); + ApiExtractor.mPropertySet?.clear(); + ApiExtractor.mSystemExportSet?.clear(); + localPackageSet?.clear(); +} + +/** + * dependencies of sourceFiles + */ +export const sourceFileDependencies: Map> = new Map(); + +/** + * Identifier cache name + */ +export const IDENTIFIER_CACHE: string = 'IdentifierCache'; + +/** + * Subsystem Number For Obfuscation + */ +export const OBF_ERR_CODE = '108'; + +/** + * Logger for obfuscation + */ +export let obfLogger: Object = undefined; + +enum LoggerLevel { + WARN = 'warn', + ERROR = 'error' +} + +export function initObfLogger(share: Object): void { + if (share) { + obfLogger = share.getHvigorConsoleLogger ? share.getHvigorConsoleLogger(OBF_ERR_CODE) : share.getLogger(OBFUSCATION_TOOL); + return; + } + obfLogger = logger; +} + +export function printObfLogger(errorInfo: string, errorCodeInfo: HvigorErrorInfo | string, level: LoggerLevel): void { + if (obfLogger.printError) { + switch (level) { + case LoggerLevel.WARN: + obfLogger.printWarn(errorCodeInfo); + break; + case LoggerLevel.ERROR: + obfLogger.printError(errorCodeInfo); + break; + default: + break; + } + return; + } + + switch (level) { + case LoggerLevel.WARN: + obfLogger.warn(yellow, errorInfo); + break; + case LoggerLevel.ERROR: + obfLogger.error(red, errorInfo); + break; + default: + return; + } +} + +// Collect all keep files. If the path configured by the developer is a folder, all files in the compilation will be used to match this folder. +function collectAllKeepFiles(startPaths: string[], excludePathSet: Set): Set { + const allKeepFiles: Set = new Set(); + const keepFolders: string[] = []; + startPaths.forEach(filePath => { + if (excludePathSet.has(filePath)) { + return; + } + if (fs.statSync(filePath).isDirectory()) { + keepFolders.push(filePath); + } else { + allKeepFiles.add(filePath); + } + }); + if (keepFolders.length === 0) { + return allKeepFiles; + } + + allSourceFilePaths.forEach(filePath => { + if (keepFolders.some(folderPath => filePath.startsWith(folderPath)) && !excludePathSet.has(filePath)) { + allKeepFiles.add(filePath); + } + }); + return allKeepFiles; +} + +// Collect all keep files and then collect their dependency files. +export function handleKeepFilesAndGetDependencies(mergedObConfig: MergedConfig, arkObfuscator: ArkObfuscator, + projectConfig: Object): Set { + if (mergedObConfig === undefined || mergedObConfig.keepSourceOfPaths.length === 0) { + sourceFileDependencies.clear(); + return new Set(); + } + const keepPaths = mergedObConfig.keepSourceOfPaths; + const excludePaths = mergedObConfig.excludePathSet; + let allKeepFiles: Set = collectAllKeepFiles(keepPaths, excludePaths); + arkObfuscator.setKeepSourceOfPaths(allKeepFiles); + const keepFilesAndDependencies: Set = getFileNamesForScanningWhitelist(mergedObConfig, allKeepFiles, projectConfig); + sourceFileDependencies.clear(); + return keepFilesAndDependencies; +} + +/** + * Use tsc's dependency collection to collect the dependency files of the keep files. + * Risk: The files resolved by typescript are different from the files resolved by rollup. For example, the two entry files have different priorities. + * Tsc looks for files in the types field in oh-packagek.json5 first, and rollup looks for files in the main field. + */ +function getFileNamesForScanningWhitelist(mergedObConfig: MergedConfig, allKeepFiles: Set, + projectConfig: Object): Set { + const keepFilesAndDependencies: Set = new Set(); + if (!mergedObConfig.options.enableExportObfuscation) { + return keepFilesAndDependencies; + } + let stack: string[] = Array.from(allKeepFiles); + while (stack.length > 0) { + const filePath: string = toUnixPath(stack.pop()); + if (keepFilesAndDependencies.has(filePath)) { + continue; + } + + keepFilesAndDependencies.add(filePath); + const dependentModules: ts.ModeAwareCache = sourceFileDependencies.get(filePath); + dependentModules?.forEach(resolvedModule => { + if (!resolvedModule) { + // For `import moduleName form 'xx.so'`, when the xx.so cannot be resolved, dependentModules is [null] + return; + } + let curDependencyPath: string = toUnixPath(resolvedModule.resolvedFileName); + // resolvedModule can record system Api declaration files and ignore them + if (isCurrentProjectFiles(curDependencyPath, projectConfig)) { + stack.push(curDependencyPath); + } + }); + } + return keepFilesAndDependencies; +} + +/** + * Get namecache by path + * + * If it is a declaration file, retrieves the corresponding source file's obfuscation results + * Or retrieves obfuscation results from full compilation run + */ +export function getNameCacheByPath( + moduleInfo: ModuleInfo, + isDeclaration: boolean, + projectRootPath: string | undefined +): Map { + let historyNameCache = new Map(); + let nameCachePath = moduleInfo.relativeSourceFilePath; + if (isDeclaration) { + nameCachePath = getRelativeSourcePath( + moduleInfo.originSourceFilePath, + projectRootPath, + sourceFileBelongProject.get(toUnixPath(moduleInfo.originSourceFilePath)) + ); + } + if (nameCacheMap) { + let identifierCache = nameCacheMap.get(nameCachePath)?.[IDENTIFIER_CACHE]; + deleteLineInfoForNameString(historyNameCache, identifierCache); + } + return historyNameCache; +} + +/** + * Set newly updated namecache for project source files + */ +export function setNewNameCache( + newNameCache: Object, + isDeclaration: boolean, + moduleInfo: ModuleInfo, + projectConfig: Object +): void { + if (newNameCache && !isDeclaration) { + let obfName: string = moduleInfo.relativeSourceFilePath; + let isOhModule: boolean = isPackageModulesFile(moduleInfo.originSourceFilePath, projectConfig); + if (projectConfig.obfuscationMergedObConfig?.options.enableFileNameObfuscation && !isOhModule) { + obfName = mangleFilePath(moduleInfo.relativeSourceFilePath); + } + newNameCache.obfName = obfName; + nameCacheMap.set(moduleInfo.relativeSourceFilePath, newNameCache); + } +} + +/** + * Set unobfuscation list after obfuscation + */ +export function setUnobfuscationNames( + unobfuscationNameMap: Map> | undefined, + relativeSourceFilePath: string, + isDeclaration: boolean +): void { + if (unobfuscationNameMap && !isDeclaration) { + let arrayObject: Record = {}; + // The type of unobfuscationNameMap's value is Set, convert Set to Array. + unobfuscationNameMap.forEach((value: Set, key: string) => { + let array: string[] = Array.from(value); + arrayObject[key] = array; + }); + unobfuscationNamesObj[relativeSourceFilePath] = arrayObject; + } +} + +/** + * Write out obfuscated files + */ +export function writeObfuscatedFile(newFilePath: string, content: string): void { + startSingleFileEvent(EventList.WRITE_FILE, performancePrinter.timeSumPrinter); + mkdirsSync(path.dirname(newFilePath)); + fs.writeFileSync(newFilePath, content); + endSingleFileEvent(EventList.WRITE_FILE, performancePrinter.timeSumPrinter, false, true); +} + +// create or update incremental caches, also set the shouldReObfuscate flag if needed +export function updateIncrementalCaches(arkObfuscator: ArkObfuscator): void { + if (arkObfuscator.filePathManager) { + const deletedFilesSet: Set = arkObfuscator.filePathManager.getDeletedSourceFilePaths(); + ProjectCollections.projectWhiteListManager.createOrUpdateWhiteListCaches(deletedFilesSet); + arkObfuscator.fileContentManager.deleteFileContent(deletedFilesSet); + arkObfuscator.shouldReObfuscate = ProjectCollections.projectWhiteListManager.getShouldReObfuscate(); + } +} + +// Scan all files of project and create or update cache for it +export function readProjectCaches(allFiles: Set, arkObfuscator: ArkObfuscator): void { + arkObfuscator.filePathManager?.createOrUpdateSourceFilePaths(allFiles); + + // read fileContent caches if is increamental + if (arkObfuscator.isIncremental) { + arkObfuscator.fileContentManager.readFileNamesMap(); + } +} + +export function getUpdatedFiles( + sourceProjectConfig: Object, + allSourceFilePaths: Set, + moduleSourceFiles: ModuleSourceFile[] +): Set { + let updatedFiles: Set = new Set(); + if (sourceProjectConfig.arkObfuscator?.isIncremental) { + moduleSourceFiles.forEach((sourceFile) => { + updatedFiles.add(toUnixPath(sourceFile.moduleId)); + }); + sourceProjectConfig.arkObfuscator?.filePathManager.addedSourceFilePaths.forEach((path: string) => { + updatedFiles.add(path); + }); + // Add declaration files written by users + allSourceFilePaths.forEach((path: string) => { + if (path.endsWith('.d.ts') || path.endsWith('.d.ets')) { + updatedFiles.add(path); + } + }); + } else { + updatedFiles = allSourceFilePaths; + } + return updatedFiles; +} + +/** + * Preprocess of obfuscation: + * 1. collect whileLists for each file. + * 2. create or update incremental caches + */ +export function obfuscationPreprocess( + sourceProjectConfig: Object, + obfuscationConfig: MergedConfig, + allSourceFilePaths: Set, + keepFilesAndDependencies: Set, + moduleSourceFiles: ModuleSourceFile[] +): void { + if (sourceProjectConfig.arkObfuscator) { + readProjectCaches(allSourceFilePaths, sourceProjectConfig.arkObfuscator); + + const updatedFiles = getUpdatedFiles(sourceProjectConfig, allSourceFilePaths, moduleSourceFiles); + + readProjectAndLibsSource( + updatedFiles, + obfuscationConfig, + sourceProjectConfig.arkObfuscator, + sourceProjectConfig.compileHar, + keepFilesAndDependencies + ); + + updateIncrementalCaches(sourceProjectConfig.arkObfuscator); + ProjectCollections.clearProjectWhiteListManager(); + } +} + +/** + * Reobfuscate all files if needed. + */ +export async function reObfuscate( + arkObfuscator: ArkObfuscator, + harFilesRecord: Map, + logger: Function, + projectConfig: Object +): Promise { + // name cache cannot be used since we need to reObfuscate all files + clearNameCache(); + const sortedFiles: string[] = arkObfuscator.fileContentManager.getSortedFiles(); + for (const originFilePath of sortedFiles) { + const transformedFilePath: string = arkObfuscator.fileContentManager.fileNamesMap.get(originFilePath); + const fileContentObj: ProjectCollections.FileContent = arkObfuscator.fileContentManager.readFileContent(transformedFilePath); + const originSourceFilePath: string = toUnixPath(fileContentObj.moduleInfo.originSourceFilePath); + const isOriginalDeclaration: boolean = (/\.d\.e?ts$/).test(originSourceFilePath); + if (isOriginalDeclaration) { + if (!harFilesRecord.has(originSourceFilePath)) { + const genFilesInHar: GeneratedFileInHar = { + sourcePath: originSourceFilePath, + originalDeclarationCachePath: fileContentObj.moduleInfo.buildFilePath, + originalDeclarationContent: fileContentObj.moduleInfo.content + }; + harFilesRecord.set(originSourceFilePath, genFilesInHar); + } + continue; + } + MemoryUtils.tryGC(); + await writeArkguardObfuscatedSourceCode(fileContentObj.moduleInfo, logger, projectConfig, fileContentObj.previousStageSourceMap); + MemoryUtils.tryGC(); + } +} + +/** + * Include collect file, resolve denpendency, read source + */ +export function collectSourcesWhiteList(rollupObject: Object, allSourceFilePaths: Set, sourceProjectConfig: Object, + sourceFiles: ModuleSourceFile[] +): void { + collectAllFiles(undefined, rollupObject.getModuleIds(), rollupObject); + startFilesEvent(EventList.SCAN_SOURCEFILES, performancePrinter.timeSumPrinter); + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.SCAN_SOURCEFILES); + if (compileToolIsRollUp()) { + const obfuscationConfig = sourceProjectConfig.obfuscationMergedObConfig; + handleUniversalPathInObf(obfuscationConfig, allSourceFilePaths); + const keepFilesAndDependencies = handleKeepFilesAndGetDependencies( + obfuscationConfig, + sourceProjectConfig.arkObfuscator, + sourceProjectConfig + ); + obfuscationPreprocess( + sourceProjectConfig, + obfuscationConfig, + allSourceFilePaths, + keepFilesAndDependencies, + sourceFiles + ); + } + MemoryMonitor.stopRecordStage(recordInfo); + endFilesEvent(EventList.SCAN_SOURCEFILES, performancePrinter.timeSumPrinter); +} + +/** + * Handle post obfuscation tasks: + * 1.ReObfuscate all files if whitelists changed in incremental build. + * 2.Obfuscate declaration files. + * 3.Write fileNamesMap. + */ +export async function handlePostObfuscationTasks( + sourceProjectConfig: Object, + projectConfig: Object, + rollupObject: Object, + logger: Function +): Promise { + const arkObfuscator = sourceProjectConfig.arkObfuscator; + + if (arkObfuscator?.shouldReObfuscate) { + await reObfuscate(arkObfuscator, harFilesRecord, logger, projectConfig); + arkObfuscator.shouldReObfuscate = false; + } + + if (compileToolIsRollUp() && rollupObject.share.arkProjectConfig.compileMode === ESMODULE) { + await mangleDeclarationFileName(logger, rollupObject.share.arkProjectConfig, sourceFileBelongProject); + } + + if (arkObfuscator?.fileContentManager) { + arkObfuscator.fileContentManager.writeFileNamesMap(); + } + + printTimeSumInfo('All files obfuscation:'); + printTimeSumData(); + endFilesEvent(EventList.ALL_FILES_OBFUSCATION); +} + +/** + * Write obfuscation caches if needed + */ +export function writeObfuscationCaches(sourceProjectConfig: Object, eventOrEventFactory: Object): void { + const eventObfuscatedCode = createAndStartEvent(eventOrEventFactory, 'write obfuscation name cache'); + + const needToWriteCache = compileToolIsRollUp() && sourceProjectConfig.arkObfuscator && sourceProjectConfig.obfuscationOptions; + const isWidgetCompile = sourceProjectConfig.widgetCompile; + + if (needToWriteCache) { + writeObfuscationNameCache( + sourceProjectConfig, + sourceProjectConfig.entryPackageInfo, + sourceProjectConfig.obfuscationOptions.obfuscationCacheDir, + sourceProjectConfig.obfuscationMergedObConfig.options?.printNameCache + ); + } + + if (needToWriteCache && !isWidgetCompile) { + writeUnobfuscationContent(sourceProjectConfig); + } + + stopEvent(eventObfuscatedCode); +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/common/process_ark_config.ts b/compiler/src/interop/src/fast_build/ark_compiler/common/process_ark_config.ts new file mode 100644 index 0000000000000000000000000000000000000000..ca6be23131834428a63367c5f70780eafc46bf3a --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/common/process_ark_config.ts @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2023 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 path from 'path'; +import fs from 'fs'; +import { + ArkObfuscator, + initObfuscationConfig, + readProjectPropertiesByCollectedPaths, + performancePrinter, + EventList, + blockPrinter, + endFilesEvent, + startFilesEvent +} from 'arkguard'; + +import { + TS2ABC, + ESMODULE, + NODE_MODULES, + OH_MODULES, + OBFUSCATION_TOOL +} from './ark_define'; +import { + isAotMode, + isDebug, + isBranchElimination +} from '../utils'; +import { + isHarmonyOs, + isLinux, + isMac, + isWindows, + toUnixPath +} from '../../../utils'; +import { getArkBuildDir } from '../../../ark_utils'; +import { checkAotConfig } from '../../../gen_aot'; +import { projectConfig as mainProjectConfig } from '../../../../main'; +import type { MergedConfig } from './ob_config_resolver'; +import type { ReseverdSetForArkguard } from 'arkguard'; //??? +import { MemoryMonitor } from '../../meomry_monitor/rollup-plugin-memory-monitor'; +import { MemoryDefine } from '../../meomry_monitor/memory_define'; +import { initObfLogger, printObfLogger } from './ob_config_resolver'; + +type ArkConfig = { + arkRootPath: string; + ts2abcPath: string; + js2abcPath: string; + mergeAbcPath: string; + es2abcPath: string; + aotCompilerPath: string; + nodePath: string; + isDebug: boolean; + isBranchElimination: boolean; + optTryCatchFunc: boolean; +}; + +export function initArkConfig(projectConfig: Object): ArkConfig { + let arkRootPath: string = path.join(__dirname, '..', '..', '..', '..', 'bin', 'ark'); //??? + if (projectConfig.arkFrontendDir) { + arkRootPath = projectConfig.arkFrontendDir; + } + let arkConfig: ArkConfig = { + arkRootPath: '', + ts2abcPath: '', + js2abcPath: '', + mergeAbcPath: '', + es2abcPath: '', + aotCompilerPath: '', + nodePath: '', + isDebug: false, + isBranchElimination: false, + optTryCatchFunc: false + }; + arkConfig.nodePath = 'node'; + if (projectConfig.nodeJs) { + arkConfig.nodePath = projectConfig.nodePath; + } + arkConfig.isDebug = isDebug(projectConfig); + arkConfig.isBranchElimination = isBranchElimination(projectConfig); + arkConfig.optTryCatchFunc = mainProjectConfig.optTryCatchFunc; + arkConfig.arkRootPath = arkRootPath; + processPlatformInfo(arkConfig); + processCompatibleVersion(projectConfig, arkConfig); + return arkConfig; +} + +export function initArkProjectConfig(share: Object): Object { + let projectConfig: Object = share.projectConfig; + let arkProjectConfig: Object = {}; + let entryPackageName: string = share.projectConfig.entryPackageName || ''; + let entryModuleVersion: string = share.projectConfig.entryModuleVersion || ''; + arkProjectConfig.entryPackageInfo = `${entryPackageName}|${entryModuleVersion}`; + arkProjectConfig.projectRootPath = share.projectConfig.projectTopDir; + if (projectConfig.aceBuildJson && fs.existsSync(projectConfig.aceBuildJson)) { + const buildJsonInfo = JSON.parse(fs.readFileSync(projectConfig.aceBuildJson).toString()); + arkProjectConfig.projectRootPath = buildJsonInfo.projectRootPath; + arkProjectConfig.modulePathMap = buildJsonInfo.modulePathMap; + arkProjectConfig.isOhosTest = buildJsonInfo.isOhosTest; + arkProjectConfig.arkRouterMap = buildJsonInfo.routerMap; + // Collect bytecode har's declaration files entries include dynamic import and workers, use + // by es2abc for dependency resolution. + arkProjectConfig.declarationEntry = buildJsonInfo.declarationEntry; + if (buildJsonInfo.patchConfig) { + arkProjectConfig.oldMapFilePath = buildJsonInfo.patchConfig.oldMapFilePath; + } + if (checkAotConfig(projectConfig.compileMode, buildJsonInfo, + (error: string) => { share.throwArkTsCompilerError(error) })) { + arkProjectConfig.processTs = true; + arkProjectConfig.pandaMode = TS2ABC; + arkProjectConfig.anBuildOutPut = buildJsonInfo.anBuildOutPut; + arkProjectConfig.anBuildMode = buildJsonInfo.anBuildMode; + arkProjectConfig.apPath = buildJsonInfo.apPath; + } else { + arkProjectConfig.processTs = false; + arkProjectConfig.pandaMode = buildJsonInfo.pandaMode; + } + + if (projectConfig.compileMode === ESMODULE) { + arkProjectConfig.nodeModulesPath = buildJsonInfo.nodeModulesPath; + // harNameOhmMap stores har packages that are dependent for bytecode har when compile bytecode har. + arkProjectConfig.harNameOhmMap = buildJsonInfo.harNameOhmMap; + arkProjectConfig.hspNameOhmMap = buildJsonInfo.hspNameOhmMap; + projectConfig.packageDir = buildJsonInfo.packageManagerType === 'ohpm' ? OH_MODULES : NODE_MODULES; + } + if (buildJsonInfo.dynamicImportLibInfo) { + arkProjectConfig.dynamicImportLibInfo = buildJsonInfo.dynamicImportLibInfo; + } + if (buildJsonInfo.byteCodeHarInfo) { + arkProjectConfig.byteCodeHarInfo = buildJsonInfo.byteCodeHarInfo; + } + // Currently, the IDE does not have this configuration option, and cacheBytecodeHar is true by default. + arkProjectConfig.cacheBytecodeHar = !Object.prototype.hasOwnProperty.call(buildJsonInfo, 'cacheBytecodeHar') || + buildJsonInfo.cacheBytecodeHar; + } + if (projectConfig.aceManifestPath && fs.existsSync(projectConfig.aceManifestPath)) { + const manifestJsonInfo = JSON.parse(fs.readFileSync(projectConfig.aceManifestPath).toString()); + if (manifestJsonInfo.minPlatformVersion) { + arkProjectConfig.minPlatformVersion = manifestJsonInfo.minPlatformVersion; + } + } + if (projectConfig.aceModuleJsonPath && fs.existsSync(projectConfig.aceModuleJsonPath)) { + const moduleJsonInfo = JSON.parse(fs.readFileSync(projectConfig.aceModuleJsonPath).toString()); + if (moduleJsonInfo.app.minAPIVersion) { + arkProjectConfig.minPlatformVersion = moduleJsonInfo.app.minAPIVersion; + } + if (moduleJsonInfo.module) { + arkProjectConfig.moduleName = moduleJsonInfo.module.name; + } + if (moduleJsonInfo.app) { + arkProjectConfig.bundleName = moduleJsonInfo.app.bundleName; + } + } + + // Hotreload attributes are initialized by arkui in main.js, just copy them. + arkProjectConfig.hotReload = mainProjectConfig.hotReload; + arkProjectConfig.coldReload = mainProjectConfig.coldReload; + arkProjectConfig.isFirstBuild = mainProjectConfig.isFirstBuild; + arkProjectConfig.patchAbcPath = mainProjectConfig.patchAbcPath; + arkProjectConfig.changedFileList = mainProjectConfig.changedFileList; + + if (mainProjectConfig.es2abcCompileTsInAotMode || mainProjectConfig.es2abcCompileTsInNonAotMode) { + arkProjectConfig.pandaMode = mainProjectConfig.pandaMode; + arkProjectConfig.processTs = mainProjectConfig.processTs; + } + arkProjectConfig.compileMode = projectConfig.compileMode; + arkProjectConfig.entryObj = mainProjectConfig.entryObj; + arkProjectConfig.entryArrayForObf = mainProjectConfig.entryArrayForObf; + arkProjectConfig.cardEntryObj = mainProjectConfig.cardEntryObj; + if (mainProjectConfig.updateVersionInfo) { + arkProjectConfig.updateVersionInfo = mainProjectConfig.updateVersionInfo; + } + if (!isDebug(projectConfig)) { + arkProjectConfig.useTsHar = mainProjectConfig.useTsHar; + startFilesEvent(EventList.OBFUSCATION_INITIALIZATION, performancePrinter.timeSumPrinter); + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.INIT_ARK_PROJECT_CONFIG); + MemoryMonitor.stopRecordStage(recordInfo); + initObfLogger(share); + initObfuscationConfig(projectConfig, arkProjectConfig, printObfLogger); + endFilesEvent(EventList.OBFUSCATION_INITIALIZATION, performancePrinter.timeSumPrinter); + } else { + // Set performance printer to undefined in case we cannot disable it without obfuscation initialization + blockPrinter(); + } + return arkProjectConfig; +} + +function initTerserConfig(projectConfig: any, logger: any, mergedObConfig: MergedConfig, isHarCompiled: boolean): any { + const isCompact = projectConfig.obfuscationOptions ? mergedObConfig.options.compact : isHarCompiled; + const minifyOptions = { + format: { + beautify: !isCompact, + indent_level: 2 + }, + compress: { + join_vars: false, + sequences: 0, + directives: false, + drop_console: mergedObConfig.options.removeLog + }, + mangle: { + reserved: mergedObConfig.reservedNames, + toplevel: mergedObConfig.options.enableToplevelObfuscation + } + }; + const applyNameCache: string | undefined = mergedObConfig.options.applyNameCache; + if (applyNameCache && applyNameCache.length > 0) { + if (fs.existsSync(applyNameCache)) { + minifyOptions.nameCache = JSON.parse(fs.readFileSync(applyNameCache, 'utf-8')); + } else { + logger.error(`ArkTS:ERROR Namecache file ${applyNameCache} does not exist`); + } + } else { + if (projectConfig.obfuscationOptions && projectConfig.obfuscationOptions.obfuscationCacheDir) { + const defaultNameCachePath: string = path.join(projectConfig.obfuscationOptions.obfuscationCacheDir, 'nameCache.json'); + if (fs.existsSync(defaultNameCachePath)) { + minifyOptions.nameCache = JSON.parse(fs.readFileSync(defaultNameCachePath, 'utf-8')); + } else { + minifyOptions.nameCache = {}; + } + } + } + + if (mergedObConfig.options.enablePropertyObfuscation) { + minifyOptions.mangle.properties = { + reserved: mergedObConfig.reservedPropertyNames, + keep_quoted: !mergedObConfig.options.enableStringPropertyObfuscation + }; + } + return minifyOptions; +} + +// Scan the source code of project and libraries to collect whitelists. +export function readProjectAndLibsSource(allFiles: Set, mergedObConfig: MergedConfig, arkObfuscator: ArkObfuscator, isHarCompiled: boolean, + keepFilesAndDependencies: Set): void { + if (mergedObConfig?.options === undefined || mergedObConfig.options.disableObfuscation || allFiles.size === 0) { + return; + } + const obfOptions = mergedObConfig.options; + let projectAndLibs: ReseverdSetForArkguard = readProjectPropertiesByCollectedPaths(allFiles, + { + mNameObfuscation: { + mEnable: true, + mReservedProperties: [], + mRenameProperties: obfOptions.enablePropertyObfuscation, + mKeepStringProperty: !obfOptions.enableStringPropertyObfuscation, + mEnableAtKeep: obfOptions.enableAtKeep + }, + mExportObfuscation: obfOptions.enableExportObfuscation, + mKeepFileSourceCode: { + mKeepSourceOfPaths: new Set(), + mkeepFilesAndDependencies: keepFilesAndDependencies, + } + }, isHarCompiled); + if (obfOptions.enablePropertyObfuscation) { + arkObfuscator.addReservedSetForPropertyObf(projectAndLibs); + } + if (obfOptions.enableExportObfuscation) { + arkObfuscator.addReservedSetForDefaultObf(projectAndLibs); + } + if (obfOptions.enableAtKeep) { + // emit atKeep names and consumer configs + arkObfuscator.obfConfigResolver.emitConsumerConfigFiles(); + } +} + +function processPlatformInfo(arkConfig: ArkConfig): void { + const arkPlatformPath: string = getArkBuildDir(arkConfig.arkRootPath); + if (isWindows()) { + arkConfig.es2abcPath = path.join(arkPlatformPath, 'bin', 'es2abc.exe'); + arkConfig.ts2abcPath = path.join(arkPlatformPath, 'src', 'index.js'); + arkConfig.mergeAbcPath = path.join(arkPlatformPath, 'bin', 'merge_abc.exe'); + arkConfig.js2abcPath = path.join(arkPlatformPath, 'bin', 'js2abc.exe'); + arkConfig.aotCompilerPath = path.join(arkPlatformPath, 'bin', 'ark_aot_compiler.exe'); + return; + } + if (isLinux() || isMac()) { + arkConfig.es2abcPath = path.join(arkPlatformPath, 'bin', 'es2abc'); + arkConfig.ts2abcPath = path.join(arkPlatformPath, 'src', 'index.js'); + arkConfig.mergeAbcPath = path.join(arkPlatformPath, 'bin', 'merge_abc'); + arkConfig.js2abcPath = path.join(arkPlatformPath, 'bin', 'js2abc'); + arkConfig.aotCompilerPath = path.join(arkPlatformPath, 'bin', 'ark_aot_compiler'); + return; + } + if (isHarmonyOs()) { + arkConfig.es2abcPath = path.join(arkPlatformPath, 'bin', 'es2abc'); + return; + } +} + +function processCompatibleVersion(projectConfig: Object, arkConfig: ArkConfig): void { + const platformPath: string = getArkBuildDir(arkConfig.arkRootPath); + if (projectConfig.minPlatformVersion && projectConfig.minPlatformVersion.toString() === '8') { + // use ts2abc to compile apps with 'CompatibleSdkVersion' set to 8 + arkConfig.ts2abcPath = path.join(platformPath, 'legacy_api8', 'src', 'index.js'); + projectConfig.pandaMode = TS2ABC; + } +} + +export const utProcessArkConfig = { + processCompatibleVersion, + initTerserConfig +}; diff --git a/compiler/src/interop/src/fast_build/ark_compiler/error_code.ts b/compiler/src/interop/src/fast_build/ark_compiler/error_code.ts new file mode 100644 index 0000000000000000000000000000000000000000..08cc8ebe8ae25528ac4758eb439ec5077173603f --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/error_code.ts @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { es2abcErrorReferences } from './url_config.json'; + +export enum SubsystemCode { + ETS2BUNDLE = '103', + ABC2PROGRAM = '104', + ES2ABC = '107', +} + +export enum ErrorCode { + // INTERNAL ERRORS + ETS2BUNDLE_INTERNAL_UNABLE_TO_RETRIEVE_SOURCE_CODE_FROM_SUMMARY = '10310001', + ETS2BUNDLE_INTERNAL_UNABLE_TO_GENERATE_CACHE_SOURCE_FILE = '10310002', + ETS2BUNDLE_INTERNAL_UNABLE_TO_RETRIEVE_PACKAGE_CACHE_IN_INCREMENTAL_BUILD = '10310003', + ETS2BUNDLE_INTERNAL_INVALID_COMPILE_MODE = '10310004', + ETS2BUNDLE_INTERNAL_HASH_JSON_FILE_GENERATION_MISSING_PATHS = '10310005', + ETS2BUNDLE_INTERNAL_INCREMENTAL_BUILD_MISSING_CACHE_ABC_FILE_PATH = '10310006', + ETS2BUNDLE_INTERNAL_COLD_RELOAD_FAILED_INCORRECT_SYMBOL_MAP_CONFIG = '10310007', + ETS2BUNDLE_INTERNAL_HOT_RELOAD_FAILED_INCORRECT_SYMBOL_MAP_CONFIG = '10310008', + ETS2BUNDLE_INTERNAL_MODULE_INFO_NOT_FOUND = '10310009', + ETS2BUNDLE_INTERNAL_META_INFO_NOT_FOUND = '10310010', + ETS2BUNDLE_INTERNAL_MOCK_CONFIG_KEY_TO_OHM_URL_CONVERSION_FAILED = '10310011', + ETS2BUNDLE_INTERNAL_GET_MODULE_INFO_FAILED = '10310012', + ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META = '10310013', + ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META_PKG_PATH = '10310014', + ETS2BUNDLE_INTERNAL_WRITE_SOURCE_MAP_FAILED = '10310015', + ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_PKG_CONTENT_INFO = '10310016', + ETS2BUNDLE_INTERNAL_PACKAGE_NOT_FOUND_IN_CONTEXT_INFO = '10310017', + ETS2BUNDLE_INTERNAL_PACKAGE_ENTRY_FILE_NOT_FOUND = '10310018', + ETS2BUNDLE_INTERNAL_ARKGUARD_OBFUSCATION_FAILED = '10310019', + ETS2BUNDLE_INTERNAL_SOURCE_CODE_OBFUSCATION_FAILED = '10310020', + ETS2BUNDLE_INTERNAL_ES2ABC_SUBPROCESS_START_FAILED = '10310021', + ETS2BUNDLE_INTERNAL_EXECUTE_ES2ABC_WITH_ASYNC_HANDLER_FAILED = '10310022', + ETS2BUNDLE_INTERNAL_FAILED_TO_FIND_GLUD_CODE = '10310023', + ETS2BUNDLE_INTERNAL_WRONG_MODULE_NAME_FROM_ACEMODULEJSON = '10310024', + ETS2BUNDLE_INTERNAL_MISSING_BRIDGECODE_PATH_INFO = '10310025', + + // EXTERNAL ERRORS + ETS2BUNDLE_EXTERNAL_FORBIDDEN_IMPORT_ARKTS_FILE = '10311001', + ETS2BUNDLE_EXTERNAL_FAILED_TO_RESOLVE_OHM_URL = '10311002', + ETS2BUNDLE_EXTERNAL_KIT_CONFIG_FILE_NOT_FOUND = '10311003', + ETS2BUNDLE_EXTERNAL_LAZY_IMPORT_NOT_ALLOWED_WITH_KIT = '10311004', // Deprecated + ETS2BUNDLE_EXTERNAL_IDENTIFIER_IMPORT_NOT_ALLOWED_IN_TS_FILE = '10311005', + ETS2BUNDLE_EXTERNAL_IMPORT_NAME_NOT_EXPORTED_FROM_KIT = '10311006', + ETS2BUNDLE_EXTERNAL_KIT_NAMESPACE_IMPORT_EXPORT_NOT_SUPPORTED = '10311007', + ETS2BUNDLE_EXTERNAL_EMPTY_IMPORT_NOT_ALLOWED_WITH_KIT = '10311008', + ETS2BUNDLE_EXTERNAL_ES2ABC_EXECUTION_FAILED = '10311009', + ETS2BUNDLE_EXTERNAL_LAZY_IMPORT_RE_EXPORT_ERROR = '10311010', + ETS2BUNDLE_EXTERNAL_DUPLICATE_FILE_NAMES_ERROR = '10311011', + // INTEROPTRANSFORMER ERROR CODE + ETS2BUNDLE_EXTERNAL_CLASS_HAS_NO_CONSTRUCTOR_WITHOUT_ARGS = '10311012', + ETS2BUNDLE_EXTERNAL_UNION_TYPE_AMBIGUITY = '10311013', + ETS2BUNDLE_EXTERNAL_ALIAS_CONFIG_FORMAT_INVALID = '10311014', + ETS2BUNDLE_EXTERNAL_GET_LANGUAGE_VERSION_FAILED = '10311015', + ETS2BUNDLE_EXTERNAL_COLLECT_INTEROP_INFO_FAILED = '10311015', + + // CONSTANTS FOR ES2ABC ERROR CODE + ES2ABC_SYNTAX_ERROR_ERROR_CODE = '10705000', + ES2ABC_PATCH_FIX_ERROR_ERROR_CODE = '10706001' +} + +// DESCRIPTION +export const ArkTSInternalErrorDescription: string = 'ArkTS: INTERNAL ERROR'; +export const ArkTSErrorDescription: string = 'ArkTS: ERROR'; + +// CONSTANTS FOR ES2ABC ERROR CODE +// ES2ABC_SYNTAX_ERROR_ERROR_CODE 10705000 +export const ES2ABC_SYNTAX_ERROR_PREFIX = 'SyntaxError: '; +export const ES2ABC_SYNTAX_ERROR_DESCRIPTION = 'Syntax Error'; +// ES2ABC_PATCH_FIX_ERROR_ERROR_CODE 10706001 +export const ES2ABC_PATCH_FIX_ERROR_PREFIX = '[Patch] '; +export const ES2ABC_PATCH_FIX_ERROR_DESCRIPTION = 'Unsupported Change in Hot Reload'; +export const ES2ABC_PATCH_FIX_ERROR_ACTION = 'For more details on hot reload specification, ' + + `please refer to ${es2abcErrorReferences.harmonyOSGuideHotReload}.`; + +export const ES2ABC_ERROR_MAPPING: Record = { + [ES2ABC_SYNTAX_ERROR_PREFIX]: { + code: ErrorCode.ES2ABC_SYNTAX_ERROR_ERROR_CODE, + description: ES2ABC_SYNTAX_ERROR_DESCRIPTION, + solutions: [], + }, + [ES2ABC_PATCH_FIX_ERROR_PREFIX]: { + code: ErrorCode.ES2ABC_PATCH_FIX_ERROR_ERROR_CODE, + description: ES2ABC_PATCH_FIX_ERROR_DESCRIPTION, + solutions: [ES2ABC_PATCH_FIX_ERROR_ACTION], + }, +}; \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/generate_bundle_abc.ts b/compiler/src/interop/src/fast_build/ark_compiler/generate_bundle_abc.ts new file mode 100644 index 0000000000000000000000000000000000000000..a498f9d5f590dc2a2772a49d42b486c6252d0faf --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/generate_bundle_abc.ts @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 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 { JSBUNDLE } from './common/ark_define'; +import { BundlePreviewMode } from './bundle/bundle_preview_mode'; +import { BundleBuildMode } from './bundle/bundle_build_mode'; + +/** + * rollup generatebundle hook + * @param {rollup OutputOptions} options + * @param {rollup [fileName: string]: AssetInfo | ChunkInfo} bundle + * @param {boolean} isWrite + */ +export function generateBundleAbc(options: any, bundle: any, isWrite: boolean) { + if (bundle === null || this.share.projectConfig.compileMode !== JSBUNDLE) { + return; + } + + generateAbc(this, bundle); +} + +function generateAbc(rollupObject: any, rollupBundleFileSet: any) { + if (rollupObject.share.projectConfig.watchMode === 'true') { + const bundlePreviewMode: BundlePreviewMode = new BundlePreviewMode(rollupObject, rollupBundleFileSet); + bundlePreviewMode.generateAbc(); + } else { + const bundleBuildMode: BundleBuildMode = new BundleBuildMode(rollupObject, rollupBundleFileSet); + bundleBuildMode.generateAbc(); + } +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/generate_module_abc.ts b/compiler/src/interop/src/fast_build/ark_compiler/generate_module_abc.ts new file mode 100644 index 0000000000000000000000000000000000000000..4d3caf0a9570cc3bb41e2849641c71bb526acd6e --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/generate_module_abc.ts @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2023 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 { ESMODULE } from './common/ark_define'; +import { ModuleBuildMode } from './module/module_build_mode'; +import { ModuleColdreloadMode } from './module/module_coldreload_mode'; +import { ModuleHotfixMode } from './module/module_hotfix_mode'; +import { ModuleHotreloadMode } from './module/module_hotreload_mode'; +import { ModulePreviewMode } from './module/module_preview_mode'; +import { ModuleSourceFile } from './module/module_source_file'; +import { + getHookEventFactory, + createAndStartEvent, + stopEvent +} from '../../ark_utils'; +import type { ModuleMode } from './module/module_mode'; +import { SourceMapGenerator } from './generate_sourcemap'; + +let moduleMode: ModuleMode = null; + +export async function generateModuleAbc(error) { + const hookEventFactory = getHookEventFactory(this.share, 'genAbc', 'buildEnd'); + if (error) { + // When error thrown in previous plugins, rollup will catch and call buildEnd plugin. + // Stop generate abc if error exists + return; + } + if (this.share.projectConfig.compileMode === ESMODULE) { + if (this.share.projectConfig.singleFileEmit) { + if (ModuleSourceFile.needProcessMock) { + ModuleSourceFile.generateMockConfigFile(this); + } + } else { + await ModuleSourceFile.processModuleSourceFiles(this, hookEventFactory); + } + if (this.share.projectConfig.compileHar) { + SourceMapGenerator.getInstance().buildModuleSourceMapInfo(hookEventFactory); + // compileHar: compile har of project, which convert .ets to .d.ts and js + if (!this.share.projectConfig.byteCodeHar) { + return; + } + } + generateAbc(this, hookEventFactory); + } +} + +function generateAbc(rollupObject: Object, parentEvent: Object): void { + const eventGenerateAbc = createAndStartEvent(parentEvent, 'generate abc'); + if (rollupObject.share.projectConfig.watchMode !== 'true') { + if (rollupObject.share.arkProjectConfig.coldReload) { + const moduleColdreloadMode: ModuleColdreloadMode = new ModuleColdreloadMode(rollupObject); + moduleColdreloadMode.generateAbc(rollupObject, eventGenerateAbc); + moduleMode = moduleColdreloadMode; + } else if (rollupObject.share.arkProjectConfig.hotReload) { + // If file changes are monitored by the IDE, rollup is not started in watch mode, + // so rollupObject.share.projectConfig.watchMode is not true. Hotreload in this mode + // supports scenarios where entry depends on HAR and HSP. + const moduleHotreloadMode: ModuleHotreloadMode = new ModuleHotreloadMode(rollupObject); + moduleHotreloadMode.generateAbc(rollupObject, eventGenerateAbc); + moduleMode = moduleHotreloadMode; + } else { + const moduleBuildMode: ModuleBuildMode = new ModuleBuildMode(rollupObject); + moduleBuildMode.generateAbc(rollupObject, eventGenerateAbc); + moduleMode = moduleBuildMode; + } + } else if (rollupObject.share.arkProjectConfig.hotReload) { + // If file changes are monitored by rollup, rollup must be started in watch mode, + // so rollupObject.share.projectConfig.watchMode needs to be true. Hotreload in this mode + // does not support scenarios where entry depends on HSP. + const moduleHotreloadMode: ModuleHotreloadMode = new ModuleHotreloadMode(rollupObject); + moduleHotreloadMode.generateAbc(rollupObject, eventGenerateAbc); + moduleMode = moduleHotreloadMode; + } else if (rollupObject.share.arkProjectConfig.hotFix) { + const moduleHotfixMode: ModuleHotfixMode = new ModuleHotfixMode(rollupObject); + moduleHotfixMode.generateAbc(rollupObject, eventGenerateAbc); + moduleMode = moduleHotfixMode; + } else { + const modulePreviewMode: ModulePreviewMode = new ModulePreviewMode(rollupObject); + modulePreviewMode.generateAbc(rollupObject, eventGenerateAbc); + moduleMode = modulePreviewMode; + } + stopEvent(eventGenerateAbc); +} + +export function cleanModuleMode(): void { + if (moduleMode) { + moduleMode.triggerAsync = null; + moduleMode.triggerEndSignal = null; + moduleMode = null; + } +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/generate_sourcemap.ts b/compiler/src/interop/src/fast_build/ark_compiler/generate_sourcemap.ts new file mode 100644 index 0000000000000000000000000000000000000000..26c05860f02425d84c9fc455466bd8843fd946e4 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/generate_sourcemap.ts @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use rollupObject 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 path from 'path'; +import fs from 'fs'; +import { + createAndStartEvent, + stopEvent +} from '../../ark_utils'; +import { + EXTNAME_ETS, + EXTNAME_JS, + EXTNAME_TS, + EXTNAME_MJS, + EXTNAME_CJS, + GEN_ABC_PLUGIN_NAME, + SOURCEMAPS, + SOURCEMAPS_JSON, + yellow, + reset +} from "./common/ark_define"; +import { + changeFileExtension, + isCommonJsPluginVirtualFile, + isCurrentProjectFiles, + isDebug, + shouldETSOrTSFileTransformToJS +} from "./utils"; +import { + toUnixPath, + isPackageModulesFile, + getProjectRootPath +} from "../../utils"; +import { + handleObfuscatedFilePath, + mangleFilePath, + enableObfuscateFileName +} from './common/ob_config_resolver'; +import { MemoryMonitor } from '../meomry_monitor/rollup-plugin-memory-monitor'; +import { MemoryDefine } from '../meomry_monitor/memory_define'; +import { + ArkTSInternalErrorDescription, + ErrorCode +} from './error_code'; +import { + CommonLogger, + LogData, + LogDataFactory +} from './logger'; + +export class SourceMapGenerator { + private static instance: SourceMapGenerator | undefined = undefined; + private static rollupObject: Object | undefined; + + private projectConfig: Object; + private sourceMapPath: string; + private cacheSourceMapPath: string; + private triggerAsync: Object; + private triggerEndSignal: Object; + private sourceMaps: Object = {}; + private isNewSourceMap: boolean = true; + private keyCache: Map = new Map(); + private logger: CommonLogger; + + public sourceMapKeyMappingForObf: Map = new Map(); + + constructor(rollupObject: Object) { + this.projectConfig = Object.assign(rollupObject.share.arkProjectConfig, rollupObject.share.projectConfig); + this.sourceMapPath = this.getSourceMapSavePath(); + this.cacheSourceMapPath = path.join(this.projectConfig.cachePath, SOURCEMAPS_JSON); + this.triggerAsync = rollupObject.async; + this.triggerEndSignal = rollupObject.signal; + this.logger = CommonLogger.getInstance(rollupObject); + } + + static init(rollupObject: Object): void { + SourceMapGenerator.rollupObject = rollupObject; + SourceMapGenerator.instance = new SourceMapGenerator(SourceMapGenerator.rollupObject); + + // adapt compatibility with hvigor + if (!SourceMapGenerator.instance.projectConfig.entryPackageName || + !SourceMapGenerator.instance.projectConfig.entryModuleVersion) { + SourceMapGenerator.instance.isNewSourceMap = false; + } + } + + static getInstance(): SourceMapGenerator { + if (!SourceMapGenerator.instance) { + SourceMapGenerator.instance = new SourceMapGenerator(SourceMapGenerator.rollupObject); + } + return SourceMapGenerator.instance; + } + + //In window plateform, if receive path join by '/', should transform '/' to '\' + private getAdaptedModuleId(moduleId: string): string { + return moduleId.replace(/\//g, path.sep); + } + + private getPkgInfoByModuleId(moduleId: string, shouldObfuscateFileName: boolean = false): Object { + moduleId = this.getAdaptedModuleId(moduleId); + + const moduleInfo: Object = SourceMapGenerator.rollupObject.getModuleInfo(moduleId); + if (!moduleInfo) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_GET_MODULE_INFO_FAILED, + ArkTSInternalErrorDescription, + `Failed to get ModuleInfo, moduleId: ${moduleId}` + ); + this.logger.printErrorAndExit(errInfo); + } + const metaInfo: Object = moduleInfo['meta']; + if (!metaInfo) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META, + ArkTSInternalErrorDescription, + `Failed to get ModuleInfo properties 'meta', moduleId: ${moduleId}` + ); + this.logger.printErrorAndExit(errInfo); + } + const pkgPath = metaInfo['pkgPath']; + if (!pkgPath) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META_PKG_PATH, + ArkTSInternalErrorDescription, + `Failed to get ModuleInfo properties 'meta.pkgPath', moduleId: ${moduleId}` + ); + this.logger.printErrorAndExit(errInfo); + } + + const dependencyPkgInfo = metaInfo['dependencyPkgInfo']; + let middlePath = this.getIntermediateModuleId(moduleId, metaInfo).replace(pkgPath + path.sep, ''); + if (shouldObfuscateFileName) { + middlePath = mangleFilePath(middlePath); + } + return { + entry: { + name: this.projectConfig.entryPackageName, + version: this.projectConfig.entryModuleVersion + }, + dependency: dependencyPkgInfo ? { + name: dependencyPkgInfo['pkgName'], + version: dependencyPkgInfo['pkgVersion'] + } : undefined, + modulePath: toUnixPath(middlePath) + }; + } + + public setNewSoureMaps(isNewSourceMap: boolean): void { + this.isNewSourceMap = isNewSourceMap; + } + + public isNewSourceMaps(): boolean { + return this.isNewSourceMap; + } + + //generate sourcemap key, notice: moduleId is absolute path + public genKey(moduleId: string, shouldObfuscateFileName: boolean = false): string { + moduleId = this.getAdaptedModuleId(moduleId); + + let key: string = this.keyCache.get(moduleId); + if (key && !shouldObfuscateFileName) { + return key; + } + const pkgInfo = this.getPkgInfoByModuleId(moduleId, shouldObfuscateFileName); + if (pkgInfo.dependency) { + key = `${pkgInfo.entry.name}|${pkgInfo.dependency.name}|${pkgInfo.dependency.version}|${pkgInfo.modulePath}`; + } else { + key = `${pkgInfo.entry.name}|${pkgInfo.entry.name}|${pkgInfo.entry.version}|${pkgInfo.modulePath}`; + } + if (key && !shouldObfuscateFileName) { + this.keyCache.set(moduleId, key); + } + return key; + } + + private getSourceMapSavePath(): string { + if (this.projectConfig.compileHar && this.projectConfig.sourceMapDir && !this.projectConfig.byteCodeHar) { + return path.join(this.projectConfig.sourceMapDir, SOURCEMAPS); + } + return isDebug(this.projectConfig) ? path.join(this.projectConfig.aceModuleBuild, SOURCEMAPS) : + path.join(this.projectConfig.cachePath, SOURCEMAPS); + } + + public buildModuleSourceMapInfo(parentEvent: Object): void { + if (this.projectConfig.widgetCompile) { + return; + } + + const eventUpdateCachedSourceMaps = createAndStartEvent(parentEvent, 'update cached source maps'); + // If hap/hsp depends on bytecode har under debug mode, the source map of bytecode har need to be merged with + // source map of hap/hsp. + if (isDebug(this.projectConfig) && !this.projectConfig.byteCodeHar && !!this.projectConfig.byteCodeHarInfo) { + Object.keys(this.projectConfig.byteCodeHarInfo).forEach((packageName) => { + const sourceMapsPath = this.projectConfig.byteCodeHarInfo[packageName].sourceMapsPath; + if (!sourceMapsPath && !!this.logger && !!this.logger.warn) { + this.logger.warn(yellow, `ArkTS:WARN Property 'sourceMapsPath' not found in '${packageName}'.`, reset); + } + if (!!sourceMapsPath) { + const bytecodeHarSourceMap = JSON.parse(fs.readFileSync(toUnixPath(sourceMapsPath)).toString()); + Object.assign(this.sourceMaps, bytecodeHarSourceMap); + } + }); + } + const updateSourceRecordInfo = MemoryMonitor.recordStage(MemoryDefine.UPDATE_SOURCE_MAPS); + const cacheSourceMapObject: Object = this.updateCachedSourceMaps(); + MemoryMonitor.stopRecordStage(updateSourceRecordInfo); + stopEvent(eventUpdateCachedSourceMaps); + + this.triggerAsync(() => { + const eventWriteFile = createAndStartEvent(parentEvent, 'write source map (async)', true); + fs.writeFile(this.sourceMapPath, JSON.stringify(cacheSourceMapObject, null, 2), 'utf-8', (err) => { + if (err) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_WRITE_SOURCE_MAP_FAILED, + ArkTSInternalErrorDescription, + `Failed to write sourceMaps. ${err.message}`, + this.sourceMapPath + ); + this.logger.printErrorAndExit(errInfo); + } + fs.copyFileSync(this.sourceMapPath, this.cacheSourceMapPath); + stopEvent(eventWriteFile, true); + this.triggerEndSignal(); + }); + }); + } + + //update cache sourcemap object + public updateCachedSourceMaps(): Object { + if (!this.isNewSourceMap) { + this.modifySourceMapKeyToCachePath(this.sourceMaps); + } + + let cacheSourceMapObject: Object; + + if (!fs.existsSync(this.cacheSourceMapPath)) { + cacheSourceMapObject = this.sourceMaps; + } else { + cacheSourceMapObject = JSON.parse(fs.readFileSync(this.cacheSourceMapPath).toString()); + + // remove unused source files's sourceMap + let unusedFiles = []; + let compileFileList: Set = new Set(); + for (let moduleId of SourceMapGenerator.rollupObject.getModuleIds()) { + // exclude .dts|.d.ets file + if (isCommonJsPluginVirtualFile(moduleId) || !isCurrentProjectFiles(moduleId, this.projectConfig)) { + continue; + } + + if (this.isNewSourceMap) { + const isPackageModules = isPackageModulesFile(moduleId, this.projectConfig); + if (enableObfuscateFileName(isPackageModules, this.projectConfig)){ + compileFileList.add(this.genKey(moduleId, true)); + } else { + compileFileList.add(this.genKey(moduleId)); + } + continue; + } + + // adapt compatibilty with hvigor + const projectRootPath = getProjectRootPath(moduleId, this.projectConfig, this.projectConfig?.rootPathSet); + let cacheModuleId = this.getIntermediateModuleId(toUnixPath(moduleId)) + .replace(toUnixPath(projectRootPath), toUnixPath(this.projectConfig.cachePath)); + + const isPackageModules = isPackageModulesFile(moduleId, this.projectConfig); + if (enableObfuscateFileName(isPackageModules, this.projectConfig)) { + compileFileList.add(mangleFilePath(cacheModuleId)); + } else { + compileFileList.add(cacheModuleId); + } + } + + Object.keys(cacheSourceMapObject).forEach(key => { + let newkeyOrOldCachePath = key; + if (!this.isNewSourceMap) { + newkeyOrOldCachePath = toUnixPath(path.join(this.projectConfig.projectRootPath, key)); + } + if (!compileFileList.has(newkeyOrOldCachePath)) { + unusedFiles.push(key); + } + }); + unusedFiles.forEach(file => { + delete cacheSourceMapObject[file]; + }) + + // update sourceMap + Object.keys(this.sourceMaps).forEach(key => { + cacheSourceMapObject[key] = this.sourceMaps[key]; + }); + } + // update the key for filename obfuscation + for (let [key, newKey] of this.sourceMapKeyMappingForObf) { + this.updateSourceMapKeyWithObf(cacheSourceMapObject, key, newKey); + } + return cacheSourceMapObject; + } + + public getSourceMaps(): Object { + return this.sourceMaps; + } + + public getSourceMap(moduleId: string): Object { + return this.getSpecifySourceMap(this.sourceMaps, moduleId); + } + + //get specify sourcemap, allow receive param sourcemap + public getSpecifySourceMap(specifySourceMap: Object, moduleId: string): Object { + const key = this.isNewSourceMap ? this.genKey(moduleId) : moduleId; + if (specifySourceMap && specifySourceMap[key]) { + return specifySourceMap[key]; + } + return undefined; + } + + public updateSourceMap(moduleId: string, map: Object) { + if (!this.sourceMaps) { + this.sourceMaps = {}; + } + this.updateSpecifySourceMap(this.sourceMaps, moduleId, map); + } + + //update specify sourcemap, allow receive param sourcemap + public updateSpecifySourceMap(specifySourceMap: Object, moduleId: string, sourceMap: Object) { + const key = this.isNewSourceMap ? this.genKey(moduleId) : moduleId; + specifySourceMap[key] = sourceMap; + } + + public fillSourceMapPackageInfo(moduleId: string, sourcemap: Object) { + if (!this.isNewSourceMap) { + return; + } + + const pkgInfo = this.getPkgInfoByModuleId(moduleId); + sourcemap['entry-package-info'] = `${pkgInfo.entry.name}|${pkgInfo.entry.version}`; + if (pkgInfo.dependency) { + sourcemap['package-info'] = `${pkgInfo.dependency.name}|${pkgInfo.dependency.version}`; + } + } + + private getIntermediateModuleId(moduleId: string, metaInfo?: Object): string { + let extName: string = ""; + switch (path.extname(moduleId)) { + case EXTNAME_ETS: { + extName = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig, metaInfo) ? EXTNAME_JS : EXTNAME_TS; + break; + } + case EXTNAME_TS: { + extName = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig, metaInfo) ? EXTNAME_JS : ''; + break; + } + case EXTNAME_JS: + case EXTNAME_MJS: + case EXTNAME_CJS: { + extName = (moduleId.endsWith(EXTNAME_MJS) || moduleId.endsWith(EXTNAME_CJS)) ? EXTNAME_JS : ''; + break; + } + default: + break; + } + if (extName.length !== 0) { + return changeFileExtension(moduleId, extName); + } + return moduleId; + } + + public setSourceMapPath(path: string): void { + this.sourceMapPath = path; + } + + public modifySourceMapKeyToCachePath(sourceMap: object): void { + const projectConfig: object = this.projectConfig; + + // modify source map keys to keep IDE tools right + const relativeCachePath: string = toUnixPath(projectConfig.cachePath.replace( + projectConfig.projectRootPath + path.sep, '')); + Object.keys(sourceMap).forEach(key => { + let newKey: string = relativeCachePath + '/' + key; + if (!newKey.endsWith(EXTNAME_JS)) { + const moduleId: string = this.projectConfig.projectRootPath + path.sep + key; + const extName: string = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig) ? EXTNAME_JS : EXTNAME_TS; + newKey = changeFileExtension(newKey, extName); + } + const isOhModules = key.startsWith('oh_modules'); + newKey = handleObfuscatedFilePath(newKey, isOhModules, this.projectConfig); + sourceMap[newKey] = sourceMap[key]; + delete sourceMap[key]; + }); + } + + public static cleanSourceMapObject(): void { + if (this.instance) { + this.instance.keyCache.clear(); + this.instance.sourceMaps = undefined; + this.instance = undefined; + } + if (this.rollupObject) { + this.rollupObject = undefined; + } + } + + private updateSourceMapKeyWithObf(specifySourceMap: Object, key: string, newKey: string): void { + if (!specifySourceMap.hasOwnProperty(key) || key === newKey) { + return; + } + specifySourceMap[newKey] = specifySourceMap[key]; + delete specifySourceMap[key]; + } + + public saveKeyMappingForObfFileName(originalFilePath: string): void { + this.sourceMapKeyMappingForObf.set(this.genKey(originalFilePath), this.genKey(originalFilePath, true)); + } + + //use by UT + static initInstance(rollupObject: Object): SourceMapGenerator { + if (!SourceMapGenerator.instance) { + SourceMapGenerator.init(rollupObject); + } + return SourceMapGenerator.getInstance(); + } +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/interop/interop_manager.ts b/compiler/src/interop/src/fast_build/ark_compiler/interop/interop_manager.ts new file mode 100644 index 0000000000000000000000000000000000000000..d2db050a21552afa9253e6c68f00eedd22348f67 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/interop/interop_manager.ts @@ -0,0 +1,547 @@ +/* + * 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 fs from 'fs'; +import path from 'path'; + +import { + projectConfig, + sdkConfigs +} from '../../../../main'; +import { toUnixPath } from '../../../utils'; +import { + ArkTSEvolutionModule, + FileInfo, + AliasConfig +} from './type'; +import { + hasExistingPaths, + isSubPathOf +} from '../utils'; +import { + CommonLogger, + LogData, + LogDataFactory +} from '../logger'; +import { + ArkTSErrorDescription, + ArkTSInternalErrorDescription, + ErrorCode +} from '../error_code'; +import { EXTNAME_TS } from '../common/ark_define'; +import { + ARKTS_1_1, + ARKTS_1_2, + ARKTS_HYBRID +} from './pre_define'; + +export let entryFileLanguageInfo = new Map(); + +export class FileManager { + private static instance: FileManager | undefined = undefined; + + static arkTSModuleMap: Map = new Map(); + static aliasConfig: Map> = new Map(); + static dynamicLibPath: Set = new Set(); + static staticSDKDeclPath: Set = new Set(); + static staticSDKGlueCodePath: Set = new Set(); + static mixCompile: boolean = false; + static glueCodeFileInfos: Map = new Map(); + static isInteropSDKEnabled: boolean = false; + static sharedObj: Object | undefined = undefined; + + private constructor() { } + + public static init( + dependentModuleMap: Map, + aliasPaths?: Map, + dynamicSDKPath?: Set, + staticSDKDeclPath?: Set, + staticSDKGlueCodePath?: Set + ): void { + if (FileManager.instance === undefined) { + FileManager.instance = new FileManager(); + FileManager.initLanguageVersionFromDependentModuleMap(dependentModuleMap); + FileManager.initAliasConfig(aliasPaths); + FileManager.initSDK(dynamicSDKPath, staticSDKDeclPath, staticSDKGlueCodePath); + } + } + + public static initForTest( + dependentModuleMap: Map, + aliasPaths: Map, + dynamicSDKPath?: Set, + staticSDKDeclPath?: Set, + staticSDKGlueCodePath?: Set + ): void { + if (FileManager.instance === undefined) { + FileManager.instance = new FileManager(); + FileManager.initLanguageVersionFromDependentModuleMap(dependentModuleMap); + FileManager.initAliasConfig(aliasPaths); + FileManager.initSDK(dynamicSDKPath, staticSDKDeclPath, staticSDKGlueCodePath, false); + } + } + + public static getInstance(): FileManager { + if (!FileManager.instance) { + FileManager.instance = new FileManager(); + } + return FileManager.instance; + } + + public static setRollUpObj(shared: Object): void { + FileManager.sharedObj = shared; + } + + public static setMixCompile(mixCompile: boolean): void { + FileManager.mixCompile = mixCompile; + } + + private static initLanguageVersionFromDependentModuleMap( + dependentModuleMap: Map + ): void { + const convertedMap = new Map(); + + for (const [key, module] of dependentModuleMap) { + module.dynamicFiles = module.dynamicFiles?.map(toUnixPath); + module.staticFiles = module.staticFiles?.map(toUnixPath); + const convertedModule: ArkTSEvolutionModule = { + ...module, + modulePath: toUnixPath(module.modulePath), + declgenV1OutPath: module.declgenV1OutPath ? toUnixPath(module.declgenV1OutPath) : undefined, + declgenV2OutPath: module.declgenV2OutPath ? toUnixPath(module.declgenV2OutPath) : undefined, + declgenBridgeCodePath: module.declgenBridgeCodePath ? toUnixPath(module.declgenBridgeCodePath) : undefined, + declFilesPath: module.declFilesPath ? toUnixPath(module.declFilesPath) : undefined, + }; + convertedMap.set(key, convertedModule); + } + + this.arkTSModuleMap = convertedMap; + } + + private static initAliasConfig(aliasPaths: Map): void { + if (!aliasPaths) { + return; + } + + for (const [pkgName, filePath] of aliasPaths) { + const rawContent = fs.readFileSync(filePath, 'utf-8'); + const jsonData = JSON.parse(rawContent); + const pkgAliasMap = this.parseAliasJson(pkgName, jsonData); + this.aliasConfig.set(pkgName, pkgAliasMap); + } + } + + private static parseAliasJson(pkgName: string, jsonData: any): Map { + const map = new Map(); + + for (const [aliasKey, config] of Object.entries(jsonData)) { + if (!this.isValidAliasConfig(config)) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_ALIAS_CONFIG_FORMAT_INVALID, + ArkTSErrorDescription, + 'Invalid alias config format detected.', + `Package: ${pkgName}`, + ['Please ensure each alias entry contains "originalAPIName" and "isStatic" fields.'] + ); + + FileManager.logError(errInfo); + } + + map.set(aliasKey, { + originalAPIName: config.originalAPIName, + isStatic: config.isStatic + }); + } + + return map; + } + + private static isValidAliasConfig(config: any): config is AliasConfig { + return typeof config === 'object' && + config !== null && + 'originalAPIName' in config && + 'isStatic' in config; + } + + private static initSDK( + dynamicSDKPath?: Set, + staticSDKBaseUrl?: Set, + staticSDKGlueCodePaths?: Set, + checkFileExist: boolean = true + ): void { + if (dynamicSDKPath) { + for (const path of dynamicSDKPath) { + FileManager.dynamicLibPath.add(toUnixPath(path)); + } + } + const isStaticBaseValid = !staticSDKBaseUrl || hasExistingPaths(staticSDKBaseUrl); + const isGlueCodeValid = !staticSDKGlueCodePaths || hasExistingPaths(staticSDKGlueCodePaths); + FileManager.isInteropSDKEnabled = isStaticBaseValid && isGlueCodeValid; + if (!FileManager.isInteropSDKEnabled && checkFileExist) { + return; + } + if (staticSDKBaseUrl) { + for (const path of staticSDKBaseUrl) { + FileManager.staticSDKDeclPath.add(toUnixPath(path)); + } + } + if (staticSDKGlueCodePaths) { + for (const path of staticSDKGlueCodePaths) { + FileManager.staticSDKGlueCodePath.add(toUnixPath(path)); + } + } + } + + public static cleanFileManagerObject(): void { + if (this.instance) { + this.instance = undefined; + } + + FileManager.arkTSModuleMap?.clear(); + FileManager.dynamicLibPath?.clear(); + FileManager.staticSDKDeclPath?.clear(); + FileManager.staticSDKGlueCodePath?.clear(); + FileManager.glueCodeFileInfos?.clear(); + FileManager.aliasConfig?.clear(); + FileManager.mixCompile = false; + entryFileLanguageInfo.clear(); + } + + getLanguageVersionByFilePath(filePath: string): { + languageVersion: string, + pkgName: string + } | undefined { + const path = toUnixPath(filePath); + + const moduleMatch = FileManager.matchModulePath(path); + if (moduleMatch) { + return moduleMatch; + } + + const sdkMatch = FileManager.matchSDKPath(path); + if (sdkMatch) { + return sdkMatch; + } + const firstLine = readFirstLineSync(filePath); + if (firstLine.includes('use static')) { + return { + languageVersion: ARKTS_1_2, + pkgName: '' + }; + } + return { + languageVersion: ARKTS_1_1, + pkgName: '' + }; + } + + private static matchModulePath(path: string): { + languageVersion: string, + pkgName: string + } | undefined { + let matchedModuleInfo: ArkTSEvolutionModule; + + for (const [, moduleInfo] of FileManager.arkTSModuleMap) { + if (isSubPathOf(path, moduleInfo.modulePath)) { + matchedModuleInfo = moduleInfo; + break; + } + } + + if (!matchedModuleInfo) { + return undefined; + } + + const isHybrid = matchedModuleInfo.language === ARKTS_HYBRID; + const pkgName = matchedModuleInfo.packageName; + + if (!isHybrid) { + return { + languageVersion: matchedModuleInfo.language, + pkgName + }; + } + + const isDynamic = + matchedModuleInfo.dynamicFiles.includes(path) || + (matchedModuleInfo.declgenV2OutPath && isSubPathOf(path, matchedModuleInfo.declgenV2OutPath)); + + if (isDynamic) { + return { + languageVersion: ARKTS_1_1, + pkgName + }; + } + + const isStatic = + matchedModuleInfo.staticFiles.includes(path) || + (matchedModuleInfo.declgenV1OutPath && isSubPathOf(path, matchedModuleInfo.declgenV1OutPath)) || + (matchedModuleInfo.declgenBridgeCodePath && isSubPathOf(path, matchedModuleInfo.declgenBridgeCodePath)); + + if (isStatic) { + return { + languageVersion: ARKTS_1_2, + pkgName + }; + } + + return undefined; + } + + private static logError(error: LogData): void { + if (FileManager.sharedObj) { + CommonLogger.getInstance(FileManager.sharedObj).printErrorAndExit(error); + } else { + console.error(error.toString()); + } + } + + private static matchSDKPath(path: string): { + languageVersion: string, + pkgName: string + } | undefined { + const sdkMatches: [Set | undefined, string][] = [ + [FileManager.dynamicLibPath, ARKTS_1_1], + [FileManager.staticSDKDeclPath, ARKTS_1_2], + [FileManager.staticSDKGlueCodePath, ARKTS_1_2], + ]; + + for (const [paths, version] of sdkMatches) { + const isMatch = paths && Array.from(paths).some( + p => p && (isSubPathOf(path, p)) + ); + if (isMatch) { + return { languageVersion: version, pkgName: 'SDK' }; + } + } + return undefined; + } + + queryOriginApiName(moduleName: string, containingFile: string): AliasConfig { + if (!FileManager.mixCompile) { + return undefined; + } + if (!FileManager.isInteropSDKEnabled) { + return undefined; + } + const result = this.getLanguageVersionByFilePath(containingFile); + if (!result) { + return undefined; + } + + const alias = FileManager.aliasConfig.get(result.pkgName); + if (!alias) { + return undefined; + } + + return alias.get(moduleName); + } + + getGlueCodePathByModuleRequest(moduleRequest: string): { fullPath: string, basePath: string } | undefined { + const extensions = ['.ts', '.ets']; + for (const basePath of FileManager.staticSDKGlueCodePath) { + const fullPath = extensions + .map(ext => path.resolve(basePath, moduleRequest + ext)) + .find(fs.existsSync); + + if (fullPath) { + return { + fullPath: toUnixPath(fullPath), + basePath: toUnixPath(basePath) + }; + } + } + + return undefined; + } +} + +export function initFileManagerInRollup(share: Object): void { + if (!share.projectConfig.mixCompile) { + return; + } + + FileManager.mixCompile = true; + const sdkInfo = collectSDKInfo(share); + + FileManager.init( + share.projectConfig.dependentModuleMap, + share.projectConfig.aliasPaths, + sdkInfo.dynamicSDKPath, + sdkInfo.staticSDKInteropDecl, + sdkInfo.staticSDKGlueCodePath + ); + FileManager.setRollUpObj(share); +} + +export function collectSDKInfo(share: Object): { + dynamicSDKPath: Set, + staticSDKInteropDecl: Set, + staticSDKGlueCodePath: Set +} { + const dynamicSDKPath: Set = new Set(); + const staticInteroSDKBasePath = process.env.staticInteroSDKBasePath || + path.resolve(share.projectConfig.etsLoaderPath, '../../../ets1.2/build-tools/interop'); //??? + const staticSDKInteropDecl: Set = new Set([ + path.resolve(staticInteroSDKBasePath, './declarations/kits'), + path.resolve(staticInteroSDKBasePath, './declarations/api'), + path.resolve(staticInteroSDKBasePath, './declarations/arkts'), + ].map(toUnixPath)); + + const staticSDKGlueCodePath: Set = new Set([ + path.resolve(staticInteroSDKBasePath, './bridge/kits'), + path.resolve(staticInteroSDKBasePath, './bridge/api'), + path.resolve(staticInteroSDKBasePath, './bridge/arkts'), + ].map(toUnixPath)); + + const declarationsPath: string = path.resolve(share.projectConfig.etsLoaderPath, './declarations').replace(/\\/g, '/'); + const componentPath: string = path.resolve(share.projectConfig.etsLoaderPath, './components').replace(/\\/g, '/'); + const etsComponentPath: string = path.resolve(share.projectConfig.etsLoaderPath, '../../component').replace(/\\/g, '/'); + + if (process.env.externalApiPaths) { + const externalApiPaths = path.resolve(process.env.externalApiPaths, '../'); //??? + staticSDKGlueCodePath.add(path.resolve(externalApiPaths, './ets1.2/interop/bridge')); + staticSDKInteropDecl.add(path.resolve(externalApiPaths, './ets1.2/interop/declarations')); + } + + dynamicSDKPath.add(declarationsPath); + dynamicSDKPath.add(componentPath); + dynamicSDKPath.add(etsComponentPath); + dynamicSDKPath.add(toUnixPath(share.projectConfig.etsLoaderPath)); + sdkConfigs.forEach(({ apiPath }) => { + apiPath.forEach(path => { + dynamicSDKPath.add(toUnixPath(path)); + }); + }); + return { + dynamicSDKPath: dynamicSDKPath, + staticSDKInteropDecl: staticSDKInteropDecl, + staticSDKGlueCodePath: staticSDKGlueCodePath + }; +} + +function readFirstLineSync(filePath: string): string { + const buffer = fs.readFileSync(filePath, 'utf-8'); + const newlineIndex = buffer.indexOf('\n'); + if (newlineIndex === -1) { + return buffer.trim(); + } + return buffer.substring(0, newlineIndex).trim(); +} + +export function isBridgeCode(filePath: string, projectConfig: Object): boolean { + if (!projectConfig?.mixCompile) { + return false; + } + for (const [pkgName, dependentModuleInfo] of projectConfig.dependentModuleMap) { + if (isSubPathOf(filePath, dependentModuleInfo.declgenBridgeCodePath)) { + return true; + } + } + return false; +} + +export function isMixCompile(): boolean { + return process.env.mixCompile === 'true'; +} + +/** + * Delete the 1.2 part in abilityPagesFullPath. This array will be used in transform. + * The 1.2 source files will not participate in the 1.1 compilation process. + */ +export function processAbilityPagesFullPath(abilityPagesFullPath: Set): void { + if (!isMixCompile()) { + return; + } + + const extensions = ['.ts', '.ets']; + + for (const filePath of Array.from(abilityPagesFullPath)) { + let realPath: string | null = null; + + for (const ext of extensions) { + const candidate = filePath.endsWith(ext) ? filePath : filePath + ext; + if (fs.existsSync(candidate)) { + realPath = candidate; + break; + } + } + + if (!realPath) { + continue; + } + + const firstLine = readFirstLineSync(realPath); + if (firstLine.includes('use static')) { + abilityPagesFullPath.delete(filePath); + } + } +} + + +export function transformAbilityPages(abilityPath: string): boolean { + const entryBridgeCodePath = process.env.entryBridgeCodePath; + if (!entryBridgeCodePath) { + const errInfo = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_MISSING_BRIDGECODE_PATH_INFO, + ArkTSInternalErrorDescription, + `Missing entryBridgeCodePath` + ); + throw Error(errInfo.toString()); + } + if (!entryFileLanguageInfo?.get(abilityPath)) { + return false; + } + if (abilityPath.includes(':')) { + abilityPath = abilityPath.substring(0, abilityPath.lastIndexOf(':')); + } + const bridgeCodePath = path.join(entryBridgeCodePath, abilityPath + EXTNAME_TS); + if (fs.existsSync(bridgeCodePath)) { + projectConfig.entryObj[transformModuleNameToRelativePath(abilityPath)] = bridgeCodePath; + return true; + } + return false; +} + +function transformModuleNameToRelativePath(moduleName): string { + let defaultSourceRoot = 'src/main'; + const normalizedModuleName = moduleName.replace(/\\/g, '/'); + const normalizedRoot = defaultSourceRoot.replace(/\\/g, '/'); + + const rootIndex = normalizedModuleName.indexOf(`/${normalizedRoot}/`); + if (rootIndex === -1) { + const errInfo = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_WRONG_MODULE_NAME_FROM_ACEMODULEJSON, + ArkTSInternalErrorDescription, + `defaultSourceRoot '${defaultSourceRoot}' not found ` + + `when process moduleName '${moduleName}'` + ); + throw Error(errInfo.toString()); + } + + const relativePath = normalizedModuleName.slice(rootIndex + normalizedRoot.length + 1); + return './' + relativePath; +} + +export function getApiPathForInterop(apiDirs: string[], languageVersion: string): void { + if (languageVersion !== ARKTS_1_2) { + return; + } + + const staticPaths = [...FileManager.staticSDKDeclPath]; + apiDirs.unshift(...staticPaths); +} + diff --git a/compiler/src/interop/src/fast_build/ark_compiler/interop/pre_define.ts b/compiler/src/interop/src/fast_build/ark_compiler/interop/pre_define.ts new file mode 100644 index 0000000000000000000000000000000000000000..eaa908d4149f56a25a56bee5b66a7200743d75d5 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/interop/pre_define.ts @@ -0,0 +1,19 @@ +/* + * 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 ARKTS_1_2: string = '1.2'; +export const ARKTS_1_1: string = '1.1'; +export const ARKTS_1_0: string = '1.0'; +export const ARKTS_HYBRID: string = 'hybrid'; \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/interop/process_arkts_evolution.ts b/compiler/src/interop/src/fast_build/ark_compiler/interop/process_arkts_evolution.ts new file mode 100644 index 0000000000000000000000000000000000000000..7cf11fe98c789047b8d91337b4267025298e4a26 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/interop/process_arkts_evolution.ts @@ -0,0 +1,695 @@ +/* + * 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 fs from 'fs'; +import path from 'path'; +import ts from 'typescript'; + +import { getResolveModule } from '../../../ets_checker'; +import { + ARKTS_1_0, + ARKTS_1_1, + ARKTS_1_2, + ARKTS_HYBRID +} from './pre_define'; +import { + IFileLog, + LogType, + mkdirsSync, + toUnixPath +} from '../../../utils'; +import { getPkgInfo } from '../../../ark_utils'; +import { + EXTNAME_D_ETS, + EXTNAME_ETS, + EXTNAME_TS, + SUPER_ARGS +} from '../../../pre_define'; +import { + CommonLogger, + LogData, + LogDataFactory +} from '../logger'; +import { + ArkTSErrorDescription, + ErrorCode +} from '../error_code'; +import createAstNodeUtils from '../../../create_ast_node_utils'; +import { + red, + reset +} from '../common/ark_define'; + +interface DeclFileConfig { + declPath: string; + ohmUrl: string; +} + +interface DeclFilesConfig { + packageName: string; + files: { + [filePath: string]: DeclFileConfig; + } +} + +export interface ArkTSEvolutionModule { + language: string; // "1.1" | "1.2" + packageName: string; + moduleName: string; + modulePath: string; + declgenV1OutPath?: string; + declgenV2OutPath?: string; + declgenBridgeCodePath?: string; + declFilesPath?: string; +} + +interface ResolvedFileInfo { + moduleRequest: string; + resolvedFileName: string; +} + +export const interopTransformLog: IFileLog = new createAstNodeUtils.FileLog(); + +export let pkgDeclFilesConfig: { [pkgName: string]: DeclFilesConfig } = {}; + +export let arkTSModuleMap: Map = new Map(); + +export let arkTSEvolutionModuleMap: Map = new Map(); + +export let arkTSHybridModuleMap: Map = new Map(); + +let arkTSEvoFileOHMUrlMap: Map = new Map(); + +let declaredClassVars: Set = new Set(); + +export function addDeclFilesConfig(filePath: string, projectConfig: Object, logger: Object, + pkgPath: string, pkgName: string): void { + const { projectFilePath, pkgInfo } = getPkgInfo(filePath, projectConfig, logger, pkgPath, pkgName); + const declgenV2OutPath: string = getDeclgenV2OutPath(pkgName); + if (!declgenV2OutPath) { + return; + } + if (!pkgDeclFilesConfig[pkgName]) { + pkgDeclFilesConfig[pkgName] = { packageName: pkgName, files: {} }; + } + if (pkgDeclFilesConfig[pkgName].files[projectFilePath]) { + return; + } + const isSO: string = pkgInfo.isSO ? 'Y' : 'N'; + // The module name of the entry module of the project during the current compilation process. + const mainModuleName: string = projectConfig.mainModuleName; + const bundleName: string = projectConfig.bundleName; + const normalizedFilePath: string = `${pkgName}/${projectFilePath}`; + const declPath: string = path.join(toUnixPath(declgenV2OutPath), projectFilePath) + EXTNAME_D_ETS; + const ohmUrl: string = `${isSO}&${mainModuleName}&${bundleName}&${normalizedFilePath}&${pkgInfo.version}`; + pkgDeclFilesConfig[pkgName].files[projectFilePath] = { declPath, ohmUrl: `@normalized:${ohmUrl}` }; +} + +export function getArkTSEvoDeclFilePath(resolvedFileInfo: ResolvedFileInfo): string { + const { moduleRequest, resolvedFileName } = resolvedFileInfo; + let arktsEvoDeclFilePath: string = moduleRequest; + const combinedMap = new Map([...arkTSEvolutionModuleMap, ...arkTSHybridModuleMap]); + for (const [pkgName, arkTSEvolutionModuleInfo] of combinedMap) { + const declgenV1OutPath: string = toUnixPath(arkTSEvolutionModuleInfo.declgenV1OutPath); + const modulePath: string = toUnixPath(arkTSEvolutionModuleInfo.modulePath); + const declgenBridgeCodePath: string = toUnixPath(arkTSEvolutionModuleInfo.declgenBridgeCodePath); + if (resolvedFileName && resolvedFileName.startsWith(modulePath + '/') && + !resolvedFileName.startsWith(declgenBridgeCodePath + '/')) { + arktsEvoDeclFilePath = resolvedFileName + .replace(modulePath, toUnixPath(path.join(declgenV1OutPath, pkgName))) + .replace(EXTNAME_ETS, EXTNAME_D_ETS); + break; + } + if (moduleRequest === pkgName) { + arktsEvoDeclFilePath = path.join(declgenV1OutPath, pkgName, 'Index.d.ets'); + break; + } + if (moduleRequest.startsWith(pkgName + '/')) { + arktsEvoDeclFilePath = moduleRequest.replace( + pkgName, + toUnixPath(path.join(declgenV1OutPath, pkgName, 'src/main/ets')) + ) + EXTNAME_D_ETS; + + if (fs.existsSync(arktsEvoDeclFilePath)) { + break; + } + /** + * If the import is exported via the package name, for example: + * import { xxx } from 'src/main/ets/xxx/xxx/...' + * there is no need to additionally concatenate 'src/main/ets' + */ + arktsEvoDeclFilePath = moduleRequest.replace( + pkgName, + toUnixPath(path.join(declgenV1OutPath, pkgName)) + ) + EXTNAME_D_ETS; + break; + } + } + return arktsEvoDeclFilePath; +} + +export function collectArkTSEvolutionModuleInfo(share: Object): void { + if (!share.projectConfig.dependentModuleMap) { + return; + } + if (!share.projectConfig.useNormalizedOHMUrl) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_COLLECT_INTEROP_INFO_FAILED, + ArkTSErrorDescription, + 'Failed to compile mixed project.', + `Failed to compile mixed project because useNormalizedOHMUrl is false.`, + ['Please check whether useNormalizedOHMUrl is true.'] + ); + CommonLogger.getInstance(share).printErrorAndExit(errInfo); + + } + // dependentModuleMap Contents eg. + // 1.2 hap -> 1.1 har: It contains the information of 1.1 har + // 1.1 hap -> 1.2 har -> 1.1 har : There is information about 3 modules. + + const throwCollectionError = (pkgName: string): void => { + share.throwArkTsCompilerError(red, 'ArkTS:INTERNAL ERROR: Failed to collect arkTs evolution module info.\n' + + `Error Message: Failed to collect arkTs evolution module "${pkgName}" info from rollup.`, reset); + }; + + for (const [pkgName, dependentModuleInfo] of share.projectConfig.dependentModuleMap) { + switch (dependentModuleInfo.language) { + case ARKTS_1_2: + if (dependentModuleInfo.declgenV1OutPath && dependentModuleInfo.declgenBridgeCodePath) { + arkTSEvolutionModuleMap.set(pkgName, dependentModuleInfo); + } else { + throwCollectionError(pkgName); + } + break; + case ARKTS_HYBRID: + if (dependentModuleInfo.declgenV2OutPath && dependentModuleInfo.declFilesPath && dependentModuleInfo.declgenBridgeCodePath) { + arkTSHybridModuleMap.set(pkgName, dependentModuleInfo); + } else { + throwCollectionError(pkgName); + } + break; + case ARKTS_1_1: + case ARKTS_1_0: + if (dependentModuleInfo.declgenV2OutPath && dependentModuleInfo.declFilesPath) { + arkTSModuleMap.set(pkgName, dependentModuleInfo); + } else { + throwCollectionError(pkgName); + } + break; + } + } +} + +export function cleanUpProcessArkTSEvolutionObj(): void { + arkTSModuleMap = new Map(); + arkTSEvolutionModuleMap = new Map(); + arkTSHybridModuleMap = new Map(); + pkgDeclFilesConfig = {}; + arkTSEvoFileOHMUrlMap = new Map(); + interopTransformLog.cleanUp(); + declaredClassVars = new Set(); +} + +export async function writeBridgeCodeFileSyncByNode(node: ts.SourceFile, moduleId: string, + metaInfo: Object): Promise { + const printer: ts.Printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const writer: ts.EmitTextWriter = ts.createTextWriter( + // @ts-ignore + ts.getNewLineCharacter({ newLine: ts.NewLineKind.LineFeed, removeComments: false })); + printer.writeFile(node, writer, undefined); + const cacheFilePath: string = genCachePathForBridgeCode(moduleId, metaInfo); + mkdirsSync(path.dirname(cacheFilePath)); + fs.writeFileSync(cacheFilePath, writer.getText()); +} + +export function genCachePathForBridgeCode(moduleId: string, metaInfo: Object, cachePath?: string): string { + const bridgeCodePath: string = getDeclgenBridgeCodePath(metaInfo.pkgName); + const filePath: string = toUnixPath(moduleId); + const relativeFilePath: string = filePath.replace( + toUnixPath(path.join(bridgeCodePath, metaInfo.pkgName)), metaInfo.moduleName); + const cacheFilePath: string = path.join(cachePath ? cachePath : process.env.cachePath, relativeFilePath); + return cacheFilePath; +} + +export function getDeclgenBridgeCodePath(pkgName: string): string { + const combinedMap = new Map([...arkTSEvolutionModuleMap, ...arkTSHybridModuleMap]); + if (combinedMap.size && combinedMap.get(pkgName)) { + const arkTSEvolutionModuleInfo: ArkTSEvolutionModule = combinedMap.get(pkgName); + return arkTSEvolutionModuleInfo.declgenBridgeCodePath; + } + return ''; +} + +function getDeclgenV2OutPath(pkgName: string): string { + const combinedMap = new Map([...arkTSModuleMap, ...arkTSHybridModuleMap]); + if (combinedMap.size && combinedMap.get(pkgName)) { + const arkTsModuleInfo: ArkTSEvolutionModule = combinedMap.get(pkgName); + return arkTsModuleInfo.declgenV2OutPath; + } + return ''; +} + +export function isArkTSEvolutionFile(filePath: string, metaInfo: Object): boolean { + if (metaInfo.language === ARKTS_1_0 || metaInfo.language === ARKTS_1_1) { + return false; + } + + if (metaInfo.language === ARKTS_1_2) { + return true; + } + + if (metaInfo.language === ARKTS_HYBRID || arkTSHybridModuleMap.has(metaInfo.pkgName)) { + const hybridModule = arkTSHybridModuleMap.get(metaInfo.pkgName); + if (!hybridModule) { + return false; + } + + const normalizedFilePath = toUnixPath(filePath); + const staticFileList = hybridModule.staticFiles || []; + + // Concatenate the corresponding source code path based on the bridge code path. + const declgenCodeBrigdePath = path.join(toUnixPath(hybridModule.declgenBridgeCodePath), metaInfo.pkgName); + let moduleId = normalizedFilePath.replace(toUnixPath(declgenCodeBrigdePath), toUnixPath(metaInfo.pkgPath)); + const arktsEvolutionFile = moduleId.replace(new RegExp(`\\${EXTNAME_TS}$`), EXTNAME_ETS); + + return new Set(staticFileList.map(toUnixPath)).has(arktsEvolutionFile); + } + + return false; +} + +export function interopTransform(program: ts.Program, id: string, mixCompile: boolean): ts.TransformerFactory { + if (!mixCompile || /\.ts$/.test(id)) { + return () => sourceFile => sourceFile; + } + // For specific scenarios, please refer to the test file process_arkts_evolution.test.ts + const typeChecker: ts.TypeChecker = program.getTypeChecker(); + return (context: ts.TransformationContext): ts.Transformer => { + const scopeUsedNames: WeakMap> = new WeakMap>(); + const fullNameToTmpVar: Map = new Map(); + const globalDeclarations: Map = new Map(); + return (rootNode: ts.SourceFile) => { + interopTransformLog.sourceFile = rootNode; + const classToInterfacesMap: Map> = collectInterfacesMap(rootNode, typeChecker); + // Support for creating 1.2 type object literals in 1.1 modules + const visitor: ts.Visitor = + createObjectLiteralVisitor(rootNode, context, typeChecker, scopeUsedNames, fullNameToTmpVar, globalDeclarations); + const processNode: ts.SourceFile = ts.visitEachChild(rootNode, visitor, context); + // Support 1.1 classes to implement 1.2 interfaces + const withHeritage: ts.SourceFile = classToInterfacesMap.size > 0 ? + ts.visitEachChild(processNode, transformHeritage(context, classToInterfacesMap), context) : processNode; + + const importStmts: ts.ImportDeclaration[] = withHeritage.statements.filter(stmt => ts.isImportDeclaration(stmt)); + const otherStmts: ts.Statement[] = withHeritage.statements.filter(stmt => !ts.isImportDeclaration(stmt)); + const globalStmts: ts.Statement[] = Array.from(globalDeclarations.values()); + + return ts.factory.updateSourceFile( + withHeritage, + [...importStmts, ...globalStmts, ...otherStmts], + withHeritage.isDeclarationFile, + withHeritage.referencedFiles, + withHeritage.typeReferenceDirectives, + withHeritage.hasNoDefaultLib, + withHeritage.libReferenceDirectives + ); + }; + }; +} + +function isFromArkTSEvolutionModule(node: ts.Node): boolean { + const sourceFile: ts.SourceFile = node.getSourceFile(); + const filePath: string = toUnixPath(sourceFile.fileName); + const combinedMap = new Map([...arkTSEvolutionModuleMap, ...arkTSHybridModuleMap]); + for (const arkTSEvolutionModuleInfo of combinedMap.values()) { + const declgenV1OutPath: string = toUnixPath(arkTSEvolutionModuleInfo.declgenV1OutPath); + if (filePath.startsWith(declgenV1OutPath + '/')) { + const relative: string = filePath.replace(declgenV1OutPath + '/', '').replace(/\.d\.ets$/, ''); + if (!arkTSEvoFileOHMUrlMap.has(filePath)) { + arkTSEvoFileOHMUrlMap.set(filePath, relative); + } + return true; + } + } + return false; +} + +function createObjectLiteralVisitor(rootNode: ts.SourceFile, context: ts.TransformationContext, typeChecker: ts.TypeChecker, + scopeUsedNames: WeakMap>, fullNameToTmpVar: Map, + globalDeclarations: Map): ts.Visitor { + return function visitor(node: ts.SourceFile): ts.SourceFile { + if (!ts.isObjectLiteralExpression(node)) { + return ts.visitEachChild(node, visitor, context); + } + + const contextualType: ts.Type | undefined = typeChecker.getContextualType(node); + if (!contextualType) { + return ts.visitEachChild(node, visitor, context); + } + const isRecordType: boolean = contextualType.aliasSymbol?.escapedName === 'Record' && + (typeof typeChecker.isStaticRecord === 'function' && typeChecker.isStaticRecord(contextualType)); + const finalType: ts.Type = unwrapType(node, contextualType); + const decl : ts.Declaration = (finalType.symbol?.declarations || finalType.aliasSymbol?.declarations)?.[0]; + + let className: string; + let tmpObjName: string; + if (!isRecordType) { + if (!decl || !isFromArkTSEvolutionModule(decl)) { + return ts.visitEachChild(node, visitor, context); + } + className = finalType.symbol?.name || finalType.aliasSymbol?.name; + if (!className) { + return ts.visitEachChild(node, visitor, context); + } + + if (ts.isClassDeclaration(decl) && !hasZeroArgConstructor(decl, className)) { + return ts.visitEachChild(node, visitor, context); + } + tmpObjName = getUniqueName(rootNode, 'tmpObj', scopeUsedNames); + declareGlobalTemp(tmpObjName, globalDeclarations); + } + + const fullName: string = buildFullClassName(decl, finalType, className, isRecordType); + const getCtorExpr: ts.Expression = buildGetConstructorCall(fullName, isRecordType); + let tmpClassName: string; + if (fullNameToTmpVar.has(fullName)) { + tmpClassName = fullNameToTmpVar.get(fullName)!; + } else { + tmpClassName = getUniqueName(rootNode, isRecordType ? 'tmpRecord' : 'tmpClass', scopeUsedNames); + fullNameToTmpVar.set(fullName, tmpClassName); + declareGlobalTemp(tmpClassName, globalDeclarations, getCtorExpr); + } + + return ts.factory.createParenthesizedExpression( + ts.factory.createCommaListExpression(buildCommaExpressions(node, isRecordType, tmpObjName, tmpClassName))); + }; +} + +function unwrapType(node: ts.SourceFile, type: ts.Type): ts.Type { + // Unwrap parenthesized types recursively + if ((type.flags & ts.TypeFlags.Parenthesized) && 'type' in type) { + return unwrapType(node, (type as ts.ParenthesizedType).type); + } + + // If union, pick the unique viable type + if (type.isUnion()) { + const arkTSEvolutionTypes: ts.Type[] = []; + + for (const tpye of type.types) { + const symbol: ts.Symbol = tpye.aliasSymbol || tpye.getSymbol(); + const decls: ts.Declaration[] = symbol?.declarations; + if (!decls || decls.length === 0) { + continue; + } + + const isArkTSEvolution: boolean = decls.some(decl => isFromArkTSEvolutionModule(decl)); + if (isArkTSEvolution) { + arkTSEvolutionTypes.push(tpye); + } + } + if (arkTSEvolutionTypes.length === 0) { + return type; + } + if (arkTSEvolutionTypes.length !== 1 || type.types.length > 1) { + const candidates: string = arkTSEvolutionTypes.map(tpye => tpye.symbol?.name || '(anonymous)').join(', '); + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_UNION_TYPE_AMBIGUITY, + ArkTSErrorDescription, + `Ambiguous union type: multiple valid ArkTSEvolution types found: [${candidates}].`, + '', + ['Please use type assertion as to disambiguate.'] + ); + interopTransformLog.errors.push({ + type: LogType.ERROR, + message: errInfo.toString(), + pos: node.getStart() + }); + return type; + } + return unwrapType(node, arkTSEvolutionTypes[0]); + } + return type; +} + +function hasZeroArgConstructor(decl: ts.ClassDeclaration, className: string): boolean { + const ctors = decl.members.filter(member => + ts.isConstructorDeclaration(member) || ts.isConstructSignatureDeclaration(member)); + const hasZeroArgCtor: boolean = ctors.length === 0 || ctors.some(ctor => ctor.parameters.length === 0); + if (!hasZeroArgCtor) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_CLASS_HAS_NO_CONSTRUCTOR_WITHOUT_ARGS, + ArkTSErrorDescription, + `The class "${className}" does not has no no-argument constructor.`, + '', + [ + 'Please confirm whether there is a no-argument constructor ' + + `in the ArkTS Evolution class "${className}" type in the object literal.` + ] + ); + interopTransformLog.errors.push({ + type: LogType.ERROR, + message: errInfo.toString(), + pos: decl.name?.getStart() ?? decl.getStart() + }); + return false; + } + return hasZeroArgCtor; +} + +function buildFullClassName(decl: ts.Declaration, finalType: ts.Type, className: string, isRecordType: boolean): string { + if (isRecordType) { + return 'Lescompat/Record;'; + } + const basePath: string = getArkTSEvoFileOHMUrl(finalType); + return ts.isInterfaceDeclaration(decl) ? + `L${basePath}/${basePath.split('/').join('$')}$${className}$ObjectLiteral;` : + `L${basePath}/${className};`; +} + +function buildGetConstructorCall(fullName: string, isRecord: boolean): ts.Expression { + return ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('globalThis'), + isRecord ? 'Panda.getInstance' : 'Panda.getClass'), + undefined, + [ts.factory.createStringLiteral(fullName)] + ); +} + +function buildPropertyAssignments(node: ts.ObjectLiteralExpression, tmpObjName: string, + usePropertyAccess: boolean = true): ts.Expression[] { + return node.properties.map(property => { + if (!ts.isPropertyAssignment(property)) { + return undefined; + } + const key = property.name; + const target = usePropertyAccess && ts.isIdentifier(key) ? + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(tmpObjName), key) : + ts.factory.createElementAccessExpression(ts.factory.createIdentifier(tmpObjName), + ts.isIdentifier(key) ? ts.factory.createStringLiteral(key.text) : key); + return ts.factory.createAssignment(target, property.initializer); + }).filter(Boolean) as ts.Expression[]; +} + +function buildCommaExpressions(node: ts.ObjectLiteralExpression, isRecordType: boolean, + tmpObjName: string, tmpClassName: string): ts.Expression[] { + const assignments: ts.Expression[] = + buildPropertyAssignments(node, isRecordType ? tmpClassName : tmpObjName, !isRecordType); + + if (isRecordType) { + return [...assignments, ts.factory.createIdentifier(tmpClassName)]; + } + + return [ + ts.factory.createAssignment( + ts.factory.createIdentifier(tmpObjName), + ts.factory.createNewExpression(ts.factory.createIdentifier(tmpClassName), undefined, []) + ), + ...assignments, + ts.factory.createIdentifier(tmpObjName) + ]; +} + +function getArkTSEvoFileOHMUrl(contextualType: ts.Type): string { + const decl: ts.Declaration = (contextualType.symbol?.declarations || contextualType.aliasSymbol?.declarations)?.[0]; + if (!decl) { + return ''; + } + const sourceFilePath: string = toUnixPath(decl.getSourceFile().fileName); + return arkTSEvoFileOHMUrlMap.get(sourceFilePath); +} + +function getUniqueName(scope: ts.Node, base: string, usedNames: WeakMap>): string { + if (!usedNames.has(scope)) { + usedNames.set(scope, new Set()); + } + const used: Set = usedNames.get(scope)!; + let name: string = base; + let counter: number = 1; + while (used.has(name)) { + name = `${base}_${counter++}`; + } + used.add(name); + return name; +} + +function declareGlobalTemp(name: string, globalDeclarations: Map, initializer?: ts.Expression): ts.Statement { + if (initializer && declaredClassVars.has(name)) { + return globalDeclarations.get(name)!; + } + + if (!globalDeclarations.has(name)) { + const decl = ts.factory.createVariableStatement(undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration(name, undefined, undefined, initializer)], ts.NodeFlags.Let)); + globalDeclarations.set(name, decl); + if (initializer) { + declaredClassVars.add(name); + } + } + + return globalDeclarations.get(name)!; +} + +function collectInterfacesMap(rootNode: ts.Node, checker: ts.TypeChecker): Map> { + const classToInterfacesMap = new Map>(); + ts.forEachChild(rootNode, function visit(node) { + if (ts.isClassDeclaration(node)) { + const interfaces = new Set(); + const visited = new Set(); + collectDeepInheritedInterfaces(node, checker, visited, interfaces); + if (interfaces.size > 0) { + classToInterfacesMap.set(node, interfaces); + } + } + ts.forEachChild(node, visit); + }); + return classToInterfacesMap; +} + +function collectDeepInheritedInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration, + checker: ts.TypeChecker, visited: Set, interfaces: Set): void { + const heritageClauses = node.heritageClauses; + if (!heritageClauses) { + return; + } + + for (const clause of heritageClauses) { + for (const exprWithTypeArgs of clause.types) { + const type = checker.getTypeAtLocation(exprWithTypeArgs.expression); + collectDeepInheritedInterfacesFromType(type, checker, visited, interfaces); + } + } +} + +function collectDeepInheritedInterfacesFromType(type: ts.Type, checker: ts.TypeChecker, + visited: Set, interfaces: Set): void { + if (visited.has(type)) { + return; + } + visited.add(type); + const decls: ts.Declaration[] = type.symbol?.declarations; + const isArkTSEvolution: boolean = decls?.some(decl => isFromArkTSEvolutionModule(decl)); + if (isArkTSEvolution) { + const ifacePath: string = getArkTSEvoFileOHMUrl(type); + interfaces.add(`L${ifacePath}/${type.symbol.name};`); + } + const baseTypes: ts.BaseType[] = checker.getBaseTypes(type as ts.InterfaceType) ?? []; + for (const baseType of baseTypes) { + collectDeepInheritedInterfacesFromType(baseType, checker, visited, interfaces); + } + + if (decls) { + for (const decl of decls) { + if (ts.isClassDeclaration(decl) || ts.isInterfaceDeclaration(decl)) { + collectDeepInheritedInterfaces(decl, checker, visited, interfaces); + } + } + } +} + +function transformHeritage(context: ts.TransformationContext, + classToInterfacesMap: Map>): ts.Visitor { + return function visitor(node: ts.SourceFile): ts.SourceFile { + if (ts.isClassDeclaration(node) && classToInterfacesMap.has(node)) { + const interfaceNames = classToInterfacesMap.get(node)!; + const updatedMembers = injectImplementsInConstructor(node, interfaceNames); + return ts.factory.updateClassDeclaration(node, node.modifiers, node.name, node.typeParameters, + node.heritageClauses, updatedMembers); + } + return ts.visitEachChild(node, visitor, context); + }; +} + +function injectImplementsInConstructor(node: ts.ClassDeclaration, interfaceNames: Set): ts.ClassElement[] { + const members: ts.ClassElement[] = [...node.members]; + const params: ts.ParameterDeclaration[] = []; + const needSuper: boolean = + node.heritageClauses?.some(clause => clause.token === ts.SyntaxKind.ExtendsKeyword) || false; + const injectStatement: ts.ExpressionStatement[] = [ + ts.factory.createExpressionStatement( + ts.factory.createStringLiteral(`implements static:${(Array.from(interfaceNames)).join(',')}`) + ) + ]; + const ctorDecl: ts.ConstructorDeclaration | undefined = + members.find(element => ts.isConstructorDeclaration(element)) as ts.ConstructorDeclaration | undefined; + if (ctorDecl) { + const newCtorDecl: ts.ConstructorDeclaration = ts.factory.updateConstructorDeclaration( + ctorDecl, ctorDecl.modifiers, ctorDecl.parameters, + ts.factory.updateBlock( + ctorDecl.body ?? ts.factory.createBlock([], true), + [...injectStatement, ...(ctorDecl.body?.statements ?? [])] + ) + ); + const index: number = members.indexOf(ctorDecl); + members.splice(index, 1, newCtorDecl); + } else { + addSuper(needSuper, injectStatement, params); + const newCtorDecl: ts.ConstructorDeclaration = ts.factory.createConstructorDeclaration( + undefined, params, + ts.factory.createBlock([...injectStatement], true) + ); + members.push(newCtorDecl); + } + return members; +} + +function addSuper(needSuper: boolean, injectStatement: ts.ExpressionStatement[], + params: ts.ParameterDeclaration[]): void { + if (needSuper) { + injectStatement.push( + ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createSuper(), undefined, [ts.factory.createSpreadElement(ts.factory.createIdentifier(SUPER_ARGS))]) + ) + ); + params.push( + ts.factory.createParameterDeclaration( + undefined, + ts.factory.createToken(ts.SyntaxKind.DotDotDotToken), + ts.factory.createIdentifier(SUPER_ARGS), + undefined, + undefined, + undefined) + ); + } +} + +export function redirectToDeclFileForInterop(resolvedFileName: string): ts.ResolvedModuleFull { + const filePath: string = toUnixPath(resolvedFileName); + const resultDETSPath: string = getArkTSEvoDeclFilePath({ moduleRequest: '', resolvedFileName: filePath }); + if (ts.sys.fileExists(resultDETSPath)) { + return getResolveModule(resultDETSPath, EXTNAME_D_ETS); + } + return undefined; +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/interop/run_declgen_standalone.ts b/compiler/src/interop/src/fast_build/ark_compiler/interop/run_declgen_standalone.ts new file mode 100644 index 0000000000000000000000000000000000000000..dde5d5b98483a52783f65fca4026d53622da44d0 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/interop/run_declgen_standalone.ts @@ -0,0 +1,441 @@ +/* + * 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 { FileManager } from './interop_manager'; +import { ResolveModuleInfo, getResolveModule, readDeaclareFiles } from '../../../ets_checker'; +import { processInteropUI } from '../../../process_interop_ui'; +import { + mkdirsSync, + readFile, + toUnixPath +} from '../../../utils'; +import { + ArkTSEvolutionModule, + BuildType, + DeclFilesConfig, + DECLGEN_CACHE_FILE, + Params, + ProjectConfig, + RunnerParms +} from './type'; +import fs from 'fs'; +import path from 'path'; +import * as ts from 'typescript'; +import { EXTNAME_D_ETS, EXTNAME_JS } from '../common/ark_define'; +import { getRealModulePath } from '../../system_api/api_check_utils'; +import { generateInteropDecls } from 'declgen/build/src/generateInteropDecls'; //??? +import { calculateFileHash } from '../utils'; + +export function run(param: Params): boolean { + FileManager.init(param.dependentModuleMap); + DeclfileProductor.init(param); + param.tasks.forEach(task => { + const moduleInfo = FileManager.arkTSModuleMap.get(task.packageName); + if (moduleInfo?.dynamicFiles.length <= 0) { + return; + } + if (task.buildTask === BuildType.DECLGEN) { + DeclfileProductor.getInstance().runDeclgen(moduleInfo); + } else if (task.buildTask === BuildType.INTEROP_CONTEXT) { + DeclfileProductor.getInstance().writeDeclFileInfo(moduleInfo, task.mainModuleName); + } else if (task.buildTask === BuildType.BYTE_CODE_HAR) { + //todo + } + }); + FileManager.cleanFileManagerObject(); + return true; +} + +class DeclfileProductor { + private static declFileProductor: DeclfileProductor; + + static compilerOptions: ts.CompilerOptions; + static sdkConfigPrefix = 'ohos|system|kit|arkts'; + static sdkConfigs = []; + static systemModules = []; + static defaultSdkConfigs = []; + static projectPath; + private projectConfig; + private pkgDeclFilesConfig: { [pkgName: string]: DeclFilesConfig } = {}; + + static init(param: Params): void { + DeclfileProductor.declFileProductor = new DeclfileProductor(param); + DeclfileProductor.compilerOptions = ts.readConfigFile( + path.join(__dirname, '../../../../../../tsconfig.json'), ts.sys.readFile).config.compilerOptions; + DeclfileProductor.initSdkConfig(); + Object.assign(DeclfileProductor.compilerOptions, { + emitNodeModulesFiles: true, + importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Preserve, + module: ts.ModuleKind.CommonJS, + moduleResolution: ts.ModuleResolutionKind.NodeJs, + noEmit: true, + packageManagerType: 'ohpm', + allowJs: true, + allowSyntheticDefaultImports: true, + esModuleInterop: true, + noImplicitAny: false, + noUnusedLocals: false, + noUnusedParameters: false, + experimentalDecorators: true, + resolveJsonModule: true, + skipLibCheck: false, + sourceMap: true, + target: 8, + types: [], + typeRoots: [], + lib: ['lib.es2021.d.ts'], + alwaysStrict: true, + checkJs: false, + maxFlowDepth: 2000, + etsAnnotationsEnable: false, + etsLoaderPath: path.join(__dirname, '../../../'), //??? + needDoArkTsLinter: true, + isCompatibleVersion: false, + skipTscOhModuleCheck: false, + skipArkTSStaticBlocksCheck: false, + incremental: true, + tsImportSendableEnable: false, + skipPathsInKeyForCompilationSettings: true, + }); + DeclfileProductor.projectPath = param.projectConfig.projectRootPath; + } + static getInstance(param?: Params): DeclfileProductor { + if (!this.declFileProductor) { + this.declFileProductor = new DeclfileProductor(param); + } + return this.declFileProductor; + } + + private constructor(param: Params) { + this.projectConfig = param.projectConfig as ProjectConfig; + } + + runDeclgen(moduleInfo: ArkTSEvolutionModule): void { + const cachePath = `${moduleInfo.declgenV2OutPath}/.${DECLGEN_CACHE_FILE}`; + let existingCache = {}; + const filesToProcess = []; + + if (fs.existsSync(cachePath)) { + existingCache = JSON.parse(fs.readFileSync(cachePath, 'utf-8')); + } + + let inputList = []; + let hashMap = {}; + moduleInfo.dynamicFiles.forEach(path => { + let unixPath = toUnixPath(path); + const fileHash = calculateFileHash(path); + if (!existingCache[unixPath] || existingCache[unixPath] !== fileHash) { + filesToProcess.push(unixPath); + hashMap[unixPath] = fileHash; + } + }); + if (filesToProcess.length === 0) { + return; + } + readDeaclareFiles().forEach(path => { + filesToProcess.push(toUnixPath(path)); + }); + + inputList = inputList.filter(filePath => !filePath.endsWith('.js')); + const config: RunnerParms = { + inputDirs: [], + inputFiles: filesToProcess, + outDir: moduleInfo.declgenV2OutPath, + // use package name as folder name + rootDir: moduleInfo.modulePath, + customResolveModuleNames: resolveModuleNames, + customCompilerOptions: DeclfileProductor.compilerOptions, + includePaths: [moduleInfo.modulePath] + }; + if (!fs.existsSync(config.outDir)) { + fs.mkdirSync(config.outDir, { recursive: true }); + } + fs.mkdirSync(config.outDir, { recursive: true }); + generateInteropDecls(config); + processInteropUI(FileManager.arkTSModuleMap.get(moduleInfo.packageName)?.declgenV2OutPath); + const newCache = { + ...existingCache, + ...hashMap + }; + fs.writeFileSync(cachePath, JSON.stringify(newCache, null, 2)); + } + + writeDeclFileInfo(moduleInfo: ArkTSEvolutionModule, mainModuleName: string): void { + moduleInfo.dynamicFiles.forEach(file => { + this.addDeclFilesConfig(file, mainModuleName, this.projectConfig.bundleName, moduleInfo); + }); + + const declFilesConfigFile: string = toUnixPath(moduleInfo.declFilesPath); + mkdirsSync(path.dirname(declFilesConfigFile)); + if (this.pkgDeclFilesConfig[moduleInfo.packageName]) { + fs.writeFileSync(declFilesConfigFile, JSON.stringify(this.pkgDeclFilesConfig[moduleInfo.packageName], null, 2), 'utf-8'); + } + } + + addDeclFilesConfig(filePath: string, mainModuleName: string, bundleName: string, moduleInfo: ArkTSEvolutionModule): void { + const projectFilePath = getRelativePath(filePath, moduleInfo.modulePath); + + const declgenV2OutPath: string = this.getDeclgenV2OutPath(moduleInfo.packageName); + if (!declgenV2OutPath) { + return; + } + if (!this.pkgDeclFilesConfig[moduleInfo.packageName]) { + this.pkgDeclFilesConfig[moduleInfo.packageName] = { packageName: moduleInfo.packageName, files: {} }; + } + if (filePath.endsWith(EXTNAME_JS)) { + return; + } + if (this.pkgDeclFilesConfig[moduleInfo.packageName].files[projectFilePath]) { + return; + } + // The module name of the entry module of the project during the current compilation process. + const normalizedFilePath: string = `${moduleInfo.packageName}/${projectFilePath}`; + const declPath: string = path.join(toUnixPath(declgenV2OutPath), projectFilePath) + EXTNAME_D_ETS; + const ohmUrl: string = `N&${mainModuleName}&${bundleName}&${normalizedFilePath}&${moduleInfo.packageVersion}`; + this.pkgDeclFilesConfig[moduleInfo.packageName].files[projectFilePath] = { declPath, ohmUrl: `@normalized:${ohmUrl}` }; + } + + getDeclgenV2OutPath(pkgName: string): string { + if (FileManager.arkTSModuleMap.size && FileManager.arkTSModuleMap.get(pkgName)) { + const arkTsModuleInfo: ArkTSEvolutionModule = FileManager.arkTSModuleMap.get(pkgName); + return arkTsModuleInfo.declgenV2OutPath; + } + return ''; + } + + static initSdkConfig(): void { + const apiDirPath = path.resolve(__dirname, '../../../../../api'); //??? + const arktsDirPath = path.resolve(__dirname, '../../../../../arkts'); + const kitsDirPath = path.resolve(__dirname, '../../../../../kits'); + const systemModulePathArray = [apiDirPath]; + if (!process.env.isFaMode) { + systemModulePathArray.push(arktsDirPath, kitsDirPath); + } + systemModulePathArray.forEach(systemModulesPath => { + if (fs.existsSync(systemModulesPath)) { + const modulePaths = []; + readFile(systemModulesPath, modulePaths); + DeclfileProductor.systemModules.push(...fs.readdirSync(systemModulesPath)); + const moduleSubdir = modulePaths.filter(filePath => { + const dirName = path.dirname(filePath); + return !(dirName === apiDirPath || dirName === arktsDirPath || dirName === kitsDirPath); + }).map(filePath => { + return filePath + .replace(apiDirPath, '') + .replace(arktsDirPath, '') + .replace(kitsDirPath, '') + .replace(/(^\\)|(.d.e?ts$)/g, '') + .replace(/\\/g, '/'); + }); + } + }); + DeclfileProductor.defaultSdkConfigs = [ + { + 'apiPath': systemModulePathArray, + 'prefix': '@ohos' + }, { + 'apiPath': systemModulePathArray, + 'prefix': '@system' + }, { + 'apiPath': systemModulePathArray, + 'prefix': '@arkts' + } + ]; + DeclfileProductor.sdkConfigs = [...DeclfileProductor.defaultSdkConfigs]; + } +} + +function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] { + const resolvedModules: ts.ResolvedModuleFull[] = []; + + for (const moduleName of moduleNames) { + let resolvedModule: ts.ResolvedModuleFull | null = null; + + resolvedModule = resolveWithDefault(moduleName, containingFile); + if (resolvedModule) { + resolvedModules.push(resolvedModule); + continue; + } + + resolvedModule = resolveSdkModule(moduleName); + if (resolvedModule) { + resolvedModules.push(resolvedModule); + continue; + } + + resolvedModule = resolveEtsModule(moduleName, containingFile); + if (resolvedModule) { + resolvedModules.push(resolvedModule); + continue; + } + + resolvedModule = resolveTsModule(moduleName, containingFile); + if (resolvedModule) { + resolvedModules.push(resolvedModule); + continue; + } + + resolvedModule = resolveOtherModule(moduleName, containingFile); + resolvedModules.push(resolvedModule ?? null); + } + + return resolvedModules; +} + +function resolveWithDefault( + moduleName: string, + containingFile: string +): ts.ResolvedModuleFull | null { + const result = ts.resolveModuleName(moduleName, containingFile, DeclfileProductor.compilerOptions, moduleResolutionHost); + if (!result.resolvedModule) { + return null; + } + + const resolvedFileName = result.resolvedModule.resolvedFileName; + if (resolvedFileName && path.extname(resolvedFileName) === EXTNAME_JS) { + const resultDETSPath = resolvedFileName.replace(EXTNAME_JS, EXTNAME_D_ETS); + if (ts.sys.fileExists(resultDETSPath)) { + return getResolveModule(resultDETSPath, EXTNAME_D_ETS); + } + } + + return result.resolvedModule; +} + +function resolveEtsModule(moduleName: string, containingFile: string): ts.ResolvedModuleFull | null { + if (!/\.ets$/.test(moduleName) || /\.d\.ets$/.test(moduleName)) { + return null; + } + + const modulePath = path.resolve(path.dirname(containingFile), moduleName); + return ts.sys.fileExists(modulePath) ? getResolveModule(modulePath, '.ets') : null; +} + +function resolveSdkModule(moduleName: string): ts.ResolvedModuleFull | null { + const prefixRegex = new RegExp(`^@(${DeclfileProductor.sdkConfigPrefix})\\.`, 'i'); + if (!prefixRegex.test(moduleName.trim())) { + return null; + } + + for (const sdkConfig of DeclfileProductor.sdkConfigs) { + const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(sdkConfig.apiPath, moduleName, ['.d.ts', '.d.ets']); + const modulePath: string = resolveModuleInfo.modulePath; + const isDETS: boolean = resolveModuleInfo.isEts; + + const moduleKey = moduleName + (isDETS ? '.d.ets' : '.d.ts'); + if (DeclfileProductor.systemModules.includes(moduleKey) && ts.sys.fileExists(modulePath)) { + return getResolveModule(modulePath, isDETS ? '.d.ets' : '.d.ts'); + } + } + + return null; +} + +function resolveTsModule(moduleName: string, containingFile: string): ts.ResolvedModuleFull | null { + if (!/\.ts$/.test(moduleName)) { + return null; + } + + + const modulePath = path.resolve(path.dirname(containingFile), moduleName); + return ts.sys.fileExists(modulePath) ? getResolveModule(modulePath, '.ts') : null; +} + +function resolveOtherModule(moduleName: string, containingFile: string): ts.ResolvedModuleFull | null { //??? + const apiModulePath = path.resolve(__dirname, '../../../api', moduleName + '.d.ts'); + const systemDETSModulePath = path.resolve(__dirname, '../../../api', moduleName + '.d.ets'); + const kitModulePath = path.resolve(__dirname, '../../../kits', moduleName + '.d.ts'); + const kitSystemDETSModulePath = path.resolve(__dirname, '../../../kits', moduleName + '.d.ets'); + const jsModulePath = path.resolve(__dirname, '../node_modules', moduleName + (moduleName.endsWith('.js') ? '' : '.js')); + const fileModulePath = path.resolve(__dirname, '../node_modules', moduleName + '/index.js'); + const DETSModulePath = path.resolve(path.dirname(containingFile), + moduleName.endsWith('.d.ets') ? moduleName : moduleName + EXTNAME_D_ETS); + + if (ts.sys.fileExists(apiModulePath)) { + return getResolveModule(apiModulePath, '.d.ts'); + } else if (ts.sys.fileExists(systemDETSModulePath)) { + return getResolveModule(systemDETSModulePath, '.d.ets'); + } else if (ts.sys.fileExists(kitModulePath)) { + return getResolveModule(kitModulePath, '.d.ts'); + } else if (ts.sys.fileExists(kitSystemDETSModulePath)) { + return getResolveModule(kitSystemDETSModulePath, '.d.ets'); + } else if (ts.sys.fileExists(jsModulePath)) { + return getResolveModule(jsModulePath, '.js'); + } else if (ts.sys.fileExists(fileModulePath)) { + return getResolveModule(fileModulePath, '.js'); + } else if (ts.sys.fileExists(DETSModulePath)) { + return getResolveModule(DETSModulePath, '.d.ets'); + } else { + const srcIndex = DeclfileProductor.projectPath.indexOf('src' + path.sep + 'main'); + if (srcIndex > 0) { + const DETSModulePathFromModule = path.resolve( + DeclfileProductor.projectPath.substring(0, srcIndex), + moduleName + path.sep + 'index' + EXTNAME_D_ETS + ); + if (ts.sys.fileExists(DETSModulePathFromModule)) { + return getResolveModule(DETSModulePathFromModule, '.d.ets'); + } + } + return null; + } +} + +function getRelativePath(filePath: string, pkgPath: string): string { + // rollup uses commonjs plugin to handle commonjs files, + // the commonjs files are prefixed with '\x00' and need to be removed. + if (filePath.startsWith('\x00')) { + filePath = filePath.replace('\x00', ''); + } + let unixFilePath: string = toUnixPath(filePath); + + // Handle .d.ets and .d.ts extensions + const dEtsIndex = unixFilePath.lastIndexOf('.d.ets'); + const dTsIndex = unixFilePath.lastIndexOf('.d.ts'); + + if (dEtsIndex !== -1) { + unixFilePath = unixFilePath.substring(0, dEtsIndex); + } else if (dTsIndex !== -1) { + unixFilePath = unixFilePath.substring(0, dTsIndex); + } else { + // Fallback to regular extension removal if not a .d file + const lastDotIndex = unixFilePath.lastIndexOf('.'); + if (lastDotIndex !== -1) { + unixFilePath = unixFilePath.substring(0, lastDotIndex); + } + } + + const projectFilePath: string = unixFilePath.replace(toUnixPath(pkgPath) + '/', ''); + return projectFilePath; +} + +const moduleResolutionHost: ts.ModuleResolutionHost = { + fileExists: (fileName: string): boolean => { + let exists = ts.sys.fileExists(fileName); + if (exists === undefined) { + exists = ts.sys.fileExists(fileName); + } + return exists; + }, + + readFile(fileName: string): string | undefined { + return ts.sys.readFile(fileName); + }, + realpath(path: string): string { + return ts.sys.realpath(path); + }, + trace(s: string): void { + console.info(s); + } +}; \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/interop/type.ts b/compiler/src/interop/src/fast_build/ark_compiler/interop/type.ts new file mode 100644 index 0000000000000000000000000000000000000000..47387278d24297945efcdcf12a481da0d262b949 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/interop/type.ts @@ -0,0 +1,114 @@ +/* + * 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 ts from 'typescript'; + +export interface ArkTSEvolutionModule { + language: string; + packageName: string; + moduleName: string; + modulePath: string; + declgenV1OutPath?: string; + declgenV2OutPath?: string; + declgenBridgeCodePath?: string; + declFilesPath?: string; + dynamicFiles: string[]; + staticFiles: string[]; + cachePath: string; + byteCodeHarInfo?: Object; + packageVersion: string; +} + +export interface Params { + dependentModuleMap: Map; + projectConfig: ProjectConfig; + tasks: taskInfo[]; +} + +export interface ProjectConfig { + cachePath: string; + bundleName: string; + mainModuleName: string; + projectRootPath: string; +}; + +export enum BuildType { + DECLGEN = 'declgen', + BYTE_CODE_HAR = 'byteCodeHar', + INTEROP_CONTEXT = 'interopContext' +} + +interface taskInfo { + packageName: string; + buildTask: BuildType; + mainModuleName?: string; +} + +export interface AliasConfig { + originalAPIName: string; + isStatic: boolean; +} + +export interface FileInfo { + recordName: string; + baseUrl: string; + absolutePath: string; + abstractPath: string; +} + +export interface RunnerParms { + inputDirs: string[]; + inputFiles: string[]; + outDir: string; + rootDir: string; + customResolveModuleNames?: (moduleName: string[], containingFile: string) => ts.ResolvedModuleFull[]; + customCompilerOptions?: ts.CompilerOptions; + includePaths?: string[]; +} + +export interface DeclFilesConfig { + packageName: string; + files: { + [filePath: string]: DeclFileConfig; + } +} + +interface DeclFileConfig { + declPath: string; + ohmUrl: string; +} + +export interface Params { + dependentModuleMap: Map; + projectConfig: ProjectConfig; + tasks: taskInfo[]; +} + +interface taskInfo { + packageName: string; + buildTask: BuildType +} + +export interface AliasConfig { + originalAPIName: string; + isStatic: boolean; +} + +export interface FileInfo { + recordName: string; + baseUrl: string; + abstractPath: string; +} +export const DECLGEN_CACHE_FILE = 'declgen_cache.json'; diff --git a/compiler/src/interop/src/fast_build/ark_compiler/logger.ts b/compiler/src/interop/src/fast_build/ark_compiler/logger.ts new file mode 100644 index 0000000000000000000000000000000000000000..50872761363d8fe73fb3c48f545604d5b8a3d269 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/logger.ts @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GEN_ABC_PLUGIN_NAME } from './common/ark_define'; +import { + ErrorCode, + SubsystemCode, + ES2ABC_ERROR_MAPPING +} from './error_code'; + +export class CommonLogger { + private static instance: CommonLogger; + private logger: Object; + private hvigorConsoleLoggerMap: { [key in SubsystemCode]?: Object }; + throwArkTsCompilerError: Object; + + private constructor(rollupObject: Object) { + this.logger = rollupObject.share.getLogger(GEN_ABC_PLUGIN_NAME); + this.throwArkTsCompilerError = rollupObject.share.throwArkTsCompilerError; + this.hvigorConsoleLoggerMap = this.initializeHvigorConsoleLoggers(rollupObject); + } + + private initializeHvigorConsoleLoggers(rollupObject: Object): { [key in SubsystemCode]?: Object } { + const loggerMap: { [key in SubsystemCode]?: Object } = {}; + if (typeof rollupObject.share.getHvigorConsoleLogger === 'function') { + loggerMap[SubsystemCode.ETS2BUNDLE] = rollupObject.share.getHvigorConsoleLogger(SubsystemCode.ETS2BUNDLE); + loggerMap[SubsystemCode.ABC2PROGRAM] = rollupObject.share.getHvigorConsoleLogger(SubsystemCode.ABC2PROGRAM); + loggerMap[SubsystemCode.ES2ABC] = rollupObject.share.getHvigorConsoleLogger(SubsystemCode.ES2ABC); + } + return loggerMap; + } + + static getInstance(rollupObject: Object): CommonLogger { + if (!CommonLogger.instance) { + CommonLogger.instance = new CommonLogger(rollupObject); + } + return CommonLogger.instance; + } + + static destroyInstance(): void { + CommonLogger.instance = undefined; + } + + info(...args: string[]): void { + this.logger.info(...args); + } + + debug(...args: string[]): void { + this.logger.debug(...args); + } + + warn(...args: string[]): void { + this.logger.warn(...args); + } + + error(...args: string[]): void { + this.logger.error(...args); + } + + printError(error: LogData | string): void { + if (typeof error === 'string') { + this.logger.error(error); + return; + } + const hvigorConsoleLogger = this.getLoggerFromErrorCode(error.code); + if (hvigorConsoleLogger) { + hvigorConsoleLogger.printError(error); + } else { + this.logger.error(error.toString()); + } + } + + printErrorAndExit(error: LogData | string): void { + if (typeof error === 'string') { + this.throwArkTsCompilerError(error); + return; + } + const hvigorConsoleLogger = this.getLoggerFromErrorCode(error.code); + if (hvigorConsoleLogger) { + hvigorConsoleLogger.printErrorAndExit(error); + } else { + this.throwArkTsCompilerError(error.toString()); + } + } + + private isValidErrorCode(errorCode: string): boolean { + return /^\d{8}$/.test(errorCode); + } + + private getLoggerFromErrorCode(errorCode: string): Object | undefined { + if (!this.isValidErrorCode(errorCode)) { + return undefined; + } + const subsystemCode = errorCode.slice(0, 3); + return this.hvigorConsoleLoggerMap[subsystemCode]; + } +} + +export class LogDataFactory { + + static newInstance( + code: ErrorCode, + description: string, + cause: string = '', + position: string = '', + solutions: string[] = [] + ): LogData { + const data: LogData = new LogData(code, description, cause, position, solutions); + return data; + } + + /** + * Parses an es2abc error string and returns a LogData instance. + * @param error The es2abc error string to parse. + * @returns A LogData instance or undefined if no matching error is found. + */ + static newInstanceFromEs2AbcError(error: string): LogData | undefined { + for (const prefix in ES2ABC_ERROR_MAPPING) { + if (error.startsWith(prefix)) { + const { code, description, solutions } = ES2ABC_ERROR_MAPPING[prefix]; + const cause = error.replace(prefix, ''); + return LogDataFactory.newInstance(code, description, cause, '', solutions); + } + } + return undefined; + } +} + +export class LogData { + + code: string; + description: string; + cause: string; + position: string; + solutions: string[]; + + constructor( + code: ErrorCode, + description: string, + cause: string = '', + position: string = '', + solutions: string[] = [ArkTSInternalErrorSolution] + ) { + this.code = code; + this.description = description; + this.cause = cause; + this.position = position; + this.solutions = solutions; + } + + toString(): string { + let errorString = `ERROR Code: ${this.code} ${this.description}\n`; + + if (this.cause || this.position) { + errorString += `Error Message: ${this.cause}`; + if (this.position) { + errorString += ` ${this.position}`; + } + errorString += '\n\n'; + } + + if (this.solutions.length > 0 && this.solutions[0] !== '') { + errorString += `* Try the following: \n${this.solutions.map(str => ` > ${str}`).join('\n')}\n`; + } + + return errorString; + } +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/module/module_build_mode.ts b/compiler/src/interop/src/fast_build/ark_compiler/module/module_build_mode.ts new file mode 100644 index 0000000000000000000000000000000000000000..4dd4f7ba9cae12f3fabe990440e560d01d0219a8 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/module/module_build_mode.ts @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 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 { ModuleMode } from './module_mode'; +import { + isEs2Abc, + isTs2Abc +} from '../../../ark_utils'; +import { SourceMapGenerator } from '../generate_sourcemap'; +import { + TS2ABC, + ES2ABC +} from '../common/ark_define'; +import { + ArkTSInternalErrorDescription, + ErrorCode +} from '../error_code'; +import { + LogData, + LogDataFactory +} from '../logger'; + +export class ModuleBuildMode extends ModuleMode { + constructor(rollupObject: Object) { + super(rollupObject); + } + + generateAbc(rollupObject: Object, parentEvent: Object): void { + this.prepareForCompilation(rollupObject, parentEvent); + SourceMapGenerator.getInstance().buildModuleSourceMapInfo(parentEvent); + this.executeArkCompiler(parentEvent); + } + + executeArkCompiler(parentEvent: Object): void { + if (isEs2Abc(this.projectConfig)) { + this.generateEs2AbcCmd(); + this.addCacheFileArgs(); + this.genDescriptionsForMergedEs2abc(!!this.projectConfig.byteCodeHarInfo); + this.generateMergedAbcOfEs2Abc(parentEvent); + } else if (isTs2Abc(this.projectConfig)) { + this.filterModulesByHashJson(); + const splittedModules: any[] = this.getSplittedModulesByNumber(); + this.invokeTs2AbcWorkersToGenProto(splittedModules); + this.processTs2abcWorkersToGenAbc(); + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_INVALID_COMPILE_MODE, + ArkTSInternalErrorDescription, + 'Invalid compilation mode. ' + + `ProjectConfig.pandaMode should be either ${TS2ABC} or ${ES2ABC}.` + ); + this.logger.printErrorAndExit(errInfo); + } + } +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/module/module_coldreload_mode.ts b/compiler/src/interop/src/fast_build/ark_compiler/module/module_coldreload_mode.ts new file mode 100644 index 0000000000000000000000000000000000000000..72593464e8bdb47c7b25c4e6b8c17a3cc4af510d --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/module/module_coldreload_mode.ts @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs'; +import path from 'path'; + +import { ModuleMode } from './module_mode'; +import { + blue, + reset, + MODULES_ABC, + SOURCEMAPS, + SYMBOLMAP +} from '../common/ark_define'; +import { isJsonSourceFile } from '../utils'; +import { + mkdirsSync, + toUnixPath, + validateFilePathLength +} from '../../../utils'; +import { + createAndStartEvent, + stopEvent +} from '../../../ark_utils'; +import { SourceMapGenerator } from '../generate_sourcemap'; +import { + ArkTSInternalErrorDescription, + ErrorCode +} from '../error_code'; +import { + LogData, + LogDataFactory +} from '../logger'; + +let isFirstBuild: boolean = true; + +export class ModuleColdreloadMode extends ModuleMode { + symbolMapFilePath: string; + constructor(rollupObject: Object) { + super(rollupObject); + if (this.projectConfig.oldMapFilePath) { + this.symbolMapFilePath = path.join(this.projectConfig.oldMapFilePath, SYMBOLMAP); + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_COLD_RELOAD_FAILED_INCORRECT_SYMBOL_MAP_CONFIG, + ArkTSInternalErrorDescription, + 'Coldreload failed, symbolMap file is not correctly configured.' + ); + this.logger.printErrorAndExit(errInfo); + } + } + + generateAbc(rollupObject: Object, parentEvent: Object): void { + isFirstBuild = this.projectConfig.isFirstBuild; + if (isFirstBuild) { + this.compileAllFiles(rollupObject, parentEvent); + } else { + this.compileChangeListFiles(rollupObject, parentEvent); + } + } + + addColdReloadArgs(): void { + if (isFirstBuild) { + this.cmdArgs.push('--dump-symbol-table'); + this.cmdArgs.push(`"${this.symbolMapFilePath}"`); + return; + } + this.addCacheFileArgs(); + this.cmdArgs.push('--input-symbol-table'); + this.cmdArgs.push(`"${this.symbolMapFilePath}"`); + this.cmdArgs.push('--cold-reload'); + } + + private compileAllFiles(rollupObject: Object, parentEvent: Object): void { + this.prepareForCompilation(rollupObject, parentEvent); + SourceMapGenerator.getInstance().buildModuleSourceMapInfo(parentEvent); + this.generateAbcByEs2abc(parentEvent, !!this.projectConfig.byteCodeHarInfo); + } + + private compileChangeListFiles(rollupObject: Object, parentEvent: Object): void { + if (!fs.existsSync(this.projectConfig.changedFileList)) { + this.logger.debug(blue, `ArkTS: Cannot find file: ${ + this.projectConfig.changedFileList}, skip cold reload build`, reset); + return; + } + + const changedFileListJson: string = fs.readFileSync(this.projectConfig.changedFileList).toString(); + const changedFileList: Array = JSON.parse(changedFileListJson).modifiedFiles; + if (typeof changedFileList === 'undefined' || changedFileList.length === 0) { + this.logger.debug(blue, `ArkTS: No changed files found, skip cold reload build`, reset); + return; + } + + let needColdreloadBuild: boolean = true; + let changedFileListInAbsolutePath: Array = changedFileList.map((file) => { + if (isJsonSourceFile(file)) { + this.logger.debug(blue, `ARKTS: json source file: ${file} changed, skip cold reload build!`, reset); + needColdreloadBuild = false; + } + return path.join(this.projectConfig.projectPath, file); + }); + + if (!needColdreloadBuild) { + return; + } + + const eventCollectModuleFileList = createAndStartEvent(parentEvent, 'collect module file list'); + this.collectModuleFileList(rollupObject, changedFileListInAbsolutePath[Symbol.iterator]()); + stopEvent(eventCollectModuleFileList); + + if (!fs.existsSync(this.projectConfig.patchAbcPath)) { + mkdirsSync(this.projectConfig.patchAbcPath); + } + + this.updateSourceMapFromFileList( + SourceMapGenerator.getInstance().isNewSourceMaps() ? changedFileListInAbsolutePath : changedFileList, + parentEvent); + const outputABCPath: string = path.join(this.projectConfig.patchAbcPath, MODULES_ABC); + validateFilePathLength(outputABCPath, this.logger); + this.moduleAbcPath = outputABCPath; + // During incremental compilation, the bytecode har path must be blocked from being written to filesInfo. + this.generateAbcByEs2abc(parentEvent, false); + } + + private updateSourceMapFromFileList(fileList: Array, parentEvent: Object): void { + const eventUpdateSourceMapFromFileList = createAndStartEvent(parentEvent, 'update source map from file list'); + const sourceMapGenerator = SourceMapGenerator.getInstance(); + const relativeProjectPath: string = this.projectConfig.projectPath.slice( + this.projectConfig.projectRootPath.length + path.sep.length); + let coldReloadSourceMap: Object = {}; + for (const file of fileList) { + if (sourceMapGenerator.isNewSourceMaps()) { + validateFilePathLength(file, this.logger); + coldReloadSourceMap[sourceMapGenerator.genKey(file)] = sourceMapGenerator.getSourceMap(file); + } else { + const sourceMapPath: string = toUnixPath(path.join(relativeProjectPath, file)); + validateFilePathLength(sourceMapPath, this.logger); + coldReloadSourceMap[sourceMapPath] = sourceMapGenerator.getSourceMap(sourceMapPath); + } + } + if (!sourceMapGenerator.isNewSourceMaps()) { + sourceMapGenerator.modifySourceMapKeyToCachePath(coldReloadSourceMap); + } + const sourceMapFilePath: string = path.join(this.projectConfig.patchAbcPath, SOURCEMAPS); + validateFilePathLength(sourceMapFilePath, this.logger); + fs.writeFileSync(sourceMapFilePath, + JSON.stringify(coldReloadSourceMap, null, 2), 'utf-8'); + stopEvent(eventUpdateSourceMapFromFileList); + } + + private generateAbcByEs2abc(parentEvent: Object, includeByteCodeHarInfo: boolean): void { + this.generateEs2AbcCmd(); + this.addColdReloadArgs(); + this.genDescriptionsForMergedEs2abc(includeByteCodeHarInfo); + this.generateMergedAbcOfEs2Abc(parentEvent); + } +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/module/module_hotfix_mode.ts b/compiler/src/interop/src/fast_build/ark_compiler/module/module_hotfix_mode.ts new file mode 100644 index 0000000000000000000000000000000000000000..42d1b0e5de1bf4e4f4fce661a3ed147e9000ba96 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/module/module_hotfix_mode.ts @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023 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 path from 'path'; + +import { ModuleMode } from './module_mode'; +import { PATCH_SYMBOL_TABLE } from '../common/ark_define'; +import { getEs2abcFileThreadNumber } from '../utils'; +import { SourceMapGenerator } from '../generate_sourcemap'; + +export class ModuleHotfixMode extends ModuleMode { + patch: boolean; + inOldSymbolTablePath: string; + enableMap: boolean; + + constructor(rollupObject: Object) { + super(rollupObject); + this.patch = false; + this.inOldSymbolTablePath = ''; + this.enableMap = false; + } + + generateAbc(rollupObject: Object, parentEvent: Object): void { + this.patch = this.projectConfig.patch || false; + this.inOldSymbolTablePath = this.projectConfig.inOldSymbolTablePath || this.projectConfig.projectRootPath; + this.enableMap = this.projectConfig.enableMap || false; + this.prepareForCompilation(rollupObject, parentEvent); + SourceMapGenerator.getInstance().buildModuleSourceMapInfo(parentEvent); + + this.generateEs2AbcCmdForHotfix(); + this.genDescriptionsForMergedEs2abc(!!this.projectConfig.byteCodeHarInfo); + this.generateMergedAbcOfEs2Abc(parentEvent); + } + + private generateEs2AbcCmdForHotfix() { + const fileThreads = getEs2abcFileThreadNumber(); + this.cmdArgs.push(`"@${this.filesInfoPath}"`); + this.cmdArgs.push('--npm-module-entry-list'); + this.cmdArgs.push(`"${this.npmEntriesInfoPath}"`); + this.cmdArgs.push('--output'); + this.cmdArgs.push(`"${this.moduleAbcPath}"`); + this.cmdArgs.push('--file-threads'); + this.cmdArgs.push(`"${fileThreads}"`); + this.cmdArgs.push(`"--target-api-version=${this.projectConfig.compatibleSdkVersion}"`); + + if (this.projectConfig.patch) { + const oldHapSymbolTable: string = path.join(this.projectConfig.inOldSymbolTablePath, PATCH_SYMBOL_TABLE); + this.cmdArgs.push('--input-symbol-table'); + this.cmdArgs.push(`"${oldHapSymbolTable}"`); + this.cmdArgs.push('--generate-patch'); + } + + if (this.projectConfig.enableMap) { + // when generating map, cache is forbiden to avoid uncomplete symbol table + const oldHapSymbolTable: string = path.join(this.projectConfig.inOldSymbolTablePath, PATCH_SYMBOL_TABLE); + this.cmdArgs.push('--dump-symbol-table'); + this.cmdArgs.push(`"${oldHapSymbolTable}"`); + } + + this.cmdArgs.push('--merge-abc'); + } +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/module/module_hotreload_mode.ts b/compiler/src/interop/src/fast_build/ark_compiler/module/module_hotreload_mode.ts new file mode 100644 index 0000000000000000000000000000000000000000..44d397d3dd9f8917d91a489e0aa39d77d6fa20f2 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/module/module_hotreload_mode.ts @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2023 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 fs from 'fs'; +import path from 'path'; + +import { ModuleMode } from './module_mode'; +import { + blue, + reset, + MODULES_ABC, + SOURCEMAPS, + SYMBOLMAP +} from '../common/ark_define'; +import { isJsonSourceFile } from '../utils'; +import { + mkdirsSync, + toUnixPath, + validateFilePathLength +} from '../../../utils'; +import { + createAndStartEvent, + stopEvent +} from '../../../ark_utils'; +import { SourceMapGenerator } from '../generate_sourcemap'; +import { + ArkTSInternalErrorDescription, + ErrorCode +} from '../error_code'; +import { + LogData, + LogDataFactory +} from '../logger'; + +let isFirstBuild: boolean = true; + +export class ModuleHotreloadMode extends ModuleMode { + symbolMapFilePath: string; + constructor(rollupObject: Object) { + super(rollupObject); + if (this.projectConfig.oldMapFilePath) { + this.symbolMapFilePath = path.join(this.projectConfig.oldMapFilePath, SYMBOLMAP); + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_HOT_RELOAD_FAILED_INCORRECT_SYMBOL_MAP_CONFIG, + ArkTSInternalErrorDescription, + 'Hot Reload failed, symbolMap file is not correctly configured.' + ); + this.logger.printErrorAndExit(errInfo); + } + } + + generateAbc(rollupObject: Object, parentEvent: Object): void { + // To support hotreload of multiple HSP modules, rollup no longer runs in watch mode. + // In this case isFirstBuild needs to be passed in by hvigor, because if multiple HSP + // module build tasks are running in the same worker in the IDE, the isFirstBuild of + // the later build task will be overwritten by the first build task to false. + if (rollupObject.share.projectConfig.watchMode !== 'true') { + isFirstBuild = this.projectConfig.isFirstBuild; + } + if (isFirstBuild) { + this.compileAllFiles(rollupObject, parentEvent); + if (rollupObject.share.projectConfig.watchMode === 'true') { + isFirstBuild = false; + } + } else { + this.compileChangeListFiles(rollupObject, parentEvent); + } + } + + addHotReloadArgs(): void { + if (isFirstBuild) { + this.cmdArgs.push('--dump-symbol-table'); + this.cmdArgs.push(`"${this.symbolMapFilePath}"`); + return; + } + this.addCacheFileArgs(); + this.cmdArgs.push('--input-symbol-table'); + this.cmdArgs.push(`"${this.symbolMapFilePath}"`); + this.cmdArgs.push('--hot-reload'); + } + + private compileAllFiles(rollupObject: Object, parentEvent: Object): void { + this.prepareForCompilation(rollupObject, parentEvent); + SourceMapGenerator.getInstance().buildModuleSourceMapInfo(parentEvent); + this.generateAbcByEs2abc(parentEvent, !!this.projectConfig.byteCodeHarInfo); + } + + private compileChangeListFiles(rollupObject: Object, parentEvent: Object): void { + if (!fs.existsSync(this.projectConfig.changedFileList)) { + this.logger.debug(blue, `ArkTS: Cannot find file: ${ + this.projectConfig.changedFileList}, skip hot reload build`, reset); + return; + } + + const changedFileListJson: string = fs.readFileSync(this.projectConfig.changedFileList).toString(); + const { + changedFileListVersion, + changedFileList + } = this.parseChangedFileListJson(changedFileListJson); + if (typeof changedFileList === 'undefined' || changedFileList.length === 0) { + this.logger.debug(blue, `ArkTS: No changed files found, skip hot reload build`, reset); + return; + } + + let needHotreloadBuild: boolean = true; + let changedFileListInAbsolutePath: Array = changedFileList.map((file) => { + if (isJsonSourceFile(file)) { + this.logger.debug(blue, `ARKTS: json source file: ${file} changed, skip hot reload build!`, reset); + needHotreloadBuild = false; + } + return changedFileListVersion === 'v1' ? path.join(this.projectConfig.projectPath, file) : file; + }); + + if (!needHotreloadBuild) { + return; + } + + const eventCollectModuleFileList = createAndStartEvent(parentEvent, 'collect module file list'); + this.collectModuleFileList(rollupObject, changedFileListInAbsolutePath[Symbol.iterator]()); + stopEvent(eventCollectModuleFileList); + + if (!fs.existsSync(this.projectConfig.patchAbcPath)) { + mkdirsSync(this.projectConfig.patchAbcPath); + } + + this.updateSourceMapFromFileList( + SourceMapGenerator.getInstance().isNewSourceMaps() ? changedFileListInAbsolutePath : changedFileList, + parentEvent); + const outputABCPath: string = path.join(this.projectConfig.patchAbcPath, MODULES_ABC); + validateFilePathLength(outputABCPath, this.logger); + this.moduleAbcPath = outputABCPath; + // During incremental compilation, the bytecode har path must be blocked from being written to filesInfo. + this.generateAbcByEs2abc(parentEvent, false); + } + + private parseChangedFileListJson(changedFileListJson: string): object { + const changedFileList = JSON.parse(changedFileListJson); + if (Object.prototype.hasOwnProperty.call(changedFileList, 'modifiedFilesV2')) { + return { + 'changedFileListVersion': 'v2', + 'changedFileList': changedFileList.modifiedFilesV2 + .filter(file => Object.prototype.hasOwnProperty.call(file, 'filePath')) + .map(file => file.filePath) + }; + } else if (Object.prototype.hasOwnProperty.call(changedFileList, 'modifiedFiles')) { + return { + 'changedFileListVersion': 'v1', + 'changedFileList': changedFileList.modifiedFiles + }; + } else { + return { + 'changedFileListVersion': '', + 'changedFileList': [] + }; + } + } + + private updateSourceMapFromFileList(fileList: Array, parentEvent: Object): void { + const eventUpdateSourceMapFromFileList = createAndStartEvent(parentEvent, 'update source map from file list'); + const sourceMapGenerator = SourceMapGenerator.getInstance(); + const relativeProjectPath: string = this.projectConfig.projectPath.slice( + this.projectConfig.projectRootPath.length + path.sep.length); + let hotReloadSourceMap: Object = {}; + for (const file of fileList) { + if (sourceMapGenerator.isNewSourceMaps()) { + validateFilePathLength(file, this.logger); + hotReloadSourceMap[sourceMapGenerator.genKey(file)] = sourceMapGenerator.getSourceMap(file); + } else { + const sourceMapPath: string = toUnixPath(path.join(relativeProjectPath, file)); + validateFilePathLength(sourceMapPath, this.logger); + hotReloadSourceMap[sourceMapPath] = sourceMapGenerator.getSourceMap(sourceMapPath); + } + } + if (!sourceMapGenerator.isNewSourceMaps()) { + sourceMapGenerator.modifySourceMapKeyToCachePath(hotReloadSourceMap); + } + const sourceMapFilePath: string = path.join(this.projectConfig.patchAbcPath, SOURCEMAPS); + validateFilePathLength(sourceMapFilePath, this.logger); + fs.writeFileSync(sourceMapFilePath, + JSON.stringify(hotReloadSourceMap, null, 2), 'utf-8'); + stopEvent(eventUpdateSourceMapFromFileList); + } + + private generateAbcByEs2abc(parentEvent: Object, includeByteCodeHarInfo: boolean): void { + this.generateEs2AbcCmd(); + this.addHotReloadArgs(); + this.genDescriptionsForMergedEs2abc(includeByteCodeHarInfo); + this.generateMergedAbcOfEs2Abc(parentEvent); + } +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/module/module_mode.ts b/compiler/src/interop/src/fast_build/ark_compiler/module/module_mode.ts new file mode 100644 index 0000000000000000000000000000000000000000..b159966457bd6c060037737fa99f3bf1eb7d9653 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/module/module_mode.ts @@ -0,0 +1,1040 @@ +/* + * Copyright (c) 2023 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 childProcess from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import cluster from 'cluster'; + +import { + COMMONJS, + COMPILE_CONTEXT_INFO_JSON, + ESM, + ESMODULE, + EXTNAME_CJS, + EXTNAME_ETS, + EXTNAME_JS, + EXTNAME_JSON, + EXTNAME_MJS, + EXTNAME_PROTO_BIN, + EXTNAME_TS, + EXTNAME_TXT, + FAIL, + SUCCESS, + FILESINFO, + FILESINFO_TXT, + MAX_WORKER_NUMBER, + MODULES_ABC, + MODULES_CACHE, + NPM_ENTRIES_PROTO_BIN, + NPMENTRIES_TXT, + OH_MODULES, + PACKAGES, + PROTO_FILESINFO_TXT, + PROTOS, + red, + reset, + SOURCEMAPS, + SOURCEMAPS_JSON, + WIDGETS_ABC, + TS2ABC, + ES2ABC, + ETS, + TS, + JS +} from '../common/ark_define'; +import { + needAotCompiler, + isMasterOrPrimary, + isAotMode, + isDebug +} from '../utils'; +import { CommonMode } from '../common/common_mode'; +import { + handleObfuscatedFilePath, + enableObfuscateFileName, + enableObfuscatedFilePathConfig +} from '../common/ob_config_resolver'; +import { + changeFileExtension, + getEs2abcFileThreadNumber, + isCommonJsPluginVirtualFile, + isCurrentProjectFiles, + shouldETSOrTSFileTransformToJS +} from '../utils'; +import { + isPackageModulesFile, + mkdirsSync, + toUnixPath, + toHashData, + validateFilePathLength +} from '../../../utils'; +import { + getPackageInfo, + getNormalizedOhmUrlByFilepath, + getOhmUrlByFilepath, + getOhmUrlByExternalPackage, + isTs2Abc, + isEs2Abc, + createAndStartEvent, + stopEvent, + transformOhmurlToPkgName, + transformOhmurlToRecordName, +} from '../../../ark_utils'; +import { + generateAot, + FaultHandler +} from '../../../gen_aot'; +import { + NATIVE_MODULE +} from '../../../pre_define'; +import { + sharedModuleSet +} from '../check_shared_module'; +import { SourceMapGenerator } from '../generate_sourcemap'; +import { MemoryMonitor } from '../../meomry_monitor/rollup-plugin-memory-monitor'; +import { MemoryDefine } from '../../meomry_monitor/memory_define'; +import { + ArkTSInternalErrorDescription, + ArkTSErrorDescription, + ErrorCode +} from '../error_code'; +import { + LogData, + LogDataFactory +} from '../logger'; +import { + ArkTSEvolutionModule, + arkTSModuleMap, + genCachePathForBridgeCode, + getDeclgenBridgeCodePath, + isArkTSEvolutionFile, + pkgDeclFilesConfig +} from '../interop/process_arkts_evolution'; +import { + FileManager, + isMixCompile +} from '../interop/interop_manager'; + +export class ModuleInfo { + filePath: string; + cacheFilePath: string; + recordName: string; + isCommonJs: boolean; + sourceFile: string; + packageName: string; + originSourceLang: string; + + constructor(filePath: string, cacheFilePath: string, isCommonJs: boolean, recordName: string, sourceFile: string, + packageName: string, originSourceLang: string + ) { + this.filePath = filePath; + this.cacheFilePath = cacheFilePath; + this.recordName = recordName; + this.isCommonJs = isCommonJs; + this.sourceFile = sourceFile; + this.packageName = packageName; + this.originSourceLang = originSourceLang + } +} + +export class PackageEntryInfo { + pkgEntryPath: string; + pkgBuildPath: string; + constructor(pkgEntryPath: string, pkgBuildPath: string) { + this.pkgEntryPath = pkgEntryPath; + this.pkgBuildPath = pkgBuildPath; + } +} + +export class ModuleMode extends CommonMode { + moduleInfos: Map; + pkgEntryInfos: Map; + hashJsonObject: Object; + filesInfoPath: string; + npmEntriesInfoPath: string; + moduleAbcPath: string; + sourceMapPath: string; + cacheFilePath: string; + cacheSourceMapPath: string; + workerNumber: number; + npmEntriesProtoFilePath: string; + protoFilePath: string; + filterModuleInfos: Map; + symlinkMap: Object; + useNormalizedOHMUrl: boolean; + compileContextInfoPath: string; + abcPaths: string[] = []; + byteCodeHar: boolean; + + constructor(rollupObject: Object) { + super(rollupObject); + this.moduleInfos = new Map(); + this.pkgEntryInfos = new Map(); + this.hashJsonObject = {}; + this.filesInfoPath = path.join(this.projectConfig.cachePath, FILESINFO_TXT); + this.npmEntriesInfoPath = path.join(this.projectConfig.cachePath, NPMENTRIES_TXT); + const outPutABC: string = this.projectConfig.widgetCompile ? WIDGETS_ABC : MODULES_ABC; + this.moduleAbcPath = path.join(this.projectConfig.aceModuleBuild, outPutABC); + this.sourceMapPath = this.arkConfig.isDebug ? path.join(this.projectConfig.aceModuleBuild, SOURCEMAPS) : + path.join(this.projectConfig.cachePath, SOURCEMAPS); + this.cacheFilePath = path.join(this.projectConfig.cachePath, MODULES_CACHE); + this.cacheSourceMapPath = path.join(this.projectConfig.cachePath, SOURCEMAPS_JSON); + this.workerNumber = MAX_WORKER_NUMBER; + this.npmEntriesProtoFilePath = path.join(this.projectConfig.cachePath, PROTOS, NPM_ENTRIES_PROTO_BIN); + this.protoFilePath = path.join(this.projectConfig.cachePath, PROTOS, PROTO_FILESINFO_TXT); + this.hashJsonObject = {}; + this.filterModuleInfos = new Map(); + this.symlinkMap = rollupObject.share.symlinkMap; + this.useNormalizedOHMUrl = this.isUsingNormalizedOHMUrl(); + if (Object.prototype.hasOwnProperty.call(this.projectConfig, 'byteCodeHarInfo')) { + let byteCodeHarInfo = this.projectConfig.byteCodeHarInfo; + for (const packageName in byteCodeHarInfo) { + const abcPath = toUnixPath(byteCodeHarInfo[packageName].abcPath); + this.abcPaths.push(abcPath); + } + } + this.byteCodeHar = !!this.projectConfig.byteCodeHar; + if (this.useNormalizedOHMUrl) { + this.compileContextInfoPath = this.generateCompileContextInfo(rollupObject); + } + } + + private generateCompileContextInfo(rollupObject: Object): string { + let compileContextInfoPath: string = path.join(this.projectConfig.cachePath, COMPILE_CONTEXT_INFO_JSON); + let compileContextInfo: Object = {}; + let hspPkgNames: Array = []; + for (const hspAliasName in this.projectConfig.hspNameOhmMap) { + let hspPkgName: string = hspAliasName; + if (this.projectConfig.dependencyAliasMap.has(hspAliasName)) { + hspPkgName = this.projectConfig.dependencyAliasMap.get(hspAliasName); + } + hspPkgNames.push(toUnixPath(hspPkgName)); + } + compileContextInfo.hspPkgNames = hspPkgNames; + let compileEntries: Set = new Set(); + let entryObj: Object = this.projectConfig.entryObj; + if (!!this.projectConfig.widgetCompile) { + entryObj = this.projectConfig.cardEntryObj; + } + for (const key in entryObj) { + let moduleId: string = entryObj[key]; + let moduleInfo: Object = rollupObject.getModuleInfo(moduleId); + if (!moduleInfo) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_MODULE_INFO_NOT_FOUND, + ArkTSInternalErrorDescription, + 'Failed to find module info. ' + + `Failed to find module info with '${moduleId}' from the context information.` + ); + this.logger.printErrorAndExit(errInfo); + } + let metaInfo: Object = moduleInfo.meta; + if (!metaInfo) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_META_INFO_NOT_FOUND, + ArkTSInternalErrorDescription, + 'Failed to find meta info. ' + + `Failed to find meta info with '${moduleId}' from the module info.` + ); + this.logger.printErrorAndExit(errInfo); + } + let pkgPath: string = metaInfo.pkgPath; + if (isMixCompile() && isArkTSEvolutionFile(moduleId, metaInfo)) { + pkgPath = path.join(getDeclgenBridgeCodePath(metaInfo.pkgName), metaInfo.pkgName); + } + const pkgParams = { + pkgName: metaInfo.pkgName, + pkgPath, + isRecordName: true, + }; + let recordName: string = getNormalizedOhmUrlByFilepath(moduleId, this.projectConfig, this.logger, pkgParams, + undefined); + compileEntries.add(recordName); + } + this.collectDeclarationFilesEntry(compileEntries, hspPkgNames); + compileContextInfo.compileEntries = Array.from(compileEntries); + if (this.projectConfig.updateVersionInfo) { + compileContextInfo.updateVersionInfo = this.projectConfig.updateVersionInfo; + } else if (this.projectConfig.pkgContextInfo) { + compileContextInfo.pkgContextInfo = this.projectConfig.pkgContextInfo; + } + // The bundleType is 'shared' in cross-app hsp. + if (this.projectConfig.bundleType === 'shared') { + compileContextInfo.needModifyRecord = true; + compileContextInfo.bundleName = this.projectConfig.bundleName; + } + fs.writeFileSync(compileContextInfoPath, JSON.stringify(compileContextInfo), 'utf-8'); + return compileContextInfoPath; + } + + private collectDeclarationFilesEntry(compileEntries: Set, hspPkgNames: Array): void { + if (this.projectConfig.arkRouterMap) { + // Collect bytecode har's declaration files entries in router map, use + // by es2abc for dependency resolution. + this.collectRouterMapEntries(compileEntries, hspPkgNames); + } + if (this.projectConfig.declarationEntry) { + // Collect bytecode har's declaration files entries include dynamic import and workers, use + // by es2abc for dependency resolution. + this.projectConfig.declarationEntry.forEach((ohmurl) => { + let pkgName: string = transformOhmurlToPkgName(ohmurl); + if (!hspPkgNames.includes(pkgName)) { + let recordName: string = transformOhmurlToRecordName(ohmurl); + compileEntries.add(recordName); + } + }); + } + } + + private collectRouterMapEntries(compileEntries: Set, hspPkgNames: Array): void { + this.projectConfig.arkRouterMap.forEach((router) => { + if (router.ohmurl) { + let pkgName: string = transformOhmurlToPkgName(router.ohmurl); + if (!hspPkgNames.includes(pkgName)) { + let recordName: string = transformOhmurlToRecordName(router.ohmurl); + compileEntries.add(recordName); + } + } + }); + } + + prepareForCompilation(rollupObject: Object, parentEvent: Object): void { + const eventPrepareForCompilation = createAndStartEvent(parentEvent, 'preparation for compilation'); + this.collectModuleFileList(rollupObject, rollupObject.getModuleIds()); + this.removeCacheInfo(rollupObject); + stopEvent(eventPrepareForCompilation); + } + + // Write the declaration file information of the 1.1 module file to the disk of the corresponding module + writeDeclFilesConfigJson(pkgName: string): void { + if (!arkTSModuleMap.size) { + return; + } + const arkTSEvolutionModuleInfo: ArkTSEvolutionModule = arkTSModuleMap.get(pkgName); + const declFilesConfigFile: string = toUnixPath(arkTSEvolutionModuleInfo.declFilesPath); + mkdirsSync(path.dirname(declFilesConfigFile)); + fs.writeFileSync(declFilesConfigFile, JSON.stringify(pkgDeclFilesConfig[pkgName], null, 2), 'utf-8'); + } + + collectModuleFileList(module: Object, fileList: IterableIterator): void { + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.PKG_ENTRY_INFOS_MODULE_INFOS); + let moduleInfos: Map = new Map(); + let pkgEntryInfos: Map = new Map(); + for (const moduleId of fileList) { + if (isCommonJsPluginVirtualFile(moduleId) || !isCurrentProjectFiles(moduleId, this.projectConfig)) { + continue; + } + const moduleInfo: Object = module.getModuleInfo(moduleId); + if (moduleInfo.meta.isNodeEntryFile && !this.useNormalizedOHMUrl) { + this.getPackageEntryInfo(moduleId, moduleInfo.meta, pkgEntryInfos); + } + + this.processModuleInfos(moduleId, moduleInfos, moduleInfo.meta); + } + if (!this.useNormalizedOHMUrl) { + this.getDynamicImportEntryInfo(pkgEntryInfos); + } + this.getNativeModuleEntryInfo(pkgEntryInfos); + this.moduleInfos = moduleInfos; + this.pkgEntryInfos = pkgEntryInfos; + MemoryMonitor.stopRecordStage(recordInfo); + } + + private isUsingNormalizedOHMUrl(): boolean { + return !!this.projectConfig.useNormalizedOHMUrl; + } + + private updatePkgEntryInfos(pkgEntryInfos: Map, key: String, value: PackageEntryInfo): void { + if (!pkgEntryInfos.has(key)) { + pkgEntryInfos.set(key, new PackageEntryInfo(key, value)); + } + } + + private getDynamicImportEntryInfo(pkgEntryInfos: Map): void { + if (this.projectConfig.dynamicImportLibInfo) { + const REG_LIB_SO: RegExp = /lib(.+)\.so/; + for (const [pkgName, pkgInfo] of Object.entries(this.projectConfig.dynamicImportLibInfo)) { + if (REG_LIB_SO.test(pkgName)) { + let ohmurl: string = pkgName.replace(REG_LIB_SO, (_, libsoKey) => { + return `@app.${this.projectConfig.bundleName}/${this.projectConfig.moduleName}/${libsoKey}`; + }); + this.updatePkgEntryInfos(pkgEntryInfos, pkgName, ohmurl); + continue; + } + let hspOhmurl: string | undefined = getOhmUrlByExternalPackage(pkgName, this.projectConfig, this.logger, + this.useNormalizedOHMUrl); + if (hspOhmurl !== undefined) { + hspOhmurl = hspOhmurl.replace(/^@(\w+):(.*)/, '@$1.$2'); + this.updatePkgEntryInfos(pkgEntryInfos, pkgName, hspOhmurl); + continue; + } + const entryFile: string = pkgInfo.entryFilePath; + this.getPackageEntryInfo(entryFile, pkgInfo, pkgEntryInfos); + } + } + } + + private getNativeModuleEntryInfo(pkgEntryInfos: Map): void { + for (const item of NATIVE_MODULE) { + let key = '@' + item; + this.updatePkgEntryInfos(pkgEntryInfos, key, '@native.' + item); + } + } + + private getPackageEntryInfo(filePath: string, metaInfo: Object, pkgEntryInfos: Map): void { + if (metaInfo.isLocalDependency) { + const hostModulesInfo: Object = metaInfo.hostModulesInfo; + const pkgBuildPath: string = getOhmUrlByFilepath(filePath, this.projectConfig, this.logger, metaInfo.moduleName); + hostModulesInfo.forEach(hostModuleInfo => { + const hostDependencyName: string = hostModuleInfo.hostDependencyName; + const hostModuleName: string = hostModuleInfo.hostModuleName; + const pkgEntryPath: string = toUnixPath(path.join(`${PACKAGES}@${hostModuleName}`, hostDependencyName)); + if (!pkgEntryInfos.has(pkgEntryPath)) { + pkgEntryInfos.set(pkgEntryPath, new PackageEntryInfo(pkgEntryPath, pkgBuildPath)); + } + this.updatePkgEntryInfos(pkgEntryInfos, hostDependencyName, `@bundle.${pkgBuildPath}`); + }); + return; + } + + if (!metaInfo.pkgPath) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META_PKG_PATH, + ArkTSInternalErrorDescription, + `Failed to get ModuleInfo properties 'meta.pkgPath', moduleId: ${filePath}` + ); + this.logger.printErrorAndExit(errInfo); + } + const pkgPath: string = metaInfo.pkgPath; + let originPkgEntryPath: string = toUnixPath(filePath.replace(pkgPath, '')); + if (originPkgEntryPath.startsWith('/')) { + originPkgEntryPath = originPkgEntryPath.slice(1, originPkgEntryPath.length); + } + const pkgEntryPath: string = toUnixPath(this.getPkgModulesFilePkgName(pkgPath)); + let pkgBuildPath: string = path.join(pkgEntryPath, originPkgEntryPath); + pkgBuildPath = toUnixPath(pkgBuildPath.substring(0, pkgBuildPath.lastIndexOf('.'))); + if (!pkgEntryInfos.has(pkgEntryPath)) { + pkgEntryInfos.set(pkgEntryPath, new PackageEntryInfo(pkgEntryPath, pkgBuildPath)); + } + // create symlink path to actual path mapping in ohpm + if (this.projectConfig.packageDir == OH_MODULES && this.symlinkMap) { + const symlinkEntries: Object = Object.entries(this.symlinkMap); + for (const [actualPath, symlinkPaths] of symlinkEntries) { + if (actualPath === pkgPath) { + (symlinkPaths).forEach((symlink: string) => { + const symlinkPkgEntryPath: string = toUnixPath(this.getPkgModulesFilePkgName(symlink)); + if (!pkgEntryInfos.has(symlinkPkgEntryPath)) { + pkgEntryInfos.set(symlinkPkgEntryPath, new PackageEntryInfo(symlinkPkgEntryPath, pkgEntryPath)); + } + }); + break; + } + } + } + } + + private processModuleInfos(moduleId: string, moduleInfos: Map, metaInfo?: Object): void { + switch (path.extname(moduleId)) { + case EXTNAME_ETS: { + const extName: string = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig, metaInfo) ? EXTNAME_JS : EXTNAME_TS; + this.addModuleInfoItem(moduleId, false, extName, metaInfo, moduleInfos, ETS); + break; + } + case EXTNAME_TS: { + const extName: string = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig, metaInfo) ? EXTNAME_JS : ''; + this.addModuleInfoItem(moduleId, false, extName, metaInfo, moduleInfos, TS); + break; + } + case EXTNAME_JS: + case EXTNAME_MJS: + case EXTNAME_CJS: { + const extName: string = (moduleId.endsWith(EXTNAME_MJS) || moduleId.endsWith(EXTNAME_CJS)) ? EXTNAME_JS : ''; + const isCommonJS: boolean = metaInfo && metaInfo.commonjs && metaInfo.commonjs.isCommonJS; + this.addModuleInfoItem(moduleId, isCommonJS, extName, metaInfo, moduleInfos, JS); + break; + } + case EXTNAME_JSON: { + this.addModuleInfoItem(moduleId, false, '', metaInfo, moduleInfos); + break; + } + default: + break; + } + } + + private handleFileNameObfuscationInModuleInfo(sourceMapGenerator: SourceMapGenerator, isPackageModules: boolean, originalFilePath: string, filePath: string, + sourceFile: string) { + if (!enableObfuscateFileName(isPackageModules, this.projectConfig)) { + if (sourceMapGenerator.isNewSourceMaps()) { + sourceFile = sourceMapGenerator.genKey(originalFilePath); + } + return {filePath: filePath, sourceFile: sourceFile}; + } + + // if release mode, enable obfuscation, enable filename obfuscation -> call mangleFilePath() + filePath = handleObfuscatedFilePath(originalFilePath, isPackageModules, this.projectConfig); + sourceFile = filePath.replace(toUnixPath(this.projectConfig.projectRootPath) + '/', ''); + + if (sourceMapGenerator.isNewSourceMaps()) { + sourceFile = sourceMapGenerator.genKey(originalFilePath); // If the file name is obfuscated, meta info cannot be found. + if (!sourceMapGenerator.sourceMapKeyMappingForObf.get(sourceFile)) { + sourceMapGenerator.saveKeyMappingForObfFileName(originalFilePath); + } + // If the file name is obfuscated, the sourceFile needs to be updated. + sourceFile = sourceMapGenerator.sourceMapKeyMappingForObf.get(sourceFile); + } + return {filePath: filePath, sourceFile: sourceFile}; + } + + private addModuleInfoItem(originalFilePath: string, isCommonJs: boolean, extName: string, + metaInfo: Object, moduleInfos: Map, originSourceLang: string = ""): void { + const sourceMapGenerator: SourceMapGenerator = SourceMapGenerator.getInstance(); + const isPackageModules = isPackageModulesFile(originalFilePath, this.projectConfig); + let filePath: string = originalFilePath; + let sourceFile: string = filePath.replace(this.projectConfig.projectRootPath + path.sep, ''); + const isObfuscateEnabled: boolean = enableObfuscatedFilePathConfig(isPackageModules, this.projectConfig); + if (isObfuscateEnabled) { + const filePathAndSourceFile = this.handleFileNameObfuscationInModuleInfo(sourceMapGenerator, isPackageModules, originalFilePath, filePath, sourceFile); + filePath = filePathAndSourceFile.filePath; + sourceFile = filePathAndSourceFile.sourceFile; + } else { + if (sourceMapGenerator.isNewSourceMaps()) { + sourceFile = sourceMapGenerator.genKey(originalFilePath); + } + } + + let moduleName: string = metaInfo.moduleName; + let recordName: string = ''; + let cacheFilePath: string = isArkTSEvolutionFile(filePath, metaInfo) ? + genCachePathForBridgeCode(originalFilePath, metaInfo, this.projectConfig.cachePath) : + this.genFileCachePath(filePath, this.projectConfig.projectRootPath, this.projectConfig.cachePath, metaInfo); + let packageName: string = ''; + + if (this.useNormalizedOHMUrl) { + packageName = metaInfo.pkgName; + let pkgPath: string = metaInfo.pkgPath; + if (isMixCompile() && isArkTSEvolutionFile(filePath, metaInfo)) { + pkgPath = path.join(getDeclgenBridgeCodePath(metaInfo.pkgName), metaInfo.pkgName); + } + const pkgParams = { + pkgName: packageName, + pkgPath, + isRecordName: true, + }; + recordName = getNormalizedOhmUrlByFilepath(filePath, this.projectConfig, this.logger, pkgParams, undefined); + } else { + recordName = getOhmUrlByFilepath(filePath, this.projectConfig, this.logger, moduleName); + if (isPackageModules) { + packageName = this.getPkgModulesFilePkgName(metaInfo.pkgPath); + } else { + packageName = + metaInfo.isLocalDependency ? moduleName : getPackageInfo(this.projectConfig.aceModuleJsonPath)[1]; + } + } + + if (extName.length !== 0) { + cacheFilePath = changeFileExtension(cacheFilePath, extName); + } + + cacheFilePath = toUnixPath(cacheFilePath); + recordName = toUnixPath(recordName); + packageName = toUnixPath(packageName); + if (!sourceMapGenerator.isNewSourceMaps()) { + sourceFile = cacheFilePath.replace(toUnixPath(this.projectConfig.projectRootPath) + '/', ''); + } + filePath = toUnixPath(filePath); + + moduleInfos.set(filePath, new ModuleInfo(filePath, cacheFilePath, isCommonJs, recordName, sourceFile, packageName, + originSourceLang)); + } + + generateEs2AbcCmd() { + const fileThreads = getEs2abcFileThreadNumber(); + this.cmdArgs.push(`"@${this.filesInfoPath}"`); + if (!this.byteCodeHar) { + this.cmdArgs.push('--npm-module-entry-list'); + this.cmdArgs.push(`"${this.npmEntriesInfoPath}"`); + } + this.cmdArgs.push('--output'); + this.cmdArgs.push(`"${this.moduleAbcPath}"`); + this.cmdArgs.push('--file-threads'); + this.cmdArgs.push(`"${fileThreads}"`); + this.cmdArgs.push('--merge-abc'); + this.cmdArgs.push(`"--target-api-version=${this.projectConfig.compatibleSdkVersion}"`); + if (this.projectConfig.compatibleSdkVersionStage) { + this.cmdArgs.push(`"--target-api-sub-version=${this.projectConfig.compatibleSdkVersionStage}"`); + } + if (this.arkConfig.isBranchElimination) { + this.cmdArgs.push('--branch-elimination'); + } + if (this.projectConfig.transformLib) { + this.cmdArgs.push(`--transform-lib`); + this.cmdArgs.push(`"${this.projectConfig.transformLib}"`); + } + if (this.compileContextInfoPath !== undefined) { + this.cmdArgs.push(`--compile-context-info`); + this.cmdArgs.push(`"${this.compileContextInfoPath}"`); + } + if (this.abcPaths.length > 0 && !this.byteCodeHar) { + this.cmdArgs.push('--enable-abc-input'); + this.cmdArgs.push('--remove-redundant-file'); + } + if (!this.arkConfig.optTryCatchFunc) { + this.cmdArgs.push('--opt-try-catch-func=false'); + } + if (this.projectConfig.allowEtsAnnotations) { + this.cmdArgs.push('--enable-annotations'); + } + if (this.projectConfig.mixCompile) { + this.cmdArgs.push('--enable-ets-implements'); + } + } + + addCacheFileArgs() { + this.cmdArgs.push('--cache-file'); + this.cmdArgs.push(`"@${this.cacheFilePath}"`); + } + + private generateCompileFilesInfo(includeByteCodeHarInfo: boolean): void { + let filesInfo: string = ''; + const cacheFilePathSet: Set = new Set(); + this.moduleInfos.forEach((info) => { + const moduleType: string = info.isCommonJs ? COMMONJS : ESM; + const isSharedModule: boolean = sharedModuleSet.has(info.filePath); + if (cacheFilePathSet.has(info.cacheFilePath)) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_DUPLICATE_FILE_NAMES_ERROR, + ArkTSErrorDescription, + `Failed to generate filesInfo.\n + File ${info.filePath} with the same name in module ${info.packageName}.\n + Please modify the names of these files with the same name in the source code.` + ); + this.logger.printError(errInfo); + } + cacheFilePathSet.add(info.cacheFilePath); + filesInfo += `${info.cacheFilePath};${info.recordName};${moduleType};${info.sourceFile};${info.packageName};` + + `${isSharedModule};${info.originSourceLang}\n`; + }); + if (includeByteCodeHarInfo) { + Object.entries(this.projectConfig.byteCodeHarInfo).forEach(([pkgName, abcInfo]) => { + // es2abc parses file path and pkgName according to the file extension .abc. + // Accurate version replacement requires 'pkgName' to es2abc. + const abcPath: string = toUnixPath(abcInfo.abcPath); + filesInfo += `${abcPath};;;;${pkgName};\n`; + }); + } + + for (const [pkgName, fileInfo] of FileManager.glueCodeFileInfos) { + filesInfo += `${fileInfo.abstractPath};${fileInfo.recordName};${ESM};${fileInfo.abstractPath};${this.projectConfig.entryPackageName};` + + `${false};ts\n`; + } + fs.writeFileSync(this.filesInfoPath, filesInfo, 'utf-8'); + } + + private generateNpmEntriesInfo() { + let entriesInfo: string = ''; + for (const value of this.pkgEntryInfos.values()) { + entriesInfo += `${value.pkgEntryPath}:${value.pkgBuildPath}\n`; + } + fs.writeFileSync(this.npmEntriesInfoPath, entriesInfo, 'utf-8'); + } + + private generateAbcCacheFilesInfo(): void { + let abcCacheFilesInfo: string = ''; + + // generate source file cache + this.moduleInfos.forEach((info) => { + let abcCacheFilePath: string = changeFileExtension(info.cacheFilePath, EXTNAME_PROTO_BIN); + abcCacheFilesInfo += `${info.cacheFilePath};${abcCacheFilePath}\n`; + }); + + // generate npm entries cache + let npmEntriesCacheFilePath: string = changeFileExtension(this.npmEntriesInfoPath, EXTNAME_PROTO_BIN); + abcCacheFilesInfo += `${this.npmEntriesInfoPath};${npmEntriesCacheFilePath}\n`; + if (this.projectConfig.cacheBytecodeHar) { + this.abcPaths.forEach((abcPath) => { + let abcCacheFilePath: string = this.genAbcCacheFilePath(abcPath); + mkdirsSync(path.dirname(abcCacheFilePath)); + abcCacheFilesInfo += `${abcPath};${abcCacheFilePath}\n`; + }); + } + fs.writeFileSync(this.cacheFilePath, abcCacheFilesInfo, 'utf-8'); + } + + // Generate cache file path for bytecode har + private genAbcCacheFilePath(abcPath: string): string { + /** + * The projectTopDir is the path of the main project, the projectRootPath is the project path to which it belongs, + * and the path of bytecode har is within the main project. Therefore, the projectTopDir is used to intercept the + * relative path of bytecodehar. + */ + let relativeAbcPath: string = abcPath.replace(toUnixPath(this.projectConfig.projectTopDir), ''); + let tempPath: string = path.join(this.projectConfig.cachePath, relativeAbcPath); + return changeFileExtension(tempPath, EXTNAME_PROTO_BIN); + } + + genDescriptionsForMergedEs2abc(includeByteCodeHarInfo: boolean): void { + this.generateCompileFilesInfo(includeByteCodeHarInfo); + if (!this.byteCodeHar) { + this.generateNpmEntriesInfo(); + } + this.generateAbcCacheFilesInfo(); + } + + generateMergedAbcOfEs2Abc(parentEvent: Object): void { + // collect data error from subprocess + let logDataList: Object[] = []; + let errMsg: string = ''; + + const genAbcCmd: string = this.cmdArgs.join(' '); + try { + let eventGenAbc: Object; + const child = this.triggerAsync(() => { + eventGenAbc = createAndStartEvent(parentEvent, 'generate merged abc by es2abc (async)', true); + return childProcess.exec(genAbcCmd, { windowsHide: true }); + }); + child.on('close', (code: any) => { + if (code === SUCCESS) { + stopEvent(eventGenAbc, true); + this.triggerEndSignal(); + this.processAotIfNeeded(); + return; + } + for (const logData of logDataList) { + this.logger.printError(logData); + } + if (errMsg !== '') { + this.logger.error(`Error Message: ${errMsg}`); + } + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_ES2ABC_EXECUTION_FAILED, + ArkTSErrorDescription, + 'Failed to execute es2abc.', + '', + ["Please refer to es2abc's error codes."] + ); + this.logger.printErrorAndExit(errInfo); + }); + + child.on('error', (err: any) => { + stopEvent(eventGenAbc, true); + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_ES2ABC_SUBPROCESS_START_FAILED, + ArkTSInternalErrorDescription, + `Failed to initialize or launch the es2abc process. ${err.toString()}` + ); + this.logger.printErrorAndExit(errInfo); + }); + + child.stderr.on('data', (data: any) => { + const logData = LogDataFactory.newInstanceFromEs2AbcError(data.toString()); + if (logData) { + logDataList.push(logData); + } else { + errMsg += data.toString(); + } + }); + + } catch (e) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_EXECUTE_ES2ABC_WITH_ASYNC_HANDLER_FAILED, + ArkTSInternalErrorDescription, + `Failed to execute es2abc with async handler. ${e.toString()}` + ); + this.logger.printErrorAndExit(errInfo); + } + } + + filterModulesByHashJson() { + if (this.hashJsonFilePath.length === 0 || !fs.existsSync(this.hashJsonFilePath)) { + for (const key of this.moduleInfos.keys()) { + this.filterModuleInfos.set(key, this.moduleInfos.get(key)); + } + return; + } + + let updatedJsonObject: Object = {}; + let jsonObject: Object = {}; + let jsonFile: string = ''; + + if (fs.existsSync(this.hashJsonFilePath)) { + jsonFile = fs.readFileSync(this.hashJsonFilePath).toString(); + jsonObject = JSON.parse(jsonFile); + this.filterModuleInfos = new Map(); + for (const [key, value] of this.moduleInfos) { + const cacheFilePath: string = value.cacheFilePath; + const cacheProtoFilePath: string = changeFileExtension(cacheFilePath, EXTNAME_PROTO_BIN); + if (!fs.existsSync(cacheFilePath)) { + this.logger.throwArkTsCompilerError( + `ArkTS:INTERNAL ERROR: Failed to get module cache abc from ${cacheFilePath} in incremental build.` + + 'Please try to rebuild the project.'); + } + if (fs.existsSync(cacheProtoFilePath)) { + const hashCacheFileContentData: string = toHashData(cacheFilePath); + const hashProtoFileContentData: string = toHashData(cacheProtoFilePath); + if (jsonObject[cacheFilePath] === hashCacheFileContentData && + jsonObject[cacheProtoFilePath] === hashProtoFileContentData) { + updatedJsonObject[cacheFilePath] = cacheFilePath; + updatedJsonObject[cacheProtoFilePath] = cacheProtoFilePath; + continue; + } + } + this.filterModuleInfos.set(key, value); + } + } + + this.hashJsonObject = updatedJsonObject; + } + + getSplittedModulesByNumber() { + const result: any = []; + if (this.filterModuleInfos.size < this.workerNumber) { + for (const value of this.filterModuleInfos.values()) { + result.push([value]); + } + return result; + } + + for (let i = 0; i < this.workerNumber; ++i) { + result.push([]); + } + + let pos: number = 0; + for (const value of this.filterModuleInfos.values()) { + const chunk = pos % this.workerNumber; + result[chunk].push(value); + pos++; + } + + return result; + } + + invokeTs2AbcWorkersToGenProto(splittedModules) { + let ts2abcCmdArgs: string[] = this.cmdArgs.slice(0); + ts2abcCmdArgs.push('--output-proto'); + ts2abcCmdArgs.push('--merge-abc'); + ts2abcCmdArgs.push('--input-file'); + if (isMasterOrPrimary()) { + this.setupCluster(cluster); + this.workerNumber = splittedModules.length; + for (let i = 0; i < this.workerNumber; ++i) { + const sn: number = i + 1; + const workerFileName: string = `${FILESINFO}_${sn}${EXTNAME_TXT}`; + const workerData: Object = { + inputs: JSON.stringify(splittedModules[i]), + cmd: ts2abcCmdArgs.join(' '), + workerFileName: workerFileName, + mode: ESMODULE, + cachePath: this.projectConfig.cachePath + }; + this.triggerAsync(() => { + const worker: Object = cluster.fork(workerData); + worker.on('message', (errorMsg) => { + this.logger.error(red, errorMsg.data.toString(), reset); + this.logger.throwArkTsCompilerError('ArkTS:ERROR Failed to execute ts2abc.'); + }); + }); + } + } + } + + processTs2abcWorkersToGenAbc() { + this.generateNpmEntriesInfo(); + let workerCount: number = 0; + if (isMasterOrPrimary()) { + cluster.on('exit', (worker, code, signal) => { + if (code === FAIL) { + this.logger.throwArkTsCompilerError('ArkTS:ERROR Failed to execute ts2abc'); + } + workerCount++; + if (workerCount === this.workerNumber) { + this.generateNpmEntryToGenProto(); + this.generateProtoFilesInfo(); + this.mergeProtoToAbc(); + this.processAotIfNeeded(); + this.afterCompilationProcess(); + } + this.triggerEndSignal(); + }); + if (this.workerNumber === 0) { + // process aot for no source file changed. + this.processAotIfNeeded(); + } + } + } + + private processAotIfNeeded(): void { + if (!needAotCompiler(this.projectConfig)) { + return; + } + let faultHandler: FaultHandler = ((error: string) => { this.logger.throwArkTsCompilerError(error); }); + generateAot(this.arkConfig.arkRootPath, this.moduleAbcPath, this.projectConfig, this.logger, faultHandler); + } + + private genFileCachePath(filePath: string, projectRootPath: string, cachePath: string, metaInfo: Object): string { + filePath = toUnixPath(filePath); + let sufStr: string = ''; + if (metaInfo) { + if (metaInfo.isLocalDependency) { + sufStr = filePath.replace(toUnixPath(metaInfo.belongModulePath), metaInfo.moduleName); + } else { + sufStr = filePath.replace(toUnixPath(metaInfo.belongProjectPath), ''); + } + } else { + sufStr = filePath.replace(toUnixPath(projectRootPath), ''); + } + const output: string = path.join(cachePath, sufStr); + return output; + } + + private getPkgModulesFilePkgName(pkgPath: string) { + pkgPath = toUnixPath(pkgPath); + const packageDir: string = this.projectConfig.packageDir; + const projectRootPath = toUnixPath(this.projectConfig.projectRootPath); + const projectPkgModulesPath: string = toUnixPath(path.join(projectRootPath, packageDir)); + let pkgName: string = ''; + if (pkgPath.includes(projectPkgModulesPath)) { + pkgName = path.join(PACKAGES, pkgPath.replace(projectPkgModulesPath, '')); + } else { + for (const key in this.projectConfig.modulePathMap) { + const value: string = this.projectConfig.modulePathMap[key]; + const fakeModulePkgModulesPath: string = toUnixPath(path.resolve(value, packageDir)); + if (pkgPath.indexOf(fakeModulePkgModulesPath) !== -1) { + const tempFilePath: string = pkgPath.replace(projectRootPath, ''); + pkgName = path.join(`${PACKAGES}@${key}`, + tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1)); + break; + } + } + } + + return pkgName.replace(new RegExp(packageDir, 'g'), PACKAGES); + } + + private generateProtoFilesInfo() { + validateFilePathLength(this.protoFilePath, this.logger); + mkdirsSync(path.dirname(this.protoFilePath)); + let protoFilesInfo: string = ''; + const sortModuleInfos: Object = new Map([...this.moduleInfos].sort()); + for (const value of sortModuleInfos.values()) { + const cacheProtoPath: string = changeFileExtension(value.cacheFilePath, EXTNAME_PROTO_BIN); + protoFilesInfo += `${toUnixPath(cacheProtoPath)}\n`; + } + if (this.pkgEntryInfos.size > 0) { + protoFilesInfo += `${toUnixPath(this.npmEntriesProtoFilePath)}\n`; + } + fs.writeFileSync(this.protoFilePath, protoFilesInfo, 'utf-8'); + } + + private mergeProtoToAbc() { + mkdirsSync(this.projectConfig.aceModuleBuild); + const cmd: string = `"${this.arkConfig.mergeAbcPath}" --input "@${this.protoFilePath}" --outputFilePath "${ + this.projectConfig.aceModuleBuild}" --output ${MODULES_ABC} --suffix protoBin`; + try { + childProcess.execSync(cmd, { windowsHide: true }); + } catch (e) { + this.logger.throwArkTsCompilerError('ArkTS:INTERNAL ERROR: Failed to merge proto file to abc.\n' + + 'Error message:' + e.toString()); + } + } + + private afterCompilationProcess() { + this.writeHashJson(); + } + + private writeHashJson() { + if (this.hashJsonFilePath.length === 0) { + return; + } + + for (const value of this.filterModuleInfos.values()) { + const cacheFilePath: string = value.cacheFilePath; + const cacheProtoFilePath: string = changeFileExtension(cacheFilePath, EXTNAME_PROTO_BIN); + if (!fs.existsSync(cacheFilePath) || !fs.existsSync(cacheProtoFilePath)) { + this.logger.throwArkTsCompilerError( + `ArkTS:ERROR ${cacheFilePath} or ${cacheProtoFilePath} is lost` + ); + } + const hashCacheFileContentData: string = toHashData(cacheFilePath); + const hashCacheProtoContentData: string = toHashData(cacheProtoFilePath); + this.hashJsonObject[cacheFilePath] = hashCacheFileContentData; + this.hashJsonObject[cacheProtoFilePath] = hashCacheProtoContentData; + } + + fs.writeFileSync(this.hashJsonFilePath, JSON.stringify(this.hashJsonObject)); + } + + private generateNpmEntryToGenProto() { + if (this.pkgEntryInfos.size <= 0) { + return; + } + mkdirsSync(path.dirname(this.npmEntriesProtoFilePath)); + const cmd: string = `"${this.arkConfig.js2abcPath}" --compile-npm-entries "${ + this.npmEntriesInfoPath}" "${this.npmEntriesProtoFilePath}"`; + try { + childProcess.execSync(cmd, { windowsHide: true }); + } catch (e) { + this.logger.throwArkTsCompilerError('ArkTS:ERROR failed to generate npm proto file to abc. Error message: ' + e.toString()); + } + } + + private removeCompilationCache(): void { + if (isEs2Abc(this.projectConfig)) { + this.removeEs2abcCompilationCache(); + } else if (isTs2Abc(this.projectConfig)) { + this.removeTs2abcCompilationCache(); + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_INVALID_COMPILE_MODE, + ArkTSInternalErrorDescription, + 'Invalid compilation mode. ' + + `ProjectConfig.pandaMode should be either ${TS2ABC} or ${ES2ABC}.` + ); + this.logger.printErrorAndExit(errInfo); + } + } + + private removeEs2abcCompilationCache(): void { + if (fs.existsSync(this.cacheFilePath)) { + const data: string = fs.readFileSync(this.cacheFilePath, 'utf-8'); + const lines: string[] = data.split(/\r?\n/); + lines.forEach(line => { + const [, abcCacheFilePath]: string[] = line.split(';'); + if (fs.existsSync(abcCacheFilePath)) { + fs.unlinkSync(abcCacheFilePath); + } + }); + fs.unlinkSync(this.cacheFilePath); + } + } + + private removeTs2abcCompilationCache(): void { + if (fs.existsSync(this.hashJsonFilePath)) { + fs.unlinkSync(this.hashJsonFilePath); + } + if (fs.existsSync(this.protoFilePath)) { + const data: string = fs.readFileSync(this.protoFilePath, 'utf-8'); + const lines: string[] = data.split(/\r?\n/); + lines.forEach(line => { + const protoFilePath: string = line; + if (fs.existsSync(protoFilePath)) { + fs.unlinkSync(protoFilePath); + } + }); + fs.unlinkSync(this.protoFilePath); + } + } +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/module/module_preview_mode.ts b/compiler/src/interop/src/fast_build/ark_compiler/module/module_preview_mode.ts new file mode 100644 index 0000000000000000000000000000000000000000..a043986c8dd419401e044ef0edc5feb81a31bbc1 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/module/module_preview_mode.ts @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 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 { ModuleMode } from './module_mode'; +import { + TS2ABC, + ES2ABC +} from '../common/ark_define'; +import { + isEs2Abc, + isTs2Abc +} from '../../../ark_utils'; +import { SourceMapGenerator } from '../generate_sourcemap'; +import { + ArkTSInternalErrorDescription, + ErrorCode +} from '../error_code'; +import { + LogData, + LogDataFactory +} from '../logger'; + +export class ModulePreviewMode extends ModuleMode { + constructor(rollupObject: Object) { + super(rollupObject); + } + + generateAbc(rollupObject: Object, parentEvent: Object): void { + this.prepareForCompilation(rollupObject, parentEvent); + SourceMapGenerator.getInstance().buildModuleSourceMapInfo(parentEvent); + this.executeArkCompiler(parentEvent); + } + + executeArkCompiler(parentEvent: Object): void { + if (isEs2Abc(this.projectConfig)) { + this.generateEs2AbcCmd(); + this.addCacheFileArgs(); + this.genDescriptionsForMergedEs2abc(!!this.projectConfig.byteCodeHarInfo); + this.generateMergedAbcOfEs2Abc(parentEvent); + } else if (isTs2Abc(this.projectConfig)) { + this.filterModulesByHashJson(); + const splittedModules: any[] = this.getSplittedModulesByNumber(); + this.invokeTs2AbcWorkersToGenProto(splittedModules); + this.processTs2abcWorkersToGenAbc(); + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_INVALID_COMPILE_MODE, + ArkTSInternalErrorDescription, + 'Invalid compilation mode. ' + + `ProjectConfig.pandaMode should be either ${TS2ABC} or ${ES2ABC}.` + ); + this.logger.printErrorAndExit(errInfo); + } + } +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/module/module_source_file.ts b/compiler/src/interop/src/fast_build/ark_compiler/module/module_source_file.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd14f7b65b5a56104650477a1955f24be6b2f546 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/module/module_source_file.ts @@ -0,0 +1,778 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use rollupObject 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 ts from 'typescript'; +import fs from 'fs'; +import path from 'path'; +import MagicString from 'magic-string'; +import { + GEN_ABC_PLUGIN_NAME, + PACKAGES, + red, + reset +} from '../common/ark_define'; +import { + getNormalizedOhmUrlByFilepath, + getOhmUrlByByteCodeHar, + getOhmUrlByFilepath, + getOhmUrlByExternalPackage, + getOhmUrlBySystemApiOrLibRequest +} from '../../../ark_utils'; +import { writeFileSyncByNode } from '../../../process_module_files'; +import { + isJsonSourceFile, + isJsSourceFile, + updateSourceMap, + writeFileContentToTempDir +} from '../utils'; +import { toUnixPath } from '../../../utils'; +import { + createAndStartEvent, + getHookEventFactory, + stopEvent +} from '../../../ark_utils'; +import { SourceMapGenerator } from '../generate_sourcemap'; +import { + printObfLogger, + collectSourcesWhiteList, + handlePostObfuscationTasks, + writeObfuscationCaches +} from '../common/ob_config_resolver'; +import { ORIGIN_EXTENTION } from '../process_mock'; +import { + TRANSFORMED_MOCK_CONFIG, + USER_DEFINE_MOCK_CONFIG +} from '../../../pre_define'; +import { + allSourceFilePaths, + compilerOptions, + localPackageSet +} from '../../../ets_checker'; +import { projectConfig } from '../../../../main'; +import { + EventList, + startFilesEvent +} from 'arkguard'; +import { MemoryMonitor } from '../../meomry_monitor/rollup-plugin-memory-monitor'; +import { MemoryDefine } from '../../meomry_monitor/memory_define'; +import { + CommonLogger, + LogData, + LogDataFactory +} from '../logger'; +import { + ArkTSInternalErrorDescription, + ErrorCode +} from '../error_code'; +import { checkIfJsImportingArkts } from '../check_import_module'; +import { + getDeclgenBridgeCodePath, + isArkTSEvolutionFile, + writeBridgeCodeFileSyncByNode +} from '../interop/process_arkts_evolution'; +import { + FileManager, + isMixCompile +} from '../interop/interop_manager'; +import { FileInfo } from '../interop/type'; +import { ARKTS_1_2 } from '../interop/pre_define'; + +const ROLLUP_IMPORT_NODE: string = 'ImportDeclaration'; +const ROLLUP_EXPORTNAME_NODE: string = 'ExportNamedDeclaration'; +const ROLLUP_EXPORTALL_NODE: string = 'ExportAllDeclaration'; +const ROLLUP_DYNAMICIMPORT_NODE: string = 'ImportExpression'; +const ROLLUP_LITERAL_NODE: string = 'Literal'; +export const sourceFileBelongProject = new Map(); + +export class ModuleSourceFile { + private static sourceFiles: ModuleSourceFile[] = []; + private moduleId: string; + private source: string | ts.SourceFile; + private metaInfo: Object; + private isSourceNode: boolean = false; + private isArkTSEvolution: boolean = false; + private static projectConfig: Object; + private static logger: CommonLogger; + private static mockConfigInfo: Object = {}; + private static mockFiles: string[] = []; + private static newMockConfigInfo: Object = {}; + private static transformedHarOrHspMockConfigInfo: Object = {}; + private static mockConfigKeyToModuleInfo: Object = {}; + private static needProcessMock: boolean = false; + private static moduleIdMap: Map = new Map(); + private static isEnvInitialized: boolean = false; + private static hookEventFactory: Object; + + constructor(moduleId: string, source: string | ts.SourceFile, metaInfo: Object) { + this.moduleId = moduleId; + this.source = source; + this.metaInfo = metaInfo; + if (typeof this.source !== 'string') { + this.isSourceNode = true; + } + if (metaInfo?.language === ARKTS_1_2 || (metaInfo && isArkTSEvolutionFile(moduleId, metaInfo))) { + this.isArkTSEvolution = true; + } + } + + static setProcessMock(rollupObject: Object): void { + // only processing mock-config.json5 in preview, OhosTest, or LocalTest mode + if (!(rollupObject.share.projectConfig.isPreview || rollupObject.share.projectConfig.isOhosTest || rollupObject.share.projectConfig.isLocalTest)) { + ModuleSourceFile.needProcessMock = false; + return; + } + + // mockParams is essential, and etsSourceRootPath && mockConfigPath need to be defined in mockParams + // mockParams = { + // "decorator": "name of mock decorator", + // "packageName": "name of mock package", + // "etsSourceRootPath": "path of ets source root", + // "mockConfigPath": "path of mock configuration file" + // "mockConfigKey2ModuleInfo": "moduleInfo of mock-config key" + // } + ModuleSourceFile.needProcessMock = (rollupObject.share.projectConfig.mockParams && + rollupObject.share.projectConfig.mockParams.etsSourceRootPath && + rollupObject.share.projectConfig.mockParams.mockConfigPath) ? true : false; + } + + static collectMockConfigInfo(rollupObject: Object): void { + if (!!rollupObject.share.projectConfig.mockParams.mockConfigKey2ModuleInfo) { + ModuleSourceFile.mockConfigKeyToModuleInfo = rollupObject.share.projectConfig.mockParams.mockConfigKey2ModuleInfo; + } + ModuleSourceFile.mockConfigInfo = require('json5').parse( + fs.readFileSync(rollupObject.share.projectConfig.mockParams.mockConfigPath, 'utf-8')); + for (let mockedTarget in ModuleSourceFile.mockConfigInfo) { + if (ModuleSourceFile.mockConfigInfo[mockedTarget].source) { + ModuleSourceFile.mockFiles.push(ModuleSourceFile.mockConfigInfo[mockedTarget].source); + if (ModuleSourceFile.mockConfigKeyToModuleInfo && ModuleSourceFile.mockConfigKeyToModuleInfo[mockedTarget]) { + ModuleSourceFile.generateTransformedMockInfo(ModuleSourceFile.mockConfigKeyToModuleInfo[mockedTarget], + ModuleSourceFile.mockConfigInfo[mockedTarget].source, mockedTarget, rollupObject); + } + } + } + } + + static addMockConfig(mockConfigInfo: Object, key: string, src: string): void { + if (Object.prototype.hasOwnProperty.call(mockConfigInfo, key)) { + return; + } + + mockConfigInfo[key] = {'source': src}; + } + + static generateTransformedMockInfo(mockModuleInfo: Object, src: string, originKey: string, rollupObject: Object): void { + let useNormalizedOHMUrl: boolean = false; + if (!!rollupObject.share.projectConfig.useNormalizedOHMUrl) { + useNormalizedOHMUrl = rollupObject.share.projectConfig.useNormalizedOHMUrl; + } + let transformedMockTarget: string | undefined = getOhmUrlByExternalPackage(originKey, ModuleSourceFile.projectConfig, + ModuleSourceFile.logger, useNormalizedOHMUrl); + if (transformedMockTarget !== undefined) { + ModuleSourceFile.addMockConfig(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transformedMockTarget, src); + return; + } + if (mockModuleInfo.filePath) { + if (useNormalizedOHMUrl) { + transformedMockTarget = ModuleSourceFile.spliceNormalizedOhmurl(mockModuleInfo, mockModuleInfo.filePath, undefined); + ModuleSourceFile.addMockConfig(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transformedMockTarget, src); + return; + } + transformedMockTarget = getOhmUrlByFilepath(mockModuleInfo.filePath, ModuleSourceFile.projectConfig, + ModuleSourceFile.logger, originKey); + transformedMockTarget = transformedMockTarget.startsWith(PACKAGES) ? `@package:${transformedMockTarget}` : + `@bundle:${transformedMockTarget}`; + ModuleSourceFile.addMockConfig(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transformedMockTarget, src); + return; + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_MOCK_CONFIG_KEY_TO_OHM_URL_CONVERSION_FAILED, + ArkTSInternalErrorDescription, + 'Failed to convert the key in mock-config to ohmurl, ' + + 'because the file path corresponding to the key in mock-config is empty.' + ); + ModuleSourceFile.logger.printError(errInfo); + } + } + + static generateNewMockInfo(originKey: string, transKey: string, rollupObject: Object, importerFile?: string): void { + if (!Object.prototype.hasOwnProperty.call(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transKey) && + !Object.prototype.hasOwnProperty.call(ModuleSourceFile.mockConfigInfo, originKey)) { + return; + } + + let useNormalizedOHMUrl = false; + if (!!rollupObject.share.projectConfig.useNormalizedOHMUrl) { + useNormalizedOHMUrl = rollupObject.share.projectConfig.useNormalizedOHMUrl; + } + let mockFile: string = ModuleSourceFile.transformedHarOrHspMockConfigInfo[transKey] ? + ModuleSourceFile.transformedHarOrHspMockConfigInfo[transKey].source : + ModuleSourceFile.mockConfigInfo[originKey].source; + let mockFilePath: string = `${toUnixPath(rollupObject.share.projectConfig.modulePath)}/${mockFile}`; + let mockFileOhmUrl: string = ''; + if (useNormalizedOHMUrl) { + // For file A that imports file B, the mock file of file B will be located in the same package of file A. So the + // moduleInfo for mock file should be the same with file A. + const targetModuleInfo: Object = rollupObject.getModuleInfo(importerFile); + mockFileOhmUrl = ModuleSourceFile.spliceNormalizedOhmurl(targetModuleInfo, mockFilePath, importerFile); + } else { + mockFileOhmUrl = getOhmUrlByFilepath(mockFilePath, + ModuleSourceFile.projectConfig, + ModuleSourceFile.logger, + rollupObject.share.projectConfig.entryModuleName, + importerFile); + mockFileOhmUrl = mockFileOhmUrl.startsWith(PACKAGES) ? `@package:${mockFileOhmUrl}` : `@bundle:${mockFileOhmUrl}`; + } + + // record mock target mapping for incremental compilation + ModuleSourceFile.addMockConfig(ModuleSourceFile.newMockConfigInfo, transKey, mockFileOhmUrl); + } + + static isMockFile(file: string, rollupObject: Object): boolean { + if (!ModuleSourceFile.needProcessMock) { + return false; + } + + for (let mockFile of ModuleSourceFile.mockFiles) { + let absoluteMockFilePath: string = `${toUnixPath(rollupObject.share.projectConfig.modulePath)}/${mockFile}`; + if (toUnixPath(absoluteMockFilePath) === toUnixPath(file)) { + return true; + } + } + + return false; + } + + static generateMockConfigFile(rollupObject: Object): void { + let transformedMockConfigCache: string = + path.resolve(rollupObject.share.projectConfig.cachePath, `./${TRANSFORMED_MOCK_CONFIG}`); + let transformedMockConfig: string = + path.resolve(rollupObject.share.projectConfig.aceModuleJsonPath, `../${TRANSFORMED_MOCK_CONFIG}`); + let userDefinedMockConfigCache: string = + path.resolve(rollupObject.share.projectConfig.cachePath, `./${USER_DEFINE_MOCK_CONFIG}`); + // full compilation + if (!fs.existsSync(transformedMockConfigCache) || !fs.existsSync(userDefinedMockConfigCache)) { + fs.writeFileSync(transformedMockConfig, JSON.stringify(ModuleSourceFile.newMockConfigInfo)); + fs.copyFileSync(transformedMockConfig, transformedMockConfigCache); + fs.copyFileSync(rollupObject.share.projectConfig.mockParams.mockConfigPath, userDefinedMockConfigCache); + return; + } + + // incremental compilation + const cachedMockConfigInfo: Object = + require('json5').parse(fs.readFileSync(userDefinedMockConfigCache, 'utf-8')); + // If mock-config.json5 is modified, incremental compilation will be disabled + if (JSON.stringify(ModuleSourceFile.mockConfigInfo) !== JSON.stringify(cachedMockConfigInfo)) { + fs.writeFileSync(transformedMockConfig, JSON.stringify(ModuleSourceFile.newMockConfigInfo)); + fs.copyFileSync(transformedMockConfig, transformedMockConfigCache); + fs.copyFileSync(rollupObject.share.projectConfig.mockParams.mockConfigPath, userDefinedMockConfigCache); + return; + } + // During incremental compilation, only at this point is the mocked file imported. + // At this time, the newMockConfigInfo does not match the mockConfig in the cache, + // so the mockConfig in the cache needs to be updated. + const cachedTransformedMockConfigInfo: Object = + require('json5').parse(fs.readFileSync(transformedMockConfigCache, 'utf-8')); + if (JSON.stringify(ModuleSourceFile.newMockConfigInfo) !== JSON.stringify(cachedTransformedMockConfigInfo)) { + ModuleSourceFile.updataCachedTransformedMockConfigInfo(ModuleSourceFile.newMockConfigInfo, cachedTransformedMockConfigInfo, + transformedMockConfigCache, transformedMockConfig); + return; + } + + // if mock-config.json5 is not modified, use the cached mock config mapping file + fs.copyFileSync(transformedMockConfigCache, transformedMockConfig); + } + + static updataCachedTransformedMockConfigInfo(newMockConfig: Object, cachedTransMockConfigInfo: Object, + transMockConfigCachePath: string, transMockConfigPath: string): void { + for (const key in newMockConfig) { + if (!Object.prototype.hasOwnProperty.call(cachedTransMockConfigInfo, key)) { + cachedTransMockConfigInfo[key] = newMockConfig[key]; + } + } + fs.writeFileSync(transMockConfigPath, JSON.stringify(cachedTransMockConfigInfo)); + fs.copyFileSync(transMockConfigPath, transMockConfigCachePath); + } + + static removePotentialMockConfigCache(rollupObject: Object): void { + const transformedMockConfigCache: string = + path.resolve(rollupObject.share.projectConfig.cachePath, `./${TRANSFORMED_MOCK_CONFIG}`); + const userDefinedMockConfigCache: string = + path.resolve(rollupObject.share.projectConfig.cachePath, `./${USER_DEFINE_MOCK_CONFIG}`); + if (fs.existsSync(transformedMockConfigCache)) { + fs.rmSync(transformedMockConfigCache); + } + + if (fs.existsSync(userDefinedMockConfigCache)) { + fs.rmSync(userDefinedMockConfigCache); + } + } + + static newSourceFile(moduleId: string, source: string | ts.SourceFile, metaInfo: Object, singleFileEmit: boolean): void { + if (singleFileEmit) { + ModuleSourceFile.moduleIdMap.set(moduleId, new ModuleSourceFile(moduleId, source, metaInfo)); + } else { + ModuleSourceFile.sourceFiles.push(new ModuleSourceFile(moduleId, source, metaInfo)); + } + } + + static getSourceFileById(moduleId: string): ModuleSourceFile | undefined { + return ModuleSourceFile.moduleIdMap.get(moduleId); + } + + static getSourceFiles(): ModuleSourceFile[] { + return ModuleSourceFile.sourceFiles; + } + + static async processSingleModuleSourceFile(rollupObject: Object, moduleId: string): Promise { + if (!ModuleSourceFile.isEnvInitialized) { + this.initPluginEnv(rollupObject); + + ModuleSourceFile.hookEventFactory = getHookEventFactory(rollupObject.share, 'genAbc', 'moduleParsed'); + ModuleSourceFile.setProcessMock(rollupObject); + if (ModuleSourceFile.needProcessMock) { + ModuleSourceFile.collectMockConfigInfo(rollupObject); + } else { + ModuleSourceFile.removePotentialMockConfigCache(rollupObject); + } + ModuleSourceFile.isEnvInitialized = true; + } + + if (ModuleSourceFile.moduleIdMap.has(moduleId)) { + let moduleSourceFile = ModuleSourceFile.moduleIdMap.get(moduleId); + ModuleSourceFile.moduleIdMap.delete(moduleId); + + if (compilerOptions.needDoArkTsLinter) { + checkIfJsImportingArkts(rollupObject, moduleSourceFile); + } + + if (!rollupObject.share.projectConfig.compileHar || ModuleSourceFile.projectConfig.byteCodeHar) { + //compileHar: compile closed source har of project, which convert .ets to .d.ts and js, doesn't transform module request. + const eventBuildModuleSourceFile = createAndStartEvent(ModuleSourceFile.hookEventFactory, 'build module source files'); + await moduleSourceFile.processModuleRequest(rollupObject, eventBuildModuleSourceFile); + stopEvent(eventBuildModuleSourceFile); + } + const eventWriteSourceFile = createAndStartEvent(ModuleSourceFile.hookEventFactory, 'write source file'); + await moduleSourceFile.writeSourceFile(eventWriteSourceFile); + stopEvent(eventWriteSourceFile); + } + } + + static async processModuleSourceFiles(rollupObject: Object, parentEvent: Object): Promise { + this.initPluginEnv(rollupObject); + + // collect mockConfigInfo + ModuleSourceFile.setProcessMock(rollupObject); + if (ModuleSourceFile.needProcessMock) { + ModuleSourceFile.collectMockConfigInfo(rollupObject); + } else { + ModuleSourceFile.removePotentialMockConfigCache(rollupObject); + } + + const sourceProjectConfig = ModuleSourceFile.projectConfig; + collectSourcesWhiteList(rollupObject, allSourceFilePaths, sourceProjectConfig, ModuleSourceFile.sourceFiles); + + startFilesEvent(EventList.ALL_FILES_OBFUSCATION); + let byteCodeHar = false; + if (Object.prototype.hasOwnProperty.call(sourceProjectConfig, 'byteCodeHar')) { + byteCodeHar = sourceProjectConfig.byteCodeHar; + } + const allFilesObfuscationRecordInfo = MemoryMonitor.recordStage(MemoryDefine.ALL_FILES_OBFUSCATION); + // Sort the collection by file name to ensure binary consistency. + ModuleSourceFile.sortSourceFilesByModuleId(); + sourceProjectConfig.localPackageSet = localPackageSet; + for (const source of ModuleSourceFile.sourceFiles) { + const filesForEach = MemoryMonitor.recordStage(MemoryDefine.FILES_FOR_EACH); + sourceFileBelongProject.set(toUnixPath(source.moduleId), source.metaInfo?.belongProjectPath); + if (!rollupObject.share.projectConfig.compileHar || byteCodeHar) { + // compileHar: compile closed source har of project, which convert .ets to .d.ts and js, doesn't transform module request. + const eventBuildModuleSourceFile = createAndStartEvent(parentEvent, 'build module source files'); + await source.processModuleRequest(rollupObject, eventBuildModuleSourceFile); + stopEvent(eventBuildModuleSourceFile); + } + const eventWriteSourceFile = createAndStartEvent(parentEvent, 'write source file'); + await source.writeSourceFile(eventWriteSourceFile); + stopEvent(eventWriteSourceFile); + MemoryMonitor.stopRecordStage(filesForEach); + } + + await handlePostObfuscationTasks(sourceProjectConfig, ModuleSourceFile.projectConfig, rollupObject, printObfLogger); + MemoryMonitor.stopRecordStage(allFilesObfuscationRecordInfo); + + writeObfuscationCaches(sourceProjectConfig, parentEvent); + + const eventGenerateMockConfigFile = createAndStartEvent(parentEvent, 'generate mock config file'); + if (ModuleSourceFile.needProcessMock) { + ModuleSourceFile.generateMockConfigFile(rollupObject); + } + stopEvent(eventGenerateMockConfigFile); + + ModuleSourceFile.sourceFiles = []; + } + + getModuleId(): string { + return this.moduleId; + } + + private async writeSourceFile(parentEvent: Object): Promise { + if (isMixCompile() && this.isArkTSEvolution) { + await writeBridgeCodeFileSyncByNode( this.source, this.moduleId, this.metaInfo); + return; + } + if (this.isSourceNode && !isJsSourceFile(this.moduleId)) { + await writeFileSyncByNode( this.source, ModuleSourceFile.projectConfig, this.metaInfo, + this.moduleId, parentEvent, ModuleSourceFile.logger); + } else { + await writeFileContentToTempDir(this.moduleId, this.source, ModuleSourceFile.projectConfig, + ModuleSourceFile.logger, parentEvent, this.metaInfo); + } + } + + private getOhmUrl(rollupObject: Object, moduleRequest: string, filePath: string | undefined, + importerFile?: string): string | undefined { + let useNormalizedOHMUrl = false; + if (!!rollupObject.share.projectConfig.useNormalizedOHMUrl) { + useNormalizedOHMUrl = rollupObject.share.projectConfig.useNormalizedOHMUrl; + } + let queryResult = undefined; + let staticOhmUrl: string | undefined = undefined; + + if (rollupObject.share.projectConfig.mixCompile) { + queryResult = FileManager.getInstance().queryOriginApiName(moduleRequest, this.moduleId); + staticOhmUrl = this.tryBuildStaticOhmUrl(queryResult, moduleRequest); + } + if (staticOhmUrl) { + return staticOhmUrl; + } + const api = (queryResult && !queryResult.isStatic) ? queryResult.originalAPIName : moduleRequest; + let systemOrLibOhmUrl = getOhmUrlBySystemApiOrLibRequest(api, ModuleSourceFile.projectConfig, + ModuleSourceFile.logger, importerFile, useNormalizedOHMUrl); + if (systemOrLibOhmUrl !== undefined) { + if (ModuleSourceFile.needProcessMock) { + ModuleSourceFile.generateNewMockInfo(moduleRequest, systemOrLibOhmUrl, rollupObject, importerFile); + } + return systemOrLibOhmUrl; + } + const externalPkgOhmurl: string | undefined = getOhmUrlByExternalPackage(moduleRequest, + ModuleSourceFile.projectConfig, ModuleSourceFile.logger, useNormalizedOHMUrl); + if (externalPkgOhmurl !== undefined) { + if (ModuleSourceFile.needProcessMock) { + ModuleSourceFile.generateNewMockInfo(moduleRequest, externalPkgOhmurl, rollupObject, importerFile); + } + return externalPkgOhmurl; + } + const byteCodeHarOhmurl: string | undefined = getOhmUrlByByteCodeHar(moduleRequest, ModuleSourceFile.projectConfig, + ModuleSourceFile.logger); + if (byteCodeHarOhmurl !== undefined) { + if (ModuleSourceFile.needProcessMock) { + ModuleSourceFile.generateNewMockInfo(moduleRequest, byteCodeHarOhmurl, rollupObject, importerFile); + } + return byteCodeHarOhmurl; + } + if (filePath) { + const targetModuleInfo: Object = rollupObject.getModuleInfo(filePath); + if (!targetModuleInfo) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_GET_MODULE_INFO_FAILED, + ArkTSInternalErrorDescription, + `Failed to get ModuleInfo, moduleId: ${filePath}` + ); + ModuleSourceFile.logger.printError(errInfo); + return undefined; + } + if (!targetModuleInfo.meta) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META, + ArkTSInternalErrorDescription, + `Failed to get ModuleInfo properties 'meta', moduleId: ${filePath}` + ); + ModuleSourceFile.logger.printError(errInfo); + return undefined; + } + let res: string = ''; + if (useNormalizedOHMUrl) { + res = ModuleSourceFile.spliceNormalizedOhmurl(targetModuleInfo, filePath, importerFile); + } else { + const moduleName: string = targetModuleInfo.meta.moduleName; + const ohmUrl: string = + getOhmUrlByFilepath(filePath, ModuleSourceFile.projectConfig, ModuleSourceFile.logger, moduleName, importerFile); + res = ohmUrl.startsWith(PACKAGES) ? `@package:${ohmUrl}` : `@bundle:${ohmUrl}`; + } + if (ModuleSourceFile.needProcessMock) { + // processing cases of har or lib mock targets + ModuleSourceFile.generateNewMockInfo(moduleRequest, res, rollupObject, importerFile); + // processing cases of user-defined mock targets + let mockedTarget: string = toUnixPath(filePath). + replace(toUnixPath(rollupObject.share.projectConfig.modulePath), ''). + replace(`/${rollupObject.share.projectConfig.mockParams.etsSourceRootPath}/`, ''); + ModuleSourceFile.generateNewMockInfo(mockedTarget, res, rollupObject, importerFile); + } + return res; + } + return undefined; + } + + private static generateNormalizedOhmulrForSDkInterop(project: Object, projectFilePath: string): string { + const packageName = project.entryPackageName; + const bundleName = project.pkgContextInfo[packageName].bundleName; + const version = project.pkgContextInfo[packageName].version; + return `${bundleName}&${packageName}/${projectFilePath}&${version}`; + } + + private static spliceNormalizedOhmurl(moduleInfo: Object, filePath: string, importerFile?: string): string { + let pkgPath: string = moduleInfo.meta.pkgPath; + if (isMixCompile() && isArkTSEvolutionFile(filePath, moduleInfo.meta)) { + pkgPath = path.join(getDeclgenBridgeCodePath(moduleInfo.meta.pkgName), moduleInfo.meta.pkgName); + } + const pkgParams = { + pkgName: moduleInfo.meta.pkgName, + pkgPath, + isRecordName: false, + }; + const ohmUrl: string = + getNormalizedOhmUrlByFilepath(filePath, ModuleSourceFile.projectConfig, ModuleSourceFile.logger, pkgParams, + importerFile); + return `@normalized:${ohmUrl}`; + } + + private processJsModuleRequest(rollupObject: Object): void { + const moduleInfo: Object = rollupObject.getModuleInfo(this.moduleId); + const importMap: Object = moduleInfo.importedIdMaps; + const REG_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]([^'"]+)['"]|(?:import)(?:\s*)\(['"]([^'"]+)['"]\)/g; + this.source = ( this.source).replace(REG_DEPENDENCY, (item, staticModuleRequest, dynamicModuleRequest) => { + const moduleRequest: string = staticModuleRequest || dynamicModuleRequest; + const ohmUrl: string | undefined = this.getOhmUrl(rollupObject, moduleRequest, importMap[moduleRequest], this.moduleId); + if (ohmUrl !== undefined) { + item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { + return quotation + ohmUrl + quotation; + }); + } + return item; + }); + this.processJsResourceRequest(); + } + + private processJsResourceRequest(): void { + this.source = (this.source as string) + .replace(/\b__harDefaultBundleName__\b/gi, projectConfig.integratedHsp ? '' : projectConfig.bundleName) + .replace(/\b__harDefaultModuleName__\b/gi, projectConfig.moduleName) + .replace(/\b__harDefaultIntegratedHspType__\b/gi, projectConfig.integratedHsp ? 'true' : 'false') + .replace(/\b__harDefaultPagePath__\b/gi, path.relative(projectConfig.projectPath || '', this.moduleId).replace(/\\/g, '/').replace(/\.js$/, '')); + } + + private async processTransformedJsModuleRequest(rollupObject: Object): Promise { + const moduleInfo: Object = rollupObject.getModuleInfo(this.moduleId); + const importMap: Object = moduleInfo.importedIdMaps; + const code: MagicString = new MagicString( this.source); + // The data collected by moduleNodeMap represents the node dataset of related types. + // The data is processed based on the AST collected during the transform stage. + const moduleNodeMap: Map = + moduleInfo.getNodeByType(ROLLUP_IMPORT_NODE, ROLLUP_EXPORTNAME_NODE, ROLLUP_EXPORTALL_NODE, + ROLLUP_DYNAMICIMPORT_NODE); + + let hasDynamicImport: boolean = false; + if (rollupObject.share.projectConfig.needCoverageInsert && moduleInfo.ast.program) { + // In coverage instrumentation scenario, + // ast from rollup because the data of ast and moduleNodeMap are inconsistent. + moduleInfo.ast.program.body.forEach((node) => { + if (!hasDynamicImport && node.type === ROLLUP_DYNAMICIMPORT_NODE) { + hasDynamicImport = true; + } + if ((node.type === ROLLUP_IMPORT_NODE || node.type === ROLLUP_EXPORTNAME_NODE || + node.type === ROLLUP_EXPORTALL_NODE) && node.source) { + const ohmUrl: string | undefined = + this.getOhmUrl(rollupObject, node.source.value, importMap[node.source.value], this.moduleId); + if (ohmUrl !== undefined) { + code.update(node.source.start, node.source.end, `'${ohmUrl}'`); + } + } + }); + } else { + for (let nodeSet of moduleNodeMap.values()) { + nodeSet.forEach(node => { + if (!hasDynamicImport && node.type === ROLLUP_DYNAMICIMPORT_NODE) { + hasDynamicImport = true; + } + if (node.source) { + if (node.source.type === ROLLUP_LITERAL_NODE) { + const ohmUrl: string | undefined = + this.getOhmUrl(rollupObject, node.source.value, importMap[node.source.value], this.moduleId); + if (ohmUrl !== undefined) { + code.update(node.source.start, node.source.end, `'${ohmUrl}'`); + } + } + } + }); + } + } + + if (hasDynamicImport) { + // update sourceMap + const relativeSourceFilePath: string = this.moduleId.startsWith(ModuleSourceFile.projectConfig.projectRootPath) ? + toUnixPath(this.moduleId.replace(ModuleSourceFile.projectConfig.projectRootPath + path.sep, '')) : + toUnixPath(this.moduleId.replace(this.metaInfo.belongProjectPath, '')); + const updatedMap: Object = code.generateMap({ + source: relativeSourceFilePath, + file: `${path.basename(this.moduleId)}`, + includeContent: false, + hires: true + }); + const sourceMapGenerator = SourceMapGenerator.getInstance(); + const key = sourceMapGenerator.isNewSourceMaps() ? this.moduleId : relativeSourceFilePath; + const sourcemap = await updateSourceMap(sourceMapGenerator.getSourceMap(key), updatedMap); + sourceMapGenerator.fillSourceMapPackageInfo(this.moduleId, sourcemap); + sourceMapGenerator.updateSourceMap(key, sourcemap); + } + + this.source = code.toString(); + } + + private processTransformedTsModuleRequest(rollupObject: Object): void { + const moduleInfo: Object = rollupObject.getModuleInfo(this.moduleId); + const importMap: Object = moduleInfo.importedIdMaps; + let isMockFile: boolean = ModuleSourceFile.isMockFile(this.moduleId, rollupObject); + + const moduleNodeTransformer: ts.TransformerFactory = context => { + const visitor: ts.Visitor = node => { + node = ts.visitEachChild(node, visitor, context); + // staticImport node + if (ts.isImportDeclaration(node) || (ts.isExportDeclaration(node) && node.moduleSpecifier)) { + // moduleSpecifier.getText() returns string carrying on quotation marks which the importMap's key does not, + // so we need to remove the quotation marks from moduleRequest. + const moduleRequest: string = (node.moduleSpecifier! as ts.StringLiteral).text.replace(/'|"/g, ''); + let ohmUrl: string | undefined = this.getOhmUrl(rollupObject, moduleRequest, importMap[moduleRequest], this.moduleId); + if (ohmUrl !== undefined) { + // the import module are added with ".origin" at the end of the ohm url in every mock file. + const realOhmUrl: string = isMockFile ? `${ohmUrl}${ORIGIN_EXTENTION}` : ohmUrl; + if (isMockFile) { + ModuleSourceFile.addMockConfig(ModuleSourceFile.newMockConfigInfo, realOhmUrl, ohmUrl); + } + const modifiers: readonly ts.Modifier[] = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + if (ts.isImportDeclaration(node)) { + return ts.factory.createImportDeclaration(modifiers, + node.importClause, ts.factory.createStringLiteral(realOhmUrl)); + } else { + return ts.factory.createExportDeclaration(modifiers, + node.isTypeOnly, node.exportClause, ts.factory.createStringLiteral(realOhmUrl)); + } + } + } + // dynamicImport node + if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) { + const moduleRequest: string = node.arguments[0].getText().replace(/'|"/g, ''); + const ohmUrl: string | undefined = this.getOhmUrl(rollupObject, moduleRequest, importMap[moduleRequest], this.moduleId); + if (ohmUrl !== undefined) { + const args: ts.Expression[] = [...node.arguments]; + args[0] = ts.factory.createStringLiteral(ohmUrl); + return ts.factory.createCallExpression(node.expression, node.typeArguments, args); + } + } + return node; + }; + return node => ts.visitNode(node, visitor); + }; + + const result: ts.TransformationResult = + ts.transform( this.source!, [moduleNodeTransformer]); + + this.source = result.transformed[0]; + } + + // Replace each module request in source file to a unique representation which is called 'ohmUrl'. + // This 'ohmUrl' will be the same as the record name for each file, to make sure runtime can find the corresponding + // record based on each module request. + async processModuleRequest(rollupObject: Object, parentEvent: Object): Promise { + if (isJsonSourceFile(this.moduleId)) { + return; + } + if (isJsSourceFile(this.moduleId)) { + const eventProcessJsModuleRequest = createAndStartEvent(parentEvent, 'process Js module request'); + this.processJsModuleRequest(rollupObject); + stopEvent(eventProcessJsModuleRequest); + return; + } + + + // Only when files were transformed to ts, the corresponding ModuleSourceFile were initialized with sourceFile node, + // if files were transformed to js, ModuleSourceFile were initialized with srouce string. + if (this.isSourceNode) { + const eventProcessTransformedTsModuleRequest = createAndStartEvent(parentEvent, 'process transformed Ts module request'); + this.processTransformedTsModuleRequest(rollupObject); + stopEvent(eventProcessTransformedTsModuleRequest); + } else { + const eventProcessTransformedJsModuleRequest = createAndStartEvent(parentEvent, 'process transformed Js module request'); + await this.processTransformedJsModuleRequest(rollupObject); + stopEvent(eventProcessTransformedJsModuleRequest); + } + } + + private static initPluginEnv(rollupObject: Object): void { + this.projectConfig = Object.assign(rollupObject.share.arkProjectConfig, rollupObject.share.projectConfig); + this.logger = CommonLogger.getInstance(rollupObject); + } + + public static sortSourceFilesByModuleId(): void { + ModuleSourceFile.sourceFiles.sort((a, b) => a.moduleId.localeCompare(b.moduleId)); + } + + private tryBuildStaticOhmUrl(queryResult: any, moduleRequest: string): string | undefined { + if (!queryResult || !queryResult.isStatic) { + return undefined; + } + + const { originalAPIName } = queryResult; + const recordName = ModuleSourceFile.generateNormalizedOhmulrForSDkInterop( + ModuleSourceFile.projectConfig, + originalAPIName + ); + const moduleName = ModuleSourceFile.projectConfig. + pkgContextInfo[ModuleSourceFile.projectConfig.entryPackageName].moduleName; + const ohmUrl = `N&${moduleName}&${recordName}`; + const glueCodeInfo = FileManager.getInstance().getGlueCodePathByModuleRequest(originalAPIName); + + if (!glueCodeInfo) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_INTERNAL_FAILED_TO_FIND_GLUD_CODE, + ArkTSInternalErrorDescription, + `Failed to find glue code for: ${moduleRequest}` + ); + ModuleSourceFile.logger.printErrorAndExit(errInfo); + } + + FileManager.glueCodeFileInfos.set(originalAPIName, { + recordName: recordName, + baseUrl: glueCodeInfo.basePath, + abstractPath: glueCodeInfo.fullPath + } as FileInfo); + + return `@normalized:${ohmUrl}`; + } + + + public static cleanUpObjects(): void { + ModuleSourceFile.sourceFiles = []; + ModuleSourceFile.projectConfig = undefined; + ModuleSourceFile.logger = undefined; + ModuleSourceFile.mockConfigInfo = {}; + ModuleSourceFile.mockFiles = []; + ModuleSourceFile.newMockConfigInfo = {}; + ModuleSourceFile.transformedHarOrHspMockConfigInfo = {}; + ModuleSourceFile.mockConfigKeyToModuleInfo = {}; + ModuleSourceFile.needProcessMock = false; + ModuleSourceFile.moduleIdMap = new Map(); + ModuleSourceFile.isEnvInitialized = false; + ModuleSourceFile.hookEventFactory = undefined; + } +} + diff --git a/compiler/src/interop/src/fast_build/ark_compiler/process_decorator.ts b/compiler/src/interop/src/fast_build/ark_compiler/process_decorator.ts new file mode 100644 index 0000000000000000000000000000000000000000..c95bfdc2c7ddbda424c92a5deed09a977624715e --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/process_decorator.ts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 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 ts from 'typescript'; +import { + isMockDecorator, + disableMockDecorator +} from './process_mock'; + +export function processDecorator(node: ts.Decorator): ts.Node { + if (isMockDecorator(node)) { + let disabled: boolean = disableMockDecorator(node); + return disabled ? undefined : node; + } + + return node; +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/process_mock.ts b/compiler/src/interop/src/fast_build/ark_compiler/process_mock.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e170dec70d35b946e343940365c7b2519f95a62 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/process_mock.ts @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023 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 ts from 'typescript'; +import { projectConfig } from '../../../main'; +import { DECORATOR_SUFFIX } from '../../pre_define'; + +export function disableMockDecorator(node: ts.Decorator): boolean { + if (!shouldDisableMockDecorator()) { + return false; + } + + let parent: ts.Node = node.parent; + switch (parent.kind) { + case ts.SyntaxKind.Parameter: { + ts.factory.updateParameterDeclaration(parent, + ts.getModifiers((parent)), + (parent).dotDotDotToken, + (parent).name, + (parent).questionToken, + (parent).type, + (parent).initializer); + break; + } + case ts.SyntaxKind.MethodDeclaration: { + ts.factory.updateMethodDeclaration(parent, + ts.getModifiers((parent)), + (parent).asteriskToken, + (parent).name, + (parent).questionToken, + (parent).typeParameters, + (parent).parameters, + (parent).type, + (parent).body); + break; + } + case ts.SyntaxKind.Constructor: { + ts.factory.updateConstructorDeclaration(parent, + ts.getModifiers((parent)), + (parent).parameters, + (parent).body); + break; + } + case ts.SyntaxKind.GetAccessor: { + ts.factory.updateGetAccessorDeclaration(parent, + ts.getModifiers((parent)), + (parent).name, + (parent).parameters, + (parent).type, + (parent).body); + break; + } + case ts.SyntaxKind.SetAccessor: { + ts.factory.updateSetAccessorDeclaration(parent, + ts.getModifiers((parent)), + (parent).name, + (parent).parameters, + (parent).body); + break; + } + case ts.SyntaxKind.PropertyDeclaration: { + if ((parent).questionToken) { + ts.factory.updatePropertyDeclaration(parent, + ts.getModifiers((parent)), + (parent).name, + (parent).questionToken, + (parent).type, + (parent).initializer); + } else if ((parent).exclamationToken) { + ts.factory.updatePropertyDeclaration(parent, + ts.getModifiers((parent)), + (parent).name, + (parent).exclamationToken, + (parent).type, + (parent).initializer); + } else { + ts.factory.updatePropertyDeclaration(parent, + ts.getModifiers((parent)), + (parent).name, + undefined, + (parent).type, + (parent).initializer); + } + break; + } + case ts.SyntaxKind.ClassDeclaration: { + ts.factory.updateClassDeclaration(parent, + ts.getModifiers((parent)), + (parent).name, + (parent).typeParameters, + (parent).heritageClauses, + (parent).members); + break; + } + default: { + break; + } + } + return true; +} + +function removeMockDecorator(decs: readonly ts.Decorator[]): ts.Decorator[] { + let res: ts.Decorator[]; + for (let dec of decs) { + if (!isMockDecorator(dec)) { + res.push(dec); + } + } + + return res; +} + +export function isMockDecorator(dec: ts.Decorator): boolean { + let decObj = dec.expression; + if (!ts.isIdentifier(decObj)) { + return false; + } + + if (projectConfig.mockParams) { + let mockDecorator: string = projectConfig.mockParams.decorator.replace(DECORATOR_SUFFIX, ''); + return ((decObj).escapedText.toString() === mockDecorator); + } + + return false; +} + +function shouldDisableMockDecorator(): boolean { + // mock decorator only takes effect under preview mode, should be removed otherswise + if (projectConfig.isPreview) { + return false; + } + + // mockParams = { + // "decorator": "name of mock decorator", + // "packageName": "name of mock package", + // "etsSourceRootPath": "path of ets source root", + // "mockConfigPath": "path of mock configuration file" + // } + return (projectConfig.mockParams && projectConfig.mockParams.decorator && projectConfig.mockParams.packageName) ? + true : false; +} + +export const ORIGIN_EXTENTION: string = '.origin'; \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/rollup-plugin-gen-abc.ts b/compiler/src/interop/src/fast_build/ark_compiler/rollup-plugin-gen-abc.ts new file mode 100644 index 0000000000000000000000000000000000000000..d7b3b7f9108ba4f9b7c13f9d4e7da77113eee75b --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/rollup-plugin-gen-abc.ts @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023 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 { initArkProjectConfig } from './common/process_ark_config'; +import { generateBundleAbc } from './generate_bundle_abc'; +import { generateModuleAbc, cleanModuleMode } from './generate_module_abc'; +import { transformForModule } from './transform'; +import { checkArkCompilerCacheInfo, shouldInvalidCache } from './cache'; +import { checkIfJsImportingArkts } from './check_import_module'; +import { cleanSharedModuleSet } from './check_shared_module'; +import { compilerOptions } from '../../ets_checker'; +import { ModuleSourceFile } from './module/module_source_file'; +import { SourceMapGenerator } from './generate_sourcemap'; +import { cleanUpUtilsObjects, writeDeclarationFiles } from '../../ark_utils'; +import { cleanUpKitImportObjects } from '../../process_kit_import'; +import { cleanUpFilesList } from './utils'; +import { CommonLogger } from './logger'; +import { FileManager, isMixCompile } from './interop/interop_manager'; +import { cleanUpProcessArkTSEvolutionObj } from './interop/process_arkts_evolution'; + +export function genAbc() { + return { + name: 'genAbc', + buildStart() { + this.share.arkProjectConfig = initArkProjectConfig(this.share); + checkArkCompilerCacheInfo(this); + //Because calling the method of SourceMapGenerator may not retrieve the rollupObject + //it is necessary to assign the rollupObject to SourceMapGenerator in the early stages of build + SourceMapGenerator.init(this); + }, + shouldInvalidCache: shouldInvalidCache, + transform: transformForModule, + moduleParsed(moduleInfo: moduleInfoType): void { + // process single ModuleSourceFile + if (this.share.projectConfig.singleFileEmit) { + ModuleSourceFile.processSingleModuleSourceFile(this, moduleInfo.id); + } + }, + beforeBuildEnd: { + // [pre] means this handler running in first at the stage of beforeBuildEnd. + order: 'pre', + handler() { + if (this.share.projectConfig.singleFileEmit) { + writeDeclarationFiles(this.share.arkProjectConfig.compileMode); + return; + } + if (compilerOptions.needDoArkTsLinter) { + checkIfJsImportingArkts(this); + } + if (this.share.projectConfig.needCoverageInsert) { + this.share.ModuleSourceFile = ModuleSourceFile.getSourceFiles(); + } + } + }, + buildEnd: generateModuleAbc, + generateBundle: generateBundleAbc, + cleanUp: () => { + SourceMapGenerator.cleanSourceMapObject(); + cleanUpUtilsObjects(); + cleanUpKitImportObjects(); + cleanUpFilesList(); + cleanModuleMode(); + ModuleSourceFile.cleanUpObjects(); + cleanSharedModuleSet(); + CommonLogger.destroyInstance(); + isMixCompile() && cleanUpProcessArkTSEvolutionObj(); + FileManager.cleanFileManagerObject(); + } + }; +} + +interface moduleInfoType { + id: string; +}; \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/terser-plugin.ts b/compiler/src/interop/src/fast_build/ark_compiler/terser-plugin.ts new file mode 100644 index 0000000000000000000000000000000000000000..c18e46c0b79eea856b6030c6edbd8b1426a9017b --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/terser-plugin.ts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 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 terser from '@rollup/plugin-terser'; + +export function terserPlugin(): Object { + return terser({ + compress: { + defaults: false, + dead_code: true, + collapse_vars: true, + unused: true, + drop_debugger: true, + if_return: true, + reduce_vars: true, + join_vars: false, + sequences: 0 + }, + format: { + semicolons: false, + beautify: true, + braces: true, + indent_level: 2 + } + }); +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/transform.ts b/compiler/src/interop/src/fast_build/ark_compiler/transform.ts new file mode 100644 index 0000000000000000000000000000000000000000..7bff84a2c3cdebb0b0556215a27831a0fea73fd0 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/transform.ts @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2023 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 path from 'path'; + +import { + ESMODULE, + GEN_ABC_PLUGIN_NAME +} from './common/ark_define'; +import { ModuleSourceFile } from './module/module_source_file'; +import { + isAotMode, + isCommonJsPluginVirtualFile, + isDebug, + isJsonSourceFile, + isJsSourceFile, + isTsOrEtsSourceFile, + shouldETSOrTSFileTransformToJS +} from './utils'; +import { + toUnixPath, + emitLogInfo, + getTransformLog +} from '../../utils'; +import { + getHookEventFactory, + createAndStartEvent, + stopEvent +} from '../../ark_utils'; +import { + resetReExportCheckLog, + reExportNoCheckMode, + processJsCodeLazyImport, + reExportCheckLog, + LazyImportOptions +} from '../../process_lazy_import'; +import { SourceMapGenerator } from './generate_sourcemap'; +import { MemoryMonitor } from '../meomry_monitor/rollup-plugin-memory-monitor'; +import { MemoryDefine } from '../meomry_monitor/memory_define'; + +/** + * rollup transform hook + * @param {string} code: transformed source code of an input file + * @param {string} id: absolute path of an input file + */ +export function transformForModule(code: string, id: string): string { + const hookEventFactory = getHookEventFactory(this.share, 'genAbc', 'transform'); + const eventTransformForModule = createAndStartEvent(hookEventFactory, 'transform for module'); + const { + autoLazyImport, + reExportCheckMode + }: LazyImportOptions = { + autoLazyImport: this.share.projectConfig?.autoLazyImport ?? false, + reExportCheckMode: this.share.projectConfig?.reExportCheckMode ?? reExportNoCheckMode + }; + if (this.share.projectConfig.compileMode === ESMODULE) { + const metaInfo: Object = this.getModuleInfo(id).meta; + const projectConfig: Object = Object.assign(this.share.arkProjectConfig, this.share.projectConfig); + if (isTsOrEtsSourceFile(id) && shouldETSOrTSFileTransformToJS(id, projectConfig, metaInfo)) { + preserveSourceMap(id, this.getCombinedSourcemap(), projectConfig, metaInfo, eventTransformForModule); + // when ets/ts -> js, we need to convert lazy-import based on the js code generated after tsc conversion + code = processJsCodeLazyImport(id, code, autoLazyImport, reExportCheckMode); + const newSourceFileRecordInfo = MemoryMonitor.recordStage(MemoryDefine.MODULE_SOURCE_FILE_NEW_SOURCE_FILE); + ModuleSourceFile.newSourceFile(id, code, metaInfo, projectConfig.singleFileEmit); + MemoryMonitor.stopRecordStage(newSourceFileRecordInfo); + } + + if (isJsSourceFile(id) || isJsonSourceFile(id)) { + let code: string = this.getModuleInfo(id).originalCode; + if (isJsSourceFile(id)) { + code = processJsCodeLazyImport(id, code, autoLazyImport, reExportCheckMode); + if (projectConfig.compatibleSdkVersion <= 10) { + const transformedResult: object = transformJsByBabelPlugin(code, eventTransformForModule); + code = transformedResult.code; + preserveSourceMap(id, transformedResult.map, projectConfig, metaInfo, eventTransformForModule); + } else { + // preserve sourceMap of js file without transforming + preserveSourceMap(id, this.getCombinedSourcemap(), projectConfig, metaInfo, eventTransformForModule); + } + } + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.MODULE_SOURCE_FILE_NEW_SOURCE_FILE); + ModuleSourceFile.newSourceFile(id, code, metaInfo, projectConfig.singleFileEmit); + MemoryMonitor.stopRecordStage(recordInfo); + } + } + stopEvent(eventTransformForModule); + + if (reExportCheckLog && reExportCheckLog.errors.length && + reExportCheckMode !== reExportNoCheckMode && !this.share.projectConfig.ignoreWarning) { + emitLogInfo(this.share.getLogger(GEN_ABC_PLUGIN_NAME), [...getTransformLog(reExportCheckLog)], true, id); + resetReExportCheckLog(); + } + + // if we perform lazy-import conversion, we need to return the converted js code + return code; +} + +function preserveSourceMap(sourceFilePath: string, sourcemap: Object, projectConfig: Object, metaInfo: Object, parentEvent: Object): void { + if (isCommonJsPluginVirtualFile(sourceFilePath)) { + // skip automatic generated files like 'jsfile.js?commonjs-exports' + return; + } + + const eventAddSourceMapInfo = createAndStartEvent(parentEvent, 'preserve source map for ts/ets files'); + const relativeSourceFilePath = sourceFilePath.startsWith(projectConfig.projectRootPath) ? + toUnixPath(sourceFilePath.replace(projectConfig.projectRootPath + path.sep, '')) : + toUnixPath(sourceFilePath.replace(metaInfo.belongProjectPath + path.sep, '')); + sourcemap.sources = [relativeSourceFilePath]; + sourcemap.file = path.basename(relativeSourceFilePath); + sourcemap.sourcesContent && delete sourcemap.sourcesContent; + const sourceMapGenerator = SourceMapGenerator.getInstance(); + const key = sourceMapGenerator.isNewSourceMaps() ? sourceFilePath : relativeSourceFilePath; + sourceMapGenerator.fillSourceMapPackageInfo(sourceFilePath, sourcemap); + sourceMapGenerator.updateSourceMap(key, sourcemap); + stopEvent(eventAddSourceMapInfo); +} + +function transformJsByBabelPlugin(code: string, parentEvent: Object): Object { + const eventTransformByBabel = createAndStartEvent(parentEvent, 'transform Js by babel plugin'); + const transformed: Object = require('@babel/core').transformSync(code, + { + plugins: [ + [require("@babel/plugin-proposal-class-properties"), { "loose": true }] + ], + compact: false, + sourceMaps: true + } + ); + stopEvent(eventTransformByBabel); + return transformed; +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/url_config.json b/compiler/src/interop/src/fast_build/ark_compiler/url_config.json new file mode 100644 index 0000000000000000000000000000000000000000..79e37c7c9cf1f2b3686a1a8a2596c016aab590b0 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/url_config.json @@ -0,0 +1,8 @@ +{ + "es2abcErrorReferences": { + "harmonyOSGuideHotReload": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-hot-reload-V5" + }, + "etsLoaderErrorReferences": { + "harmonyOSReferencesAPI": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/development-intro-api-V5" + } +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/utils.ts b/compiler/src/interop/src/fast_build/ark_compiler/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..4cbed27ae5a07ed1cd1f3b50db32ec9fedf38f7f --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/utils.ts @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2023 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 cluster from 'cluster'; +import fs from 'fs'; +import path from 'path'; +import ts from 'typescript'; +import os from 'os'; +import sourceMap from 'source-map'; +import * as crypto from 'node:crypto'; + +import { + DEBUG, + ESMODULE, + EXTNAME_ETS, + EXTNAME_JS, + EXTNAME_TS, + EXTNAME_JSON, + EXTNAME_CJS, + EXTNAME_MJS, + TEMPORARY +} from './common/ark_define'; +import { + nodeLargeOrEqualTargetVersion, + genTemporaryPath, + mkdirsSync, + validateFilePathLength, + toUnixPath, + isPackageModulesFile, + isFileInProject +} from '../../utils'; +import { + tryMangleFileName, + writeObfuscatedSourceCode, + cleanUpUtilsObjects, + createAndStartEvent, + stopEvent +} from '../../ark_utils'; +import { AOT_FULL, AOT_PARTIAL, AOT_TYPE } from '../../pre_define'; +import { SourceMapGenerator } from './generate_sourcemap'; +import { + handleObfuscatedFilePath, + enableObfuscateFileName, + enableObfuscatedFilePathConfig, + getRelativeSourcePath +} from './common/ob_config_resolver'; + +export let hasTsNoCheckOrTsIgnoreFiles: string[] = []; +export let compilingEtsOrTsFiles: string[] = []; + +export function cleanUpFilesList(): void { + hasTsNoCheckOrTsIgnoreFiles = []; + compilingEtsOrTsFiles = []; +} + +export function needAotCompiler(projectConfig: Object): boolean { + return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL || + projectConfig.anBuildMode === AOT_PARTIAL); +} + +export function isAotMode(projectConfig: Object): boolean { + return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL || + projectConfig.anBuildMode === AOT_PARTIAL || projectConfig.anBuildMode === AOT_TYPE); +} + +export function isDebug(projectConfig: Object): boolean { + return projectConfig.buildMode.toLowerCase() === DEBUG; +} + +export function isBranchElimination(projectConfig: Object): boolean { + return projectConfig.branchElimination; +} + +export function isMasterOrPrimary() { + return ((nodeLargeOrEqualTargetVersion(16) && cluster.isPrimary) || + (!nodeLargeOrEqualTargetVersion(16) && cluster.isMaster)); +} + +export function changeFileExtension(file: string, targetExt: string, originExt = ''): string { + let currentExt = originExt.length === 0 ? path.extname(file) : originExt; + let fileWithoutExt = file.substring(0, file.lastIndexOf(currentExt)); + return fileWithoutExt + targetExt; +} + +function removeCacheFile(cacheFilePath: string, ext: string): void { + let filePath = toUnixPath(changeFileExtension(cacheFilePath, ext)); + if (fs.existsSync(filePath)) { + fs.rmSync(filePath); + } +} + +export function shouldETSOrTSFileTransformToJS(filePath: string, projectConfig: Object, metaInfo?: Object): boolean { + let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath, + projectConfig, metaInfo); + + if (!projectConfig.processTs) { + removeCacheFile(cacheFilePath, EXTNAME_TS); + return true; + } + + if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) { + // file involves in compilation + const hasTsNoCheckOrTsIgnore = hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1; + // Remove cacheFile whose extension is different the target file + removeCacheFile(cacheFilePath, hasTsNoCheckOrTsIgnore ? EXTNAME_TS : EXTNAME_JS); + return hasTsNoCheckOrTsIgnore; + } + + cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig); + cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS)); + return fs.existsSync(cacheFilePath); +} + +// This API is used exclusively to determine whether a file needs to be converted into a JS file without removing the cached files, +// which differs from API 'shouldETSOrTSFileTransformToJS'. +export function shouldETSOrTSFileTransformToJSWithoutRemove(filePath: string, projectConfig: Object, metaInfo?: Object): boolean { + if (!projectConfig.processTs) { + return true; + } + + if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) { + // file involves in compilation + return hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1; + } + + let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath, + projectConfig, metaInfo); + cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig); + cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS)); + return fs.existsSync(cacheFilePath); +} + +function updateCacheFilePathIfEnableObuscatedFilePath(filePath: string, cacheFilePath: string, projectConfig: Object): string { + const isPackageModules = isPackageModulesFile(filePath, projectConfig); + if (enableObfuscatedFilePathConfig(isPackageModules, projectConfig) && enableObfuscateFileName(isPackageModules, projectConfig)) { + return handleObfuscatedFilePath(cacheFilePath, isPackageModules, projectConfig); + } + return cacheFilePath; +} + +export async function writeFileContentToTempDir(id: string, content: string, projectConfig: Object, + logger: Object, parentEvent: Object, metaInfo: Object): Promise { + if (isCommonJsPluginVirtualFile(id)) { + return; + } + + if (!isCurrentProjectFiles(id, projectConfig)) { + return; + } + + let filePath: string; + if (projectConfig.compileHar) { + // compileShared: compile shared har of project + filePath = genTemporaryPath(id, + projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath, + projectConfig.compileShared ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath, //??? + projectConfig, metaInfo, projectConfig.compileShared); + } else { + filePath = genTemporaryPath(id, projectConfig.projectPath, projectConfig.cachePath, projectConfig, metaInfo); + } + + const eventWriteFileContent = createAndStartEvent(parentEvent, 'write file content'); + switch (path.extname(id)) { + case EXTNAME_ETS: + case EXTNAME_TS: + case EXTNAME_JS: + case EXTNAME_MJS: + case EXTNAME_CJS: + await writeFileContent(id, filePath, content, projectConfig, logger, metaInfo); + break; + case EXTNAME_JSON: + const newFilePath: string = tryMangleFileName(filePath, projectConfig, id); + mkdirsSync(path.dirname(newFilePath)); + fs.writeFileSync(newFilePath, content ?? ''); + break; + default: + break; + } + stopEvent(eventWriteFileContent); +} + +async function writeFileContent(sourceFilePath: string, filePath: string, content: string, + projectConfig: Object, logger: Object, metaInfo?: Object): Promise { + if (!isSpecifiedExt(sourceFilePath, EXTNAME_JS)) { + filePath = changeFileExtension(filePath, EXTNAME_JS); + } + + if (!isDebug(projectConfig)) { + const relativeSourceFilePath: string = getRelativeSourcePath(sourceFilePath, projectConfig.projectRootPath, + metaInfo?.belongProjectPath); + await writeObfuscatedSourceCode({content: content, buildFilePath: filePath, + relativeSourceFilePath: relativeSourceFilePath, originSourceFilePath: sourceFilePath, rollupModuleId: sourceFilePath}, + logger, projectConfig, SourceMapGenerator.getInstance().getSourceMaps()); + return; + } + mkdirsSync(path.dirname(filePath)); + fs.writeFileSync(filePath, content, 'utf-8'); +} + +export function getEs2abcFileThreadNumber(): number { + const fileThreads: number = os.cpus().length < 16 ? os.cpus().length : 16; + return fileThreads; +} + +export function isCommonJsPluginVirtualFile(filePath: string): boolean { + // rollup uses commonjs plugin to handle commonjs files, + // which will automatic generate files like 'jsfile.js?commonjs-exports' + return filePath.includes('\x00'); +} + +export function isCurrentProjectFiles(filePath: string, projectConfig: Object): boolean { + if (projectConfig.rootPathSet) { + for (const projectRootPath of projectConfig.rootPathSet) { + if (isFileInProject(filePath, projectRootPath)) { + return true; + } + } + return false; + } + return isFileInProject(filePath, projectConfig.projectRootPath); +} + +export function genTemporaryModuleCacheDirectoryForBundle(projectConfig: Object): string { + const buildDirArr: string[] = projectConfig.aceModuleBuild.split(path.sep); + const abilityDir: string = buildDirArr[buildDirArr.length - 1]; + const temporaryModuleCacheDirPath: string = path.join(projectConfig.cachePath, TEMPORARY, abilityDir); + mkdirsSync(temporaryModuleCacheDirPath); + + return temporaryModuleCacheDirPath; +} + +export function isSpecifiedExt(filePath: string, fileExtendName: string) { + return path.extname(filePath) === fileExtendName; +} + +export function genCachePath(tailName: string, projectConfig: Object, logger: Object): string { + const pathName: string = projectConfig.cachePath !== undefined ? + path.join(projectConfig.cachePath, TEMPORARY, tailName) : path.join(projectConfig.aceModuleBuild, tailName); + mkdirsSync(path.dirname(pathName)); + + validateFilePathLength(pathName, logger); + return pathName; +} + +export function isTsOrEtsSourceFile(file: string): boolean { + return /(? { + if (!originMap) { + return newMap; + } + if (!newMap) { + return originMap; + } + const originConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(originMap); + const newConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(newMap); + const newMappingList: sourceMap.MappingItem[] = []; + newConsumer.eachMapping((mapping: sourceMap.MappingItem) => { + if (mapping.originalLine == null) { + return; + } + const originalPos = + originConsumer.originalPositionFor({ line: mapping.originalLine, column: mapping.originalColumn }); + if (originalPos.source == null) { + return; + } + mapping.originalLine = originalPos.line; + mapping.originalColumn = originalPos.column; + newMappingList.push(mapping); + }); + const updatedGenerator: sourceMap.SourceMapGenerator = sourceMap.SourceMapGenerator.fromSourceMap(newConsumer); + updatedGenerator._file = originMap.file; + updatedGenerator._mappings._array = newMappingList; + return JSON.parse(updatedGenerator.toString()); +} + +export function hasArkDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | + ts.StructDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration, decortorName: string): boolean { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (decorators && decorators.length) { + for (let i = 0; i < decorators.length; i++) { + const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim(); + return originalDecortor === decortorName; + } + } + return false; +} + +export const utUtils = { + writeFileContent +}; + +export function hasExistingPaths(paths: Set): boolean { + for (const p of paths) { + if (fs.existsSync(toUnixPath(p))) { + return true; + } + } + return false; +} + +export function isSubPathOf(targetPath: string, parentDir: string): boolean { + const resolvedParent = toUnixPath(path.resolve(parentDir)); + const resolvedTarget = toUnixPath(path.resolve(targetPath)); + return resolvedTarget === resolvedParent || resolvedTarget.startsWith(resolvedParent + '/'); +} + +export function calculateFileHash(filePath: string, algorithm: string = 'sha256'): string { + const fileBuffer = fs.readFileSync(filePath); + const hash = crypto.createHash(algorithm); + hash.update(fileBuffer); + return hash.digest('hex'); +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/common/init_config.ts b/compiler/src/interop/src/fast_build/common/init_config.ts new file mode 100644 index 0000000000000000000000000000000000000000..1e5775c450b11bbccde9cf9d86dcefe175931f3c --- /dev/null +++ b/compiler/src/interop/src/fast_build/common/init_config.ts @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 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 { + readAppResource, + projectConfig, + globalModulePaths +} from '../../../main'; +import { + getEntryObj, + workerFile +} from './process_project_config'; + +export function initConfig() { + getEntryObj(); + if (process.env.appResource) { + readAppResource(process.env.appResource); + } + return { + entryObj: Object.assign({}, projectConfig.entryObj, projectConfig.otherCompileFiles), + cardEntryObj: projectConfig.cardEntryObj, + workerFile: workerFile, + globalModulePaths: globalModulePaths + }; +} + diff --git a/compiler/src/interop/src/fast_build/common/process_project_config.ts b/compiler/src/interop/src/fast_build/common/process_project_config.ts new file mode 100644 index 0000000000000000000000000000000000000000..0713051acbdcccc15bb57ad95ba9991dd0303ff9 --- /dev/null +++ b/compiler/src/interop/src/fast_build/common/process_project_config.ts @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023 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 fs from 'fs'; +import path from 'path'; + +const { + projectConfig, + loadEntryObj, + abilityConfig, + initBuildInfo, + readWorkerFile, + loadWorker, + readPatchConfig, + loadModuleInfo +} = require('../../../main.js'); + +export let workerFile = null; +export function getEntryObj() { + loadEntryObj(projectConfig); + initBuildInfo(); + readPatchConfig(); + loadModuleInfo(projectConfig); + workerFile = readWorkerFile(); + if (!projectConfig.isPreview) { + loadWorker(projectConfig, workerFile); + } + projectConfig.entryObj = Object.keys(projectConfig.entryObj).reduce((newEntry, key) => { + const newKey: string = key.replace(/^\.\//, ''); + const filePath = projectConfig.entryObj[key].replace('?entry', ''); + const firstLine = fs.readFileSync(filePath, 'utf-8').split('\n')[0]; + if (!firstLine.includes('use static')) { + newEntry[newKey] = filePath; + } + return newEntry; + }, {}); +} + +export function setCopyPluginConfig(projectConfig: any, appResource: string) { + const copyPluginPattrens: object[] = []; + const BUILD_SHARE_PATH: string = '../share'; //??? + copyPluginPattrens.push({ + src: [ + path.resolve(projectConfig.projectPath, '**/*'), + '!**/*.ets', + '!**/*.ts', + '!**/*.js', + `!${projectConfig.buildPath}` + ], + dest: projectConfig.buildPath + }); + const sharePath: string = path.resolve(projectConfig.projectPath, BUILD_SHARE_PATH); + if (fs.existsSync(sharePath)) { + copyPluginPattrens.push({ + src: [ + path.resolve(projectConfig.projectPath, BUILD_SHARE_PATH, '**/*'), + '!**/*.ets', + '!**/*.ts', + '!**/*.js' + ], + dest: path.resolve(projectConfig.buildPath, BUILD_SHARE_PATH), + }); + } + if (abilityConfig.abilityType === 'page') { + if (fs.existsSync(projectConfig.manifestFilePath)) { + copyPluginPattrens.push({ + src: projectConfig.manifestFilePath, + dest: projectConfig.buildPath + }); + } else if (fs.existsSync(projectConfig.aceConfigPath)) { + copyPluginPattrens.push({ + src: projectConfig.aceConfigPath, + dest: projectConfig.buildPath + }); + } + } + if (appResource && fs.existsSync(appResource) && !projectConfig.xtsMode && + projectConfig.isPreview) { + copyPluginPattrens.push({ + src: path.resolve(__dirname, appResource), + dest: path.resolve(__dirname, projectConfig.cachePath) + }); + } + return copyPluginPattrens; +} diff --git a/compiler/src/interop/src/fast_build/common/rollup-plugin-watch-change.ts b/compiler/src/interop/src/fast_build/common/rollup-plugin-watch-change.ts new file mode 100644 index 0000000000000000000000000000000000000000..69803a7520a185c7c6bee39ad9eb4ce4925f0271 --- /dev/null +++ b/compiler/src/interop/src/fast_build/common/rollup-plugin-watch-change.ts @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023 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 path from 'path'; + +import { + shouldWriteChangedList, + writeFileSync, + getHotReloadFiles, + storedFileInfo, + resourcesRawfile, + differenceResourcesRawfile +} from '../../utils'; +import { + projectConfig, + readAppResource, + resources +} from '../../../main'; +import { + incrementWatchFile, + hotReloadSupportFiles +} from '../../ets_checker'; +import { ShouldEnableDebugLine } from '../ets_ui/rollup-plugin-ets-typescript'; + +export function watchChangeFiles() { + function addFileToCache(this: any, key: string, id: string) { + let modifiedFiles: string[] = []; + if (!projectConfig.hotReload && this.cache && this.cache.has(key)) { + modifiedFiles = this.cache.get(key); + modifiedFiles.push(id); + } else { + modifiedFiles.push(id); + } + this.cache.set(key, modifiedFiles); + } + return { + name: 'watchChangedFiles', + watchChange(id: string, change: {event: 'create' | 'update' | 'delete'}): void { + if (change.event === 'update') { + addFileToCache.call(this, 'watchModifiedFiles', id); + } + if (['create', 'delete'].includes(change.event)) { + addFileToCache.call(this, 'watchRemovedFiles', id); + } + if (path.resolve(id) === path.resolve(process.env.appResource) && process.env.compileMode === 'moduleJson') { + storedFileInfo.resourceTableChanged = true; + storedFileInfo.resourceList.clear(); + resources.app = {}; + readAppResource(process.env.appResource); + if (process.env.rawFileResource) { + resourcesRawfile(process.env.rawFileResource, storedFileInfo.resourcesArr); + this.share.rawfilechanged = differenceResourcesRawfile(storedFileInfo.lastResourcesSet, storedFileInfo.resourcesArr); + } + } + ShouldEnableDebugLine.enableDebugLine = false; + }, + beforeBuild() { + this.cache.set('watchChangedFilesCache', 'watchChangedFiles'); + const watchModifiedFiles: string[] = this.cache.get('watchModifiedFiles') || []; + const watchRemovedFiles: string[] = this.cache.get('watchRemovedFiles') || []; + if (!projectConfig.removeChangedFileListInSdk && shouldWriteChangedList(watchModifiedFiles, watchRemovedFiles)) { + writeFileSync(projectConfig.changedFileList, JSON.stringify( + getHotReloadFiles(watchModifiedFiles, watchRemovedFiles, hotReloadSupportFiles))); + } + incrementWatchFile(watchModifiedFiles, watchRemovedFiles); + if (this.cache.get('lastWatchResourcesArr')) { + storedFileInfo.lastResourcesSet = new Set([...this.cache.get('lastWatchResourcesArr')]); + } + } + }; +} diff --git a/compiler/src/interop/src/fast_build/ets_ui/arkoala-plugin.ts b/compiler/src/interop/src/fast_build/ets_ui/arkoala-plugin.ts new file mode 100644 index 0000000000000000000000000000000000000000..de8d4f97139af94cc22e40d9c9e293598f3e4e3c --- /dev/null +++ b/compiler/src/interop/src/fast_build/ets_ui/arkoala-plugin.ts @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type * as ts from 'typescript' +import { reset, yellow } from '../ark_compiler/common/ark_define' + +export interface ArkoalaPluginOptions { + /** + * Filter program source files which must be trnsformed with Arkoala plugins + * + * @param fileName Normalize source file path + * @returns true if the given file must be transformed + */ + filter?: (fileName: string) => boolean; + /** + * Specify the root directory from which router path must be resolved + * @default "." + */ + routerRootDir?: string; +} + +// This is an implementation stub, it should be replaced with arkoala-plugin.js from the Arkoala repo. +export default function arkoalaProgramTransform( + program: ts.Program, + compilerHost: ts.CompilerHost | undefined, + options: ArkoalaPluginOptions, + extras: Object, +): ts.Program { + let [,,,] = [compilerHost, options, extras] + console.warn(`${yellow}WARN: Arkoala plugin is missing in the current SDK. Source transformation will not be performed.${reset}`) + return program +} diff --git a/compiler/src/interop/src/fast_build/ets_ui/rollup-plugin-ets-checker.ts b/compiler/src/interop/src/fast_build/ets_ui/rollup-plugin-ets-checker.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5f6eafe136b4fdb78c59821ddfb6863c7c6195e --- /dev/null +++ b/compiler/src/interop/src/fast_build/ets_ui/rollup-plugin-ets-checker.ts @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2023 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 path from 'path'; +import { EventEmitter } from 'events'; +import * as ts from 'typescript'; + +import { + projectConfig, + globalProgram +} from '../../../main'; +import { + serviceChecker, + languageService, + printDiagnostic, + fastBuildLogger, + emitBuildInfo, + runArkTSLinter, + targetESVersionChanged, + collectFileToIgnoreDiagnostics, + TSC_SYSTEM_CODE +} from '../../ets_checker'; +import { TS_WATCH_END_MSG } from '../../pre_define'; +import { + setChecker, + startTimeStatisticsLocation, + stopTimeStatisticsLocation, + CompilationTimeStatistics, +} from '../../utils'; +import { + configureSyscapInfo, + configurePermission +} from '../system_api/api_check_utils'; +import { MemoryMonitor } from '../meomry_monitor/rollup-plugin-memory-monitor'; +import { MemoryDefine } from '../meomry_monitor/memory_define'; +import { LINTER_SUBSYSTEM_CODE } from '../../hvigor_error_code/hvigor_error_info'; +import { ErrorCodeModule } from '../../hvigor_error_code/const/error_code_module'; +import { collectArkTSEvolutionModuleInfo } from '../ark_compiler/interop/process_arkts_evolution'; +import { + initFileManagerInRollup, + isBridgeCode, + isMixCompile +} from '../ark_compiler/interop/interop_manager'; + +export let tsWatchEmitter: EventEmitter | undefined = undefined; +export let tsWatchEndPromise: Promise; + +export function etsChecker() { + let executedOnce: boolean = false; + return { + name: 'etsChecker', + buildStart() { + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.ROLLUP_PLUGIN_BUILD_START); + if (isMixCompile()) { + collectArkTSEvolutionModuleInfo(this.share); + } + initFileManagerInRollup(this.share); + const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsChecker', 'buildStart'); + if (process.env.watchMode === 'true' && process.env.triggerTsWatch === 'true') { + tsWatchEmitter = new EventEmitter(); + tsWatchEndPromise = new Promise(resolve => { + tsWatchEmitter.on(TS_WATCH_END_MSG, () => { + resolve(); + }); + }); + } + if (this.share.projectConfig.deviceTypes) { + configureSyscapInfo(this.share.projectConfig); + } + if (this.share.projectConfig.permission) { + configurePermission(this.share.projectConfig); + } + if (this.share.projectConfig.integratedHsp) { + projectConfig.integratedHsp = this.share.projectConfig.integratedHsp; + projectConfig.resetBundleName = this.share.projectConfig.integratedHsp; + } + Object.assign(projectConfig, this.share.projectConfig); + Object.assign(this.share.projectConfig, { + compileHar: projectConfig.compileHar, + compileShared: projectConfig.compileShared, + moduleRootPath: projectConfig.moduleRootPath, + buildPath: projectConfig.buildPath, + isCrossplatform: projectConfig.isCrossplatform, + requestPermissions: projectConfig.requestPermissions, + definePermissions: projectConfig.definePermissions, + syscapIntersectionSet: projectConfig.syscapIntersectionSet, + syscapUnionSet: projectConfig.syscapUnionSet, + deviceTypesMessage: projectConfig.deviceTypesMessage, + compileSdkVersion: projectConfig.compileSdkVersion, + compatibleSdkVersion: projectConfig.compatibleSdkVersion, + runtimeOS: projectConfig.runtimeOS + }); + const logger = this.share.getLogger('etsChecker'); + const rootFileNames: string[] = []; + const resolveModulePaths: string[] = []; + rootFileNamesCollect(rootFileNames, this.share); + if (this.share && this.share.projectConfig && this.share.projectConfig.resolveModulePaths && + Array.isArray(this.share.projectConfig.resolveModulePaths)) { + resolveModulePaths.push(...this.share.projectConfig.resolveModulePaths); + } + if (process.env.watchMode === 'true') { + !executedOnce && serviceChecker(rootFileNames, logger, resolveModulePaths, compilationTime, this.share); + startTimeStatisticsLocation(compilationTime ? compilationTime.diagnosticTime : undefined); + if (executedOnce) { + const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance(); + timePrinterInstance.setArkTSTimePrintSwitch(false); + const buildProgramRecordInfo = MemoryMonitor.recordStage(MemoryDefine.BUILDER_PROGRAM); + timePrinterInstance.appendTime(ts.TimePhase.START); + globalProgram.builderProgram = languageService.getBuilderProgram(/*withLinterProgram*/ true); + globalProgram.program = globalProgram.builderProgram.getProgram(); + timePrinterInstance.appendTime(ts.TimePhase.GET_PROGRAM); + MemoryMonitor.stopRecordStage(buildProgramRecordInfo); + const collectFileToIgnore = MemoryMonitor.recordStage(MemoryDefine.COLLECT_FILE_TOIGNORE_RUN_TSLINTER); + collectFileToIgnoreDiagnostics(rootFileNames); + runArkTSLinter(getErrorCodeLogger(LINTER_SUBSYSTEM_CODE, this.share)); + MemoryMonitor.stopRecordStage(collectFileToIgnore); + } + executedOnce = true; + const allDiagnostics: ts.Diagnostic[] = globalProgram.builderProgram + .getSyntacticDiagnostics() + .concat(globalProgram.builderProgram.getSemanticDiagnostics()); + stopTimeStatisticsLocation(compilationTime ? compilationTime.diagnosticTime : undefined); + emitBuildInfo(); + let errorCodeLogger: Object | undefined = this.share?.getHvigorConsoleLogger ? + this.share?.getHvigorConsoleLogger(TSC_SYSTEM_CODE) : undefined; + + allDiagnostics.forEach((diagnostic: ts.Diagnostic) => { + printDiagnostic(diagnostic, ErrorCodeModule.TSC, errorCodeLogger); + }); + fastBuildLogger.debug(TS_WATCH_END_MSG); + tsWatchEmitter.emit(TS_WATCH_END_MSG); + } else { + serviceChecker(rootFileNames, logger, resolveModulePaths, compilationTime, this.share); + } + setChecker(); + MemoryMonitor.stopRecordStage(recordInfo); + }, + shouldInvalidCache(): boolean { + // The generated js file might be different in some cases when we change the targetESVersion, + // so we need to regenerate them all when targetESVersion is changed. + return targetESVersionChanged; + } + }; +} + +function getErrorCodeLogger(code: string, share: Object): Object | undefined { + return !!share?.getHvigorConsoleLogger ? share?.getHvigorConsoleLogger(code) : undefined; +} + +/** + * In mixed compilation scenarios, + * hvigor is prevented from stuffing glue code into the code and causing TSC parsing failed. + * + * case: + * 1.2 File Relative Path Reference 1.1 File + * The 1.2 file is under the glue code path, and the 1.1 file cannot be found in the relative path + * In fact, dependency resolution requires interop decl file + */ +function rootFileNamesCollect(rootFileNames: string[], sharedObj: Object): void { + const entryFiles: string[] = projectConfig.widgetCompile ? Object.values(projectConfig.cardEntryObj) : Object.values(projectConfig.entryObj); + entryFiles.forEach((fileName: string) => { + if (isBridgeCode(fileName, sharedObj?.projectConfig)) { + return; + } + rootFileNames.push(path.resolve(fileName)); + }); +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ets_ui/rollup-plugin-ets-typescript.ts b/compiler/src/interop/src/fast_build/ets_ui/rollup-plugin-ets-typescript.ts new file mode 100644 index 0000000000000000000000000000000000000000..9cbb9d50923680cf6cf340f836f253d340fa1318 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ets_ui/rollup-plugin-ets-typescript.ts @@ -0,0 +1,810 @@ +/* + * Copyright (c) 2023 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 ts from 'typescript'; +import path from 'path'; +import fs from 'fs'; +import { createFilter } from '@rollup/pluginutils'; +import MagicString from 'magic-string'; +import nodeEvents from 'node:events'; + +import { + LogInfo, + componentInfo, + emitLogInfo, + getTransformLog, + genTemporaryPath, + writeFileSync, + getAllComponentsOrModules, + writeCollectionFile, + storedFileInfo, + fileInfo, + resourcesRawfile, + differenceResourcesRawfile, + CacheFile, + startTimeStatisticsLocation, + stopTimeStatisticsLocation, + CompilationTimeStatistics, + genLoaderOutPathOfHar, + harFilesRecord, + resetUtils, + getResolveModules, + toUnixPath, + getBelongModuleInfo +} from '../../utils'; +import { + preprocessExtend, + preprocessNewExtend, + validateUISyntax, + propertyCollection, + linkCollection, + resetComponentCollection, + componentCollection, + resetValidateUiSyntax +} from '../../validate_ui_syntax'; +import { + processUISyntax, + resetLog, + transformLog, + resetProcessUiSyntax +} from '../../process_ui_syntax'; +import { + projectConfig, + abilityPagesFullPath, + globalProgram, + resetMain, + globalModulePaths +} from '../../../main'; +import { + appComponentCollection, + compilerOptions as etsCheckerCompilerOptions, + resolveModuleNames, + resolveTypeReferenceDirectives, + resetEtsCheck, + collectAllFiles, + allModuleIds, + resetEtsCheckTypeScript +} from '../../ets_checker'; +import { + CUSTOM_BUILDER_METHOD, + GLOBAL_CUSTOM_BUILDER_METHOD, + INNER_CUSTOM_BUILDER_METHOD, + resetComponentMap, + INNER_CUSTOM_LOCALBUILDER_METHOD, + EXTEND_ATTRIBUTE +} from '../../component_map'; +import { + kitTransformLog, + processKitImport, + checkHasKeepTs, + resetKitImportLog +} from '../../process_kit_import'; +import { resetProcessComponentMember } from '../../process_component_member'; +import { + collectReservedNameForObf, + mangleFilePath, + resetObfuscation +} from '../ark_compiler/common/ob_config_resolver'; +import { + LazyImportOptions, + reExportNoCheckMode +} from '../../process_lazy_import'; +import arkoalaProgramTransform, { ArkoalaPluginOptions } from './arkoala-plugin'; +import processStructComponentV2 from '../../process_struct_componentV2'; +import { shouldETSOrTSFileTransformToJSWithoutRemove } from '../ark_compiler/utils'; +import { MemoryMonitor } from '../meomry_monitor/rollup-plugin-memory-monitor'; +import { MemoryDefine } from '../meomry_monitor/memory_define'; +import { ModuleSourceFile } from '../ark_compiler/module/module_source_file'; +import { ARKUI_SUBSYSTEM_CODE } from '../../../lib/hvigor_error_code/hvigor_error_info'; //??? +import { ProjectCollections } from 'arkguard'; //??? +import { + interopTransformLog, + interopTransform +} from '../ark_compiler/interop/process_arkts_evolution'; + +const filter: any = createFilter(/(? = new Map(); +let shouldDisableCache: boolean = false; +interface ShouldEnableDebugLineType { + enableDebugLine: boolean; +} + +export const ShouldEnableDebugLine: ShouldEnableDebugLineType = { + enableDebugLine: false +}; + +let disableCacheOptions = { + bundleName: 'default', + entryModuleName: 'default', + runtimeOS: 'default', + resourceTableHash: 'default', + etsLoaderVersion: 'default' +}; + +export function etsTransform() { + const allFilesInHar: Map = new Map(); + let cacheFile: { [fileName: string]: CacheFile }; + return { + name: 'etsTransform', + transform: transform, + moduleParsed(moduleInfo: moduleInfoType): void { + if (this.share.projectConfig.singleFileEmit && this.share.projectConfig.needCoverageInsert) { + //ts coverage instrumentation + this.share.sourceFile = ModuleSourceFile.getSourceFileById(moduleInfo.id); + } + }, + buildStart() { + const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'buildStart'); + startTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformBuildStartTime : undefined); + judgeCacheShouldDisabled.call(this); + if (process.env.compileMode === 'moduleJson') { + cacheFile = this.cache.get('transformCacheFiles'); + storedFileInfo.addGlobalCacheInfo(this.cache.get('resourceListCacheInfo'), + this.cache.get('resourceToFileCacheInfo'), cacheFile); + if (this.cache.get('lastResourcesArr')) { + storedFileInfo.lastResourcesSet = new Set([...this.cache.get('lastResourcesArr')]); + } + if (process.env.rawFileResource) { + resourcesRawfile(process.env.rawFileResource, storedFileInfo.resourcesArr); + this.share.rawfilechanged = differenceResourcesRawfile(storedFileInfo.lastResourcesSet, storedFileInfo.resourcesArr); + } + } + if (!!this.cache.get('enableDebugLine') !== projectConfig.enableDebugLine) { + ShouldEnableDebugLine.enableDebugLine = true; + } + stopTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformBuildStartTime : undefined); + }, + load(id: string) { + let fileCacheInfo: fileInfo; + const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'load'); + startTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformLoadTime : undefined); + if (this.cache.get('fileCacheInfo')) { + fileCacheInfo = this.cache.get('fileCacheInfo')[path.resolve(id)]; + } + // Exclude Component Preview page + if (projectConfig.isPreview && !projectConfig.checkEntry && id.match(/(? { + // if the ts or ets file code only contain interface, it doesn't have js file. + if (fs.existsSync(cacheFilePath)) { + const sourceCode: string = fs.readFileSync(cacheFilePath, 'utf-8'); + writeFileSync(buildFilePath, sourceCode); + } + }); + } + shouldDisableCache = false; + this.cache.set('disableCacheOptions', disableCacheOptions); + this.cache.set('lastResourcesArr', [...storedFileInfo.resourcesArr]); + if (projectConfig.enableDebugLine) { + this.cache.set('enableDebugLine', true); + } else { + this.cache.set('enableDebugLine', false); + } + storedFileInfo.clearCollectedInfo(this.cache); + this.cache.set('transformCacheFiles', storedFileInfo.transformCacheFiles); + }, + cleanUp(): void { + resetMain(); + resetComponentMap(); + resetEtsCheck(); + resetEtsTransform(); + resetProcessComponentMember(); + resetProcessUiSyntax(); + resetUtils(); + resetValidateUiSyntax(); + resetObfuscation(); + } + }; +} + +export function shouldEmitJsFlagById(id: string): boolean { + return shouldEmitJsFlagMap.get(id); +} + +// If a ArkTS file don't have @Entry decorator but it is entry file this time +function disableNonEntryFileCache(filePath: string): boolean { + return storedFileInfo.buildStart && filePath.match(/(? {}; + compilerHost.resolveModuleNames = resolveModuleNames; + compilerHost.getCurrentDirectory = (): string => process.cwd(); + compilerHost.getDefaultLibFileName = (options: ts.CompilerOptions): string => ts.getDefaultLibFilePath(options); + compilerHost.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives; + return compilerHost; +} + +let compilerHost: ts.CompilerHost = null; + +if (!compilerHost) { + compilerHost = getCompilerHost(); +} + + +const arkoalaTsProgramCache: WeakMap = new WeakMap(); + +function getArkoalaTsProgram(program: ts.Program): ts.Program { + let extendedProgram = arkoalaTsProgramCache.get(program); + if (!extendedProgram) { + const pluginOptions: ArkoalaPluginOptions = {}; + // This is a stub for the interface generated by ts-patch. + // Probably we can use the reported diagnostics in the output + const pluginTransformExtras: Object = { + diagnostics: [], + addDiagnostic(): number {return 0}, + removeDiagnostic(): void {}, + ts: ts, + library: 'typescript', + }; + extendedProgram = arkoalaProgramTransform(program, compilerHost, pluginOptions, pluginTransformExtras); + arkoalaTsProgramCache.set(program, extendedProgram); + } + return extendedProgram; +} + +async function transform(code: string, id: string) { + const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'transform'); + if (!filter(id)) { + return null; + } + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.STORED_FILE_INFO_TRANSFORM); + storedFileInfo.collectTransformedFiles(path.resolve(id)); + MemoryMonitor.stopRecordStage(recordInfo); + const logger = this.share.getLogger('etsTransform'); + const hvigorLogger = this.share.getHvigorConsoleLogger?.(ARKUI_SUBSYSTEM_CODE); + + if (projectConfig.compileMode !== 'esmodule') { + const compilerOptions = ts.readConfigFile( + path.resolve(__dirname, '../../../../../tsconfig.json'), ts.sys.readFile).config.compilerOptions; + compilerOptions.moduleResolution = 'nodenext'; + compilerOptions.module = 'es2020'; + const newContent: string = jsBundlePreProcess(code, id, this.getModuleInfo(id).isEntry, logger, hvigorLogger); + const result: ts.TranspileOutput = ts.transpileModule(newContent, { + compilerOptions: compilerOptions, + fileName: id, + transformers: { before: [processUISyntax(null)] } + }); + + resetCollection(); + if (transformLog && transformLog.errors.length && !projectConfig.ignoreWarning) { + emitLogInfo(logger, getTransformLog(transformLog), true, id, hvigorLogger); + resetLog(); + } + + return { + code: result.outputText, + map: result.sourceMapText ? JSON.parse(result.sourceMapText) : new MagicString(code).generateMap() + }; + } + + let tsProgram: ts.Program = globalProgram.program; + let targetSourceFile: ts.SourceFile | undefined = tsProgram.getSourceFile(id); + + // createProgram from the file which does not have corresponding ast from ets-checker's program + // by those following cases: + // 1. .ets/.ts imported by .js file with tsc's `allowJS` option is false. + // 2. .ets/.ts imported by .js file with same name '.d.ts' file which is prior to .js by tsc default resolving + if (!targetSourceFile) { + await processNoTargetSourceFile(id, code, compilationTime); + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.GLOBAL_PROGRAM_GET_CHECKER); + // init TypeChecker to run binding + globalProgram.checker = tsProgram.getTypeChecker(); + globalProgram.strictChecker = tsProgram.getLinterTypeChecker(); + MemoryMonitor.stopRecordStage(recordInfo); + targetSourceFile = tsProgram.getSourceFile(id)!; + storedFileInfo.reUseProgram = false; + collectAllFiles(tsProgram); + } else { + if (!storedFileInfo.reUseProgram) { + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.GLOBAL_PROGRAM_GET_CHECKER); + globalProgram.checker = globalProgram.program.getTypeChecker(); + globalProgram.strictChecker = globalProgram.program.getLinterTypeChecker(); + MemoryMonitor.stopRecordStage(recordInfo); + } + storedFileInfo.reUseProgram = true; + } + setPkgNameForFile(this.getModuleInfo(id)); + startTimeStatisticsLocation(compilationTime ? compilationTime.validateEtsTime : undefined); + validateEts(code, id, this.getModuleInfo(id).isEntry, logger, targetSourceFile, hvigorLogger); + stopTimeStatisticsLocation(compilationTime ? compilationTime.validateEtsTime : undefined); + const emitResult: EmitResult = { outputText: '', sourceMapText: '' }; + const writeFile: ts.WriteFileCallback = (fileName: string, data: string) => { + if (/.map$/.test(fileName)) { + emitResult.sourceMapText = data; + } else { + emitResult.outputText = data; + } + }; + + // close `noEmit` to make invoking emit() effective. + tsProgram.getCompilerOptions().noEmit = false; + const metaInfo: Object = this.getModuleInfo(id).meta; + const lazyImportOptions: LazyImportOptions = { + autoLazyImport: this.share.projectConfig?.autoLazyImport ?? false, + reExportCheckMode: this.share.projectConfig?.reExportCheckMode ?? reExportNoCheckMode + }; + const mixCompile: boolean = this.share.projectConfig?.mixCompile ?? false; + // use `try finally` to restore `noEmit` when error thrown by `processUISyntax` in preview mode + startTimeStatisticsLocation(compilationTime ? compilationTime.shouldEmitJsTime : undefined); + const shouldEmitJsFlag: boolean = getShouldEmitJs(projectConfig.shouldEmitJs, targetSourceFile, id); + shouldEmitJsFlagMap.set(id, shouldEmitJsFlag); + stopTimeStatisticsLocation(compilationTime ? compilationTime.shouldEmitJsTime : undefined); + let transformResult: ts.TransformationResult = null; + ProjectCollections.projectWhiteListManager?.setCurrentCollector(id); + try { + startTimeStatisticsLocation(compilationTime ? compilationTime.tsProgramEmitTime : undefined); + const recordInfo = MemoryMonitor.recordStage(MemoryDefine.GLOBAL_PROGRAM_UI_KIT); + if (projectConfig.useArkoala) { + tsProgram = getArkoalaTsProgram(tsProgram); + targetSourceFile = tsProgram.getSourceFile(id); + } + if (shouldEmitJsFlag) { + startTimeStatisticsLocation(compilationTime ? compilationTime.emitTime : undefined); + const uiKitrecordInfo = MemoryMonitor.recordStage(MemoryDefine.GLOBAL_PROGRAM_UI_KIT); + tsProgram.emit(targetSourceFile, writeFile, undefined, undefined, + { + before: [ + // interopTransform:The hybrid compilation scenario provides the following two capabilities: + // 1. Support for creating 1.2 type object literals in 1.1 modules + // 2. Support 1.1 classes to implement 1.2 interfaces + interopTransform(tsProgram, id, mixCompile), + processUISyntax(null, false, compilationTime, id), + processKitImport(id, metaInfo, compilationTime, true, lazyImportOptions), + collectReservedNameForObf(this.share.arkProjectConfig?.obfuscationMergedObConfig, + shouldETSOrTSFileTransformToJSWithoutRemove(id, projectConfig, metaInfo)) + ] + } + ); + MemoryMonitor.stopRecordStage(uiKitrecordInfo); + stopTimeStatisticsLocation(compilationTime ? compilationTime.emitTime : undefined); + } else { + const uiKitrecordInfo = MemoryMonitor.recordStage(MemoryDefine.GLOBAL_PROGRAM_UI_KIT); + startTimeStatisticsLocation(compilationTime ? compilationTime.transformNodesTime : undefined); + const emitResolver: ts.EmitResolver = globalProgram.checker.getEmitResolver(outFile(tsProgram.getCompilerOptions()) ? + undefined : targetSourceFile, undefined); + transformResult = ts.transformNodes(emitResolver, tsProgram.getEmitHost?.(), ts.factory, + tsProgram.getCompilerOptions(), [targetSourceFile], + // interopTransform:The hybrid compilation scenario provides the following two capabilities: + // 1. Support for creating 1.2 type object literals in 1.1 modules + // 2. Support 1.1 classes to implement 1.2 interfaces + [interopTransform(tsProgram, id, mixCompile), + processUISyntax(null, false, compilationTime, id), + processKitImport(id, metaInfo, compilationTime, false, lazyImportOptions), + collectReservedNameForObf(this.share.arkProjectConfig?.obfuscationMergedObConfig, + shouldETSOrTSFileTransformToJSWithoutRemove(id, projectConfig, metaInfo))], false); + stopTimeStatisticsLocation(compilationTime ? compilationTime.transformNodesTime : undefined); + MemoryMonitor.stopRecordStage(uiKitrecordInfo); + } + stopTimeStatisticsLocation(compilationTime ? compilationTime.tsProgramEmitTime : undefined); + MemoryMonitor.stopRecordStage(recordInfo); + } finally { + // restore `noEmit` to prevent tsc's watchService emitting automatically. + tsProgram.getCompilerOptions().noEmit = true; + } + + resetCollection(); + processStructComponentV2.resetStructMapInEts(); + if (((transformLog && transformLog.errors.length) || (kitTransformLog && kitTransformLog.errors.length)) && + !projectConfig.ignoreWarning) { + emitLogInfo(logger, getTransformLog(interopTransformLog), true, id); + emitLogInfo(logger, getTransformLog(kitTransformLog), true, id); + emitLogInfo(logger, getTransformLog(transformLog), true, id, hvigorLogger); + resetLog(); + resetKitImportLog(); + } + + return shouldEmitJsFlag ? { + code: emitResult.outputText, + // Use magicString to generate sourceMap because of Typescript do not emit sourceMap in some cases + map: emitResult.sourceMapText ? JSON.parse(emitResult.sourceMapText) : new MagicString(code).generateMap(), + meta: { + shouldEmitJs: true + } + } : printSourceFile(transformResult.transformed[0], compilationTime); +} + +async function processNoTargetSourceFile(id: string, code: string, compilationTime?: CompilationTimeStatistics): Promise { + startTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined); + if (storedFileInfo.isFirstBuild && storedFileInfo.changeFiles.length) { + storedFileInfo.newTsProgram = ts.createProgram(storedFileInfo.changeFiles, etsCheckerCompilerOptions, compilerHost); + storedFileInfo.isFirstBuild = false; + } + await CreateProgramMoment.block(id, code); + CreateProgramMoment.release(id); + globalProgram.program.initProcessingFiles(); + for (const root of CreateProgramMoment.getRoots(id, code)) { + if (!globalProgram.program.getSourceFile(root.id)) { + const newSourceFile: ts.SourceFile = ts.createSourceFile(root.id, root.code, etsCheckerCompilerOptions.target, + true, undefined, etsCheckerCompilerOptions); + newSourceFile.originalFileName = newSourceFile.fileName; + newSourceFile.resolvePath = root.id; + newSourceFile.path = root.id; + globalProgram.program.processImportedModules(newSourceFile, true); + globalProgram.program.setProgramSourceFiles(newSourceFile); + CreateProgramMoment.deleteFileCollect.add(newSourceFile.fileName); + } + } + const processingFiles: ts.SourceFile[] = globalProgram.program.getProcessingFiles(); + if (processingFiles) { + processingFiles.forEach(file => { + if (!globalProgram.program.getSourceFiles().includes(file.fileName)) { + CreateProgramMoment.deleteFileCollect.add(file.fileName); + } + globalProgram.program.setProgramSourceFiles(file); + }); + } + globalProgram.program.refreshTypeChecker(); + stopTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined); +} + +function printSourceFile(sourceFile: ts.SourceFile, compilationTime: CompilationTimeStatistics): string | null { + if (sourceFile) { + startTimeStatisticsLocation(compilationTime ? compilationTime.printNodeTime : undefined); + const printer: ts.Printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const sourceCode: string = printer.printNode(ts.EmitHint.Unspecified, sourceFile, sourceFile); + stopTimeStatisticsLocation(compilationTime ? compilationTime.printNodeTime : undefined); + return sourceCode; + } + return null; +} + +function outFile(options: ts.CompilerOptions): string { + return options.outFile || options.out; +} + +function getShouldEmitJs(shouldEmitJs: boolean, targetSourceFile: ts.SourceFile, id: string): boolean { + let shouldEmitJsFlag: boolean = true; + let hasKeepTs: boolean = false; + if (!projectConfig.processTs) { + return shouldEmitJsFlag; + } + if (projectConfig.complieHar) { + if (!projectConfig.UseTsHar && !projectConfig.byteCodeHar) { + return shouldEmitJsFlag; + } + } else { + hasKeepTs = checkHasKeepTs(targetSourceFile); + } + // FA model/code coverage instrumentation/default situation + // These three situations require calling the emit interface, while in other cases 'shouldEmitJs' be false. + // The 'shouldEmitJS' variable is obtained through 'this.share.sprojectConfig'. + if (shouldEmitJs !== undefined) { + // ark es2abc + shouldEmitJsFlag = shouldEmitJs || ts.hasTsNoCheckOrTsIgnoreFlag(targetSourceFile) && !hasKeepTs; + } + return shouldEmitJsFlag; +} + +function setPkgNameForFile(moduleInfo: Object): void { + if (moduleInfo && moduleInfo.meta && moduleInfo.meta.pkgName) { + storedFileInfo.getCurrentArkTsFile().pkgName = moduleInfo.meta.pkgName; + } +} + +function validateEts(code: string, id: string, isEntry: boolean, logger: Object, sourceFile: ts.SourceFile, + hvigorLogger: Object | undefined = undefined): void { + if (/\.ets$/.test(id)) { + clearCollection(); + const fileQuery: string = isEntry && !abilityPagesFullPath.has(path.resolve(id).toLowerCase()) ? '?entry' : ''; + const log: LogInfo[] = validateUISyntax(code, code, id, fileQuery, sourceFile); + if (log.length && !projectConfig.ignoreWarning) { + emitLogInfo(logger, log, true, id, hvigorLogger); + } + } +} + +function jsBundlePreProcess(code: string, id: string, isEntry: boolean, logger: Object, + hvigorLogger: Object | undefined = undefined): string { + if (/\.ets$/.test(id)) { + clearCollection(); + let content = preprocessExtend(code); + content = preprocessNewExtend(content); + const fileQuery: string = isEntry && !abilityPagesFullPath.has(path.resolve(id).toLowerCase()) ? '?entry' : ''; + const log: LogInfo[] = validateUISyntax(code, content, id, fileQuery); + if (log.length && !projectConfig.ignoreWarning) { + emitLogInfo(logger, log, true, id, hvigorLogger); + } + return content; + } + return code; +} + +function clearCollection(): void { + componentCollection.customComponents.clear(); + CUSTOM_BUILDER_METHOD.clear(); + INNER_CUSTOM_LOCALBUILDER_METHOD.clear(); + GLOBAL_CUSTOM_BUILDER_METHOD.clear(); + INNER_CUSTOM_BUILDER_METHOD.clear(); + storedFileInfo.getCurrentArkTsFile().compFromDETS.clear(); + storedFileInfo.getCurrentArkTsFile().recycleComponents.clear(); +} + +function resetCollection() { + componentInfo.id = 0; + propertyCollection.clear(); + linkCollection.clear(); + EXTEND_ATTRIBUTE.clear(); + resetComponentCollection(); + storedFileInfo.hasLocalBuilderInFile = false; +} + +function resetEtsTransform(): void { + ShouldEnableDebugLine.enableDebugLine = false; + projectConfig.ignoreWarning = false; + projectConfig.widgetCompile = false; + compilerHost = null; + disableCacheOptions = { + bundleName: 'default', + entryModuleName: 'default', + runtimeOS: 'default', + resourceTableHash: 'default', + etsLoaderVersion: 'default' + }; +} + +function findArkoalaRoot(): string { + let arkoalaSdkRoot: string; + if (process.env.ARKOALA_SDK_ROOT) { + arkoalaSdkRoot = process.env.ARKOALA_SDK_ROOT; + if (!isDir(arkoalaSdkRoot)) { + throw new Error('Arkoala SDK not found in ' + arkoalaSdkRoot); + } + } else { + const arkoalaPossiblePaths: string[] = globalModulePaths.map(dir => path.join(dir, '../../arkoala')); //??? + arkoalaSdkRoot = arkoalaPossiblePaths.find(possibleRootDir => isDir(possibleRootDir)) ?? ''; + if (!arkoalaSdkRoot) { + throw new Error('Arkoala SDK not found in ' + arkoalaPossiblePaths.join(';')); + } + } + + return arkoalaSdkRoot; +} + +function isDir(filePath: string): boolean { + try { + let stat: fs.Stats = fs.statSync(filePath); + return stat.isDirectory(); + } catch (e) { + return false; + } +} + +function setIncrementalFileInHar(cacheFilePath: string, buildFilePath: string, allFilesInHar: Map): void { + if (cacheFilePath.match(/\.d.e?ts$/)) { + allFilesInHar.set(cacheFilePath, buildFilePath); + return; + } + let extName = projectConfig.useTsHar ? '.ts' : '.js'; + allFilesInHar.set(cacheFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts'), + buildFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts')); + allFilesInHar.set(cacheFilePath.replace(/\.e?ts$/, extName), buildFilePath.replace(/\.e?ts$/, extName)); +} + +function checkRelateToConstEnum(id: string): boolean { + let tsProgram: ts.Program = globalProgram.builderProgram; + let targetSourceFile: ts.SourceFile | undefined = tsProgram ? tsProgram.getSourceFile(id) : undefined; + if (!targetSourceFile) { + return false; + } + if (!tsProgram.isFileUpdateInConstEnumCache) { + return false; + } + return tsProgram.isFileUpdateInConstEnumCache(targetSourceFile); +} + +interface moduleInfoType { + id: string; +}; + +interface optionsType { + id: string; +}; + +interface newSourceFileType { + id: string; + code: string; +}; + +class CreateProgramMoment { + static transFileCollect: Set = new Set(); + static awaitFileCollect: Set = new Set(); + static deleteFileCollect: Set = new Set(); + static moduleParsedFileCollect: Set = new Set(); + static promise: Promise = undefined; + static emitter = undefined; + static roots: Map = new Map(); + + static init(): void { + if (CreateProgramMoment.promise) { + return; + } + const commonDtsPath: string = path.resolve(__dirname, '../../../declarations/common.d.ts'); //??? + CreateProgramMoment.roots.set(commonDtsPath, fs.readFileSync(commonDtsPath, 'utf-8')); + CreateProgramMoment.emitter = new nodeEvents.EventEmitter(); + CreateProgramMoment.promise = new Promise(resolve => { + CreateProgramMoment.emitter.on('checkPrefCreateProgramId', () => { + if (CreateProgramMoment.awaitFileCollect.size + CreateProgramMoment.moduleParsedFileCollect.size === + CreateProgramMoment.transFileCollect.size) { + resolve(); + } + }); + }); + } + + static getPlugin() { + return { + name: 'createProgramPlugin', + load: { + order: 'pre', + handler(id: string): void { + CreateProgramMoment.transFileCollect.add(id); + } + }, + + moduleParsed(moduleInfo: moduleInfoType): void { + CreateProgramMoment.moduleParsedFileCollect.add(moduleInfo.id); + CreateProgramMoment.emitter?.emit('checkPrefCreateProgramId'); + }, + cleanUp(): void { + shouldEmitJsFlagMap.clear(); + CreateProgramMoment.reset(); + } + }; + } + + static async block(id: string, code: string): Promise { + CreateProgramMoment.init(); + CreateProgramMoment.awaitFileCollect.add(id); + CreateProgramMoment.roots.set(id, code); + CreateProgramMoment.emitter.emit('checkPrefCreateProgramId'); + return CreateProgramMoment.promise; + } + + static release(id: string): void { + CreateProgramMoment.awaitFileCollect.delete(id); + } + + static reset(): void { + CreateProgramMoment.transFileCollect.clear(); + CreateProgramMoment.awaitFileCollect.clear(); + CreateProgramMoment.moduleParsedFileCollect.clear(); + CreateProgramMoment.promise = undefined; + CreateProgramMoment.emitter = undefined; + CreateProgramMoment.roots.clear(); + } + + static resetDeleteFiles(): void { + CreateProgramMoment.deleteFileCollect.clear(); + } + + static getRoots(id: string, code: string): newSourceFileType[] { + const res: newSourceFileType[] = []; + for (const [id, code] of CreateProgramMoment.roots) { + res.push({id, code}); + } + CreateProgramMoment.promise = undefined; + CreateProgramMoment.emitter = undefined; + CreateProgramMoment.roots.clear(); + if (res.length === 0) { + return [{id, code}]; + } + return res; + } +} + +exports.createProgramPlugin = CreateProgramMoment.getPlugin; diff --git a/compiler/src/interop/src/fast_build/meomry_monitor/memory_define.ts b/compiler/src/interop/src/fast_build/meomry_monitor/memory_define.ts new file mode 100755 index 0000000000000000000000000000000000000000..54ec9e7d2eeda8299768dd481805fe6e0cbd5399 --- /dev/null +++ b/compiler/src/interop/src/fast_build/meomry_monitor/memory_define.ts @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export enum MemoryDefine { + // fileName(functionName: reocrd position) + WRITE_OBFUSCATED_SOURCE_CODE = 'ark_utils(writeObfuscatedSourceCode: writeArkguardObfuscatedSourceCode)', + ETS_CHECKER_CREATE_LANGUAGE_SERVICE = 'ets_checker(createLanguageService: tscreateLanguageService)', + CREATE_LANGUAGE_SERVICE = 'ets_checker(serviceChecker: create-languageService)', + GET_BUILDER_PROGRAM = 'ets_checker(serviceChecker: getBuilderProgram)', + RUN_ARK_TS_LINTER = 'ets_checker(serviceChecker: runArkTSLinter)', + PROCESS_BUILD_HAP = 'ets_checker(serviceChecker: processBuildHap)', + COLLECT_TSC_FILES_ALL_RESOLVED_MODULES = 'ets_checker(collectTscFiles: allResolvedModules)', + MERGE_ROLL_UP_FILES_LOCAL_PACKAGE_SET = 'ets_checker(mergeRollUpFiles: localPackageSet)', + PROCESS_BUILD_HAP_GET_SEMANTIC_DIAGNOSTICS = 'ets_checker(processBuildHap: getSemanticDiagnostics)', + PROCESS_BUILD_HAP_EMIT_BUILD_INFO = 'ets_checker(processBuildHap: emitBuildInfo)', + FILE_TO_IGNORE_DIAGNOSTICS = 'ets_checker(collectFileToIgnoreDiagnostics: fileToIgnoreDiagnostics)', + NEW_SOURCE_FILE = 'process_kit_import(processKitImport: ModuleSourceFile.newSourceFile)', + UPDATE_SOURCE_MAPS = 'generate_sourcemap(buildModuleSourceMapInfo: SourceMapGenerator-updateSourceMaps)', + MODULE_SOURCE_FILE_NEW_SOURCE_FILE = 'transform(transformForModule: ModuleSourceFile-newSourceFile)', + INIT_ARK_PROJECT_CONFIG = 'process_ark_config(initArkProjectConfig: initObfuscationConfig)', + PKG_ENTRY_INFOS_MODULE_INFOS = 'module_mode(collectModuleFileList: pkgEntryInfos-moduleInfos)', + SCAN_SOURCEFILES = 'module_source_file(processModuleSourceFiles: sourceProjectConfig)', + ALL_FILES_OBFUSCATION = 'module_source_file(processModuleSourceFiles: Allfilesobfuscation)', + FILES_FOR_EACH = 'module_source_file(processModuleSourceFiles: ModuleSourceFile-sourceFiles-forEach)', + ROLLUP_PLUGIN_BUILD_START = 'rollup-plugin-ets-checker(etsChecker: buildStart)', + BUILDER_PROGRAM = 'rollup-plugin-ets-checker(etsChecker: buildStart-builderProgram)', + COLLECT_FILE_TOIGNORE_RUN_TSLINTER = 'rollup-plugin-ets-checker(etsChecker: collectFileToIgnoreDiagnosticsRunArkTSLinter)', + SET_INCREMENTAL_FILE_IN_HAR = 'rollup-plugin-ets-typescript(load: setIncrementalFileInHar)', + STORED_FILE_INFO_TRANSFORM = 'rollup-plugin-ets-typescript(transform: storedFileInfo-transform)', + GLOBAL_PROGRAM_GET_CHECKER = 'rollup-plugin-ets-typescript(transform: globalProgram-getChecker)', + GLOBAL_PROGRAM_UI_KIT = 'rollup-plugin-ets-typescript(transform: globalProgram-ui/kit)', +} + diff --git a/compiler/src/interop/src/fast_build/meomry_monitor/memory_report.ts b/compiler/src/interop/src/fast_build/meomry_monitor/memory_report.ts new file mode 100644 index 0000000000000000000000000000000000000000..7fd65130200800e4b65fdd679fbd869ac7a91275 --- /dev/null +++ b/compiler/src/interop/src/fast_build/meomry_monitor/memory_report.ts @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * report Memory tracing Object + */ +export interface MemoryReport { + timestamp: number; + rss: number; + stage: string; + parentStage?: string; +} + +/** + * create MemoryReport Object + * @param stage now memory tracing stage + * @param parentStage now memory tracing stage + * @returns + */ +export function getMemoryReport(stage: string, parentStage?: string): MemoryReport { + const rss = process.memoryUsage.rss(); + const report: MemoryReport = { + timestamp: Date.now(), + rss: rss, + stage: stage, + parentStage: parentStage + }; + return report; +} + diff --git a/compiler/src/interop/src/fast_build/meomry_monitor/memory_worker.ts b/compiler/src/interop/src/fast_build/meomry_monitor/memory_worker.ts new file mode 100644 index 0000000000000000000000000000000000000000..6296ec7a615798aef9e0dc2c3d222d2213d86b93 --- /dev/null +++ b/compiler/src/interop/src/fast_build/meomry_monitor/memory_worker.ts @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + parentPort, + workerData +} from 'worker_threads'; +import * as fs from 'fs'; +import { MemoryReport } from './memory_report'; + +interface StageReport { + // Is the current StageReport in the record stage + record: boolean; + // Indicates the maximum RSS memory usage in the current Stage + top: number; + // The minimum RSS memory usage in the current stage + bottom: number; + // the total number of memory reports for the current Stage + data: MemoryReport[]; +} + +interface QueryMemReport { + [stageName: string]: StageReport; +} + +class MemoryCollector { + private reports: MemoryReport[] = []; + private filePath: string; + private bufferInterval: number; + private writeInterval: number; + private writeTimeoutId: NodeJS.Timeout | null = null; + private stage: string = ''; + private parentStage: string = ''; + private intervalId: NodeJS.Timeout | null = null; + private stopIntervalId: NodeJS.Timeout | null = null; + private recordMap = new Map(); + private recordStack: string[] = []; + private lastRecordTime: number = 0; + private lastRssValue: number = 0; + private lastStage: string = ''; + private throttlePercentage: number = 1.05; + private dottingFileExitFlag: boolean = false; + + + constructor(filePath: string, bufferInterval: number = 100, writeInterval: number = 1000) { + this.filePath = filePath; + this.bufferInterval = bufferInterval; + this.writeInterval = writeInterval; + } + + private recordMemoryUsage(memReport?: MemoryReport): void { + const now = Date.now(); + if (!memReport) { + const rss = process.memoryUsage.rss(); + memReport = { + timestamp: now, + rss: rss, + stage: this.stage, + parentStage: this.parentStage, + }; + } + const currentRss = memReport?.rss || process.memoryUsage().rss; + if ( + (memReport.stage && memReport.stage !== this.lastStage) || + now - this.lastRecordTime >= this.bufferInterval || + currentRss / this.lastRssValue >= this.throttlePercentage + ) { + this.lastRecordTime = now; + this.lastRssValue = currentRss; + this.lastStage = this.stage; + if (memReport.stage !== '') { + memReport.stage = this.getRealStageName(memReport.stage); + memReport.parentStage = this.getRealStageName(memReport.parentStage); + this.reports.push(memReport); + } + } + if (this.writeTimeoutId == null) { + this.writeTimeoutId = setTimeout(() => { + this.flushReports(); + }, this.writeInterval); + } + } + + private flushReports(): void { + if (this.writeTimeoutId !== null) { + clearTimeout(this.writeTimeoutId); + this.writeTimeoutId = null; + } + if (this.reports.length > 0) { + try { + fs.accessSync(this.filePath, fs.constants.F_OK); + } catch (err) { + if (!this.dottingFileExitFlag) { + let data = this.reports.map((report) => JSON.stringify(report)).join(',\n') + ',\n'; + this.reports = []; + fs.writeFileSync(this.filePath, data, 'utf8'); + this.dottingFileExitFlag = true; + } + return; + } + if (this.dottingFileExitFlag) { + let data = this.reports.map((report) => JSON.stringify(report)).join(',\n') + ',\n'; + this.reports = []; + fs.appendFileSync(this.filePath, data, 'utf8'); + } + } + } + + private getRealStageName(str: string): string { + let index = str.lastIndexOf(')'); + if (index !== -1) { + return str.slice(0, index + 1); + } + return str; + } + + private containsNonStopValue(map: Map): boolean { + for (let value of map.values()) { + if (value !== 'stop') { + return true; + } + } + return false; + } + + start(): void { + this.intervalId = setInterval(() => { + this.recordMemoryUsage(); + }, this.bufferInterval); + } + + stop(): void { + this.stopIntervalId = setInterval(() => { + if (this.containsNonStopValue(this.recordMap)) { + return; + } + + if (this.stopIntervalId) { + clearInterval(this.stopIntervalId); + this.stopIntervalId = null; + } + if (this.intervalId) { + clearInterval(this.intervalId); + this.intervalId = null; + } + if (this.writeTimeoutId) { + clearTimeout(this.writeTimeoutId); + this.writeTimeoutId = null; + } + this.flushReports(); + parentPort!.postMessage({ action: 'stop_end'}); + }, 5); + } + + record(stage: string, memReport?: MemoryReport): void { + this.recordMap.set(stage, 'record'); + if (this.recordStack.length > 0) { + this.parentStage = this.stage; + } + this.stage = stage; + this.recordStack.push(stage); + if (memReport) { + memReport.parentStage = this.parentStage; + } + this.recordMemoryUsage(memReport); + } + + stopRecord(stage: string, memReport?: MemoryReport): void { + this.recordMap.set(stage, 'stop'); + if (stage === this.stage) { + this.recordStack.pop(); + this.updateStages(); + this.updateMemReport(memReport); + } else { + const index = this.recordStack.indexOf(stage); + if (index !== -1) { + this.updateMemReport(memReport, index); + this.recordStack.splice(index); + this.updateStages(); + } else { + return; + } + } + this.recordMemoryUsage(memReport); + } + + private updateStages(): void { + const length = this.recordStack.length; + if (length > 1) { + this.stage = this.recordStack[length - 1]; + this.parentStage = this.recordStack[length - 2]; + } else if (length === 1) { + this.stage = this.recordStack[0]; + this.parentStage = ''; + } else { + this.stage = ''; + this.parentStage = ''; + } + } + + private updateMemReport(memReport: MemoryReport | undefined, index?: number): void { + if (memReport) { + if (index !== undefined && index - 1 >= 0) { + memReport.parentStage = this.recordStack[index - 1]; + } else { + memReport.parentStage = this.stage; + } + } + } + + addMemoryUsage(memReport: MemoryReport): void { + this.recordMemoryUsage(memReport); + } + + handleRecord(recordStr: string, stage: string, report: QueryMemReport): void { + try { + const reportObj = JSON.parse(recordStr); + if (reportObj.stage.indexof(stage)) { + if (reportObj.rss > report.stage.top) { + report.stage.top = reportObj.rss; + } + if (reportObj.rss < report.stage.bottom) { + report.stage.bottom = reportObj.rss; + } + reportObj.stage = stage; + report.stage.data.push(reportObj); + } + } catch (e) { + console.error(`Error parsing JSON: ${recordStr}`); + console.error(e); + } + } + + queryMemoryUsage(requestId: number, stage: string): void { + let record = this.recordMap.has(stage); + let stageReport: StageReport = { + record: record, + top: -1, + bottom: -1, + data: [], + }; + let report: QueryMemReport = {}; + report[stage] = stageReport; + let currentRecord = ''; + let inRecord = false; + const stream = fs.createReadStream(this.filePath, { encoding: 'utf8' }); + stream.on('data', (chunk) => { + for (let char of chunk) { + if (char === '{') { + inRecord = true; + currentRecord = char; + } else if (char === '}') { + inRecord = false; + currentRecord += char; + this.handleRecord(currentRecord, stage, report); + currentRecord = ''; + } else if (inRecord) { + currentRecord += char; + } + } + }); + + stream.on('end', () => { + parentPort!.postMessage({ action: 'memoryReport', requestId: requestId, report: report }); + }); + stream.on('error', (err) => { + parentPort!.postMessage({ action: 'memoryReport', requestId: requestId, report: {} }); + }); + } +} + +if (workerData) { + const { filePath, bufferInterval, writeInterval } = workerData; + const collector = new MemoryCollector(filePath, bufferInterval, writeInterval); + collector.start(); + parentPort!.on('message', (msg) => { + if (msg.action === 'stop') { + collector.stop(); + } else if (msg.action === 'recordStage') { + collector.record(msg.stage + msg.recordIndex, msg.memoryReport); + } else if (msg.action === 'stopRecordStage') { + collector.stopRecord(msg.stage + msg.recordIndex, msg.memoryReport); + } else if (msg.action === 'addMemoryReport') { + collector.addMemoryUsage(msg.memoryReport); + } else if (msg.action === 'queryMemoryUsage') { + collector.queryMemoryUsage(msg.requestId, msg.stage); + } + }); +} + diff --git a/compiler/src/interop/src/fast_build/meomry_monitor/rollup-plugin-memory-monitor.ts b/compiler/src/interop/src/fast_build/meomry_monitor/rollup-plugin-memory-monitor.ts new file mode 100644 index 0000000000000000000000000000000000000000..4dd48eefaa36b4d1c56fb3be1d832d0db629b1f9 --- /dev/null +++ b/compiler/src/interop/src/fast_build/meomry_monitor/rollup-plugin-memory-monitor.ts @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Worker } from 'worker_threads'; +import { + getMemoryReport, + MemoryReport +} from './memory_report'; +import { projectConfig } from '../../../main'; +import path from 'path'; +import fs from 'fs'; +import { ArkObfuscator } from 'arkguard'; +import * as ts from 'typescript'; + +export class MemoryMonitor { + private static instance: MemoryMonitor | null = null; + private worker: Worker | undefined; + private pendingQueries: Map void> = new Map(); + private requestIdCounter = 0; + private filePath = ''; + private recordIndexInfo: Map = new Map(); + + constructor() { + this.worker = undefined; + } + + static getInstance(): MemoryMonitor { + if (!MemoryMonitor.instance) { + MemoryMonitor.instance = new MemoryMonitor(); + } + return MemoryMonitor.instance; + } + + static recordStage(stage: string): RecordInfo { + return MemoryMonitor.getInstance().recordStageInner(stage); + } + + static stopRecordStage(recordInfo: RecordInfo): void { + return MemoryMonitor.getInstance().stopRecordStageInner(recordInfo); + } + + getRecordFileName(): string { + return this.filePath; + } + + start(): void { + if (!projectConfig.enableMemoryDotting) { + return; + } + if ( + projectConfig.enableMemoryDotting && + projectConfig.memoryDottingPath !== undefined && + !fs.existsSync(projectConfig.memoryDottingPath) + ) { + fs.mkdirSync(projectConfig.memoryDottingPath); + } else { + if (projectConfig.buildPath === undefined) { + projectConfig.buildPath = ''; + } + projectConfig.memoryDottingPath = path.resolve(projectConfig.buildPath, '../', '../', 'dottingfile'); //??? + } + this.filePath = path.resolve(projectConfig.memoryDottingPath, `memory${Date.now()}.log`); + let bufferInterval = + (projectConfig.memoryDottingRecordInterval && projectConfig.memoryDottingRecordInterval) || 100; + let writeInterval = + (projectConfig.memoryDottingWriteFileInterval && projectConfig.memoryDottingWriteFileInterval) || 1000; + const workerPath = path.resolve(__dirname, './memory_worker.js'); + this.worker = new Worker(workerPath, { + workerData: { bufferInterval: bufferInterval, writeInterval: writeInterval, filePath: this.filePath }, + }); + this.worker.on('error', (err) => { + console.error('Worker error:', err); + }); + this.worker.on('exit', (code) => { + if (code !== 0) { + console.error(`Worker stopped with exit code ${code}`); + } + }); + + this.worker.on('message', (data) => { + if (data.action === 'memoryReport') { + const requestId = data.requestId; + const report = data.report; + const resolveCallback = this.pendingQueries.get(requestId); + if (resolveCallback) { + resolveCallback(report); + this.pendingQueries.delete(requestId); + } + } else if (data.action === 'stop_end') { + ArkObfuscator.clearMemoryDottingCallBack(); + ts.MemoryDotting.clearCallBack(); + } + }); + } + + stop(): void { + if (this.worker) { + this.worker.postMessage({ action: 'stop' }); + } + } + + recordStageInner(stage: string): RecordInfo { + let recordIndex = this.recordIndexInfo.get(stage); + if (recordIndex !== undefined) { + recordIndex = recordIndex + 1; + this.recordIndexInfo.set(stage, recordIndex); + } else { + recordIndex = 1; + this.recordIndexInfo.set(stage, recordIndex); + } + if (this.worker) { + const memoryUsage = getMemoryReport(stage); + this.worker.postMessage({ + action: 'recordStage', + stage: stage, + memoryReport: memoryUsage, + recordIndex: recordIndex + }); + } + return { recordStage: stage, recordIndex: recordIndex }; + } + + stopRecordStageInner(recordInfo: RecordInfo): void { + if (this.worker) { + const memoryUsage = getMemoryReport(recordInfo.recordStage); + this.worker.postMessage({ action: 'stopRecordStage', stage: recordInfo.recordStage, memoryReport: memoryUsage }); + } + } + + addMemoryReport(memoryReport: MemoryReport): void { + if (this.worker) { + this.worker.postMessage({ action: 'addMemoryReport', memoryReport: memoryReport }); + } + } + + async queryMemoryUsage(stage: string): Promise { + return new Promise((resolve, reject) => { + if (this.worker) { + const requestId = ++this.requestIdCounter; + this.pendingQueries.set(requestId, resolve); + this.worker.postMessage({ action: 'queryMemoryUsage', requestId, stage }); + } else { + reject(new Error('Worker is not initialized.')); + } + }); + } + + cleanUp(): void { + if (this.worker) { + this.worker.terminate(); + this.worker = undefined; + MemoryMonitor.instance = null; + } + } +} + +export interface RecordInfo { + recordStage: string; + recordIndex: number; +} + +function setMemoryDottingCallBack(): void { + if (projectConfig.enableMemoryDotting) { + if (ts.MemoryDotting.setMemoryDottingCallBack !== undefined) { + ts.MemoryDotting.setMemoryDottingCallBack((stage: string) => { + return MemoryMonitor.recordStage(stage); + }, (recordInfo: RecordInfo) => { + MemoryMonitor.stopRecordStage(recordInfo); + }); + } + if (ArkObfuscator.setMemoryDottingCallBack !== undefined) { + ArkObfuscator.setMemoryDottingCallBack((stage: string) => { + return MemoryMonitor.recordStage(stage); + }, (recordInfo: RecordInfo) => { + MemoryMonitor.stopRecordStage(recordInfo); + }); + } + } +} + +interface MemoryMonitorLifecycle { + name: string; + buildStart: { + order: 'pre'; + handler(): void; + }; + buildEnd: { + order: 'post'; + handler(): void; + }; +} + +export function memoryMonitor(): MemoryMonitorLifecycle { + return { + name: 'memoryMonitor', + buildStart: { + order: 'pre', + handler(): void { + const memoryMonitorInstance: MemoryMonitor = MemoryMonitor.getInstance(); + memoryMonitorInstance.start(); + setMemoryDottingCallBack(); + }, + }, + buildEnd: { + order: 'post', + handler(): void { + const memoryMonitorInstance: MemoryMonitor = MemoryMonitor.getInstance(); + memoryMonitorInstance.stop(); + }, + }, + }; +} diff --git a/compiler/src/interop/src/fast_build/system_api/api_check_define.ts b/compiler/src/interop/src/fast_build/system_api/api_check_define.ts new file mode 100644 index 0000000000000000000000000000000000000000..94df5966fde9d30a07d2e725cb15f2947ede02dc --- /dev/null +++ b/compiler/src/interop/src/fast_build/system_api/api_check_define.ts @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const PERMISSION_TAG_CHECK_NAME: string = 'permission'; +export const PERMISSION_TAG_CHECK_ERROR: string = "To use this API, you need to apply for the permissions: $DT"; +export const SYSTEM_API_TAG_CHECK_NAME: string = 'systemapi'; +export const SYSTEM_API_TAG_CHECK_WARNING: string = "'{0}' is system api"; +export const TEST_TAG_CHECK_NAME: string = 'test'; +export const TEST_TAG_CHECK_ERROR: string = "'{0}' can only be used for testing directories "; +export const SYSCAP_TAG_CHECK_NAME: string = 'syscap'; +export const SYSCAP_TAG_CHECK_WARNING: string = "The system capacity of this api '{0}' is not supported on all devices"; +export const SYSCAP_TAG_CONDITION_CHECK_WARNING: string = 'The API is not supported on all devices. Use the canIUse condition to determine whether the API is supported.'; +export const CANIUSE_FUNCTION_NAME: string = 'canIUse'; +export const RUNTIME_OS_OH: string = 'OpenHarmony'; +export const FORM_TAG_CHECK_NAME: string = 'form'; +export const FORM_TAG_CHECK_ERROR: string = "'{0}' can't support form application."; +export const CROSSPLATFORM_TAG_CHECK_NAME: string = 'crossplatform'; +export const CROSSPLATFORM_TAG_CHECK_ERROER: string = "'{0}' can't support crossplatform application."; +export const DEPRECATED_TAG_CHECK_NAME: string = 'deprecated'; +export const DEPRECATED_TAG_CHECK_WARNING: string = "'{0}' has been deprecated."; +export const FA_TAG_CHECK_NAME: string = 'famodelonly'; +export const FA_TAG_HUMP_CHECK_NAME: string = 'FAModelOnly'; +export const FA_TAG_CHECK_ERROR: string = 'This API is used only in FA Mode, but the current Mode is Stage.'; +export const STAGE_TAG_CHECK_NAME: string = 'stagemodelonly'; +export const STAGE_TAG_HUMP_CHECK_NAME: string = 'StageModelOnly'; +export const STAGE_TAG_CHECK_ERROR: string = 'This API is used only in Stage Mode, but the current Mode is FA.'; +export const STAGE_COMPILE_MODE: string = 'moduleJson'; +export const ATOMICSERVICE_BUNDLE_TYPE: string = 'atomicService'; +export const ATOMICSERVICE_TAG_CHECK_NAME: string = 'atomicservice'; +export const ATOMICSERVICE_TAG_CHECK_ERROER: string = "'{0}' can't support atomicservice application."; +export const SINCE_TAG_NAME: string = 'since'; +export const SINCE_TAG_CHECK_ERROER: string = "The '{0}' API is supported since SDK version $SINCE1. However, the current compatible SDK version is $SINCE2."; +export const ATOMICSERVICE_TAG_CHECK_VERSION: number = 11; +export const FIND_MODULE_WARNING: string = "Cannot find name '{0}'."; + +export const CONSTANT_STEP_0: number = 0; +export const CONSTANT_STEP_1: number = 1; +export const CONSTANT_STEP_2: number = 2; +export const CONSTANT_STEP_3: number = 3; + +export const GLOBAL_DECLARE_WHITE_LIST: Set = new Set(['Context', 'PointerStyle', 'PixelMap', 'UnifiedData', + 'Summary', 'UniformDataType', 'IntentionCode', 'NavDestinationInfo', 'UIContext', 'Resource', 'WebviewController']); diff --git a/compiler/src/interop/src/fast_build/system_api/api_check_permission.ts b/compiler/src/interop/src/fast_build/system_api/api_check_permission.ts new file mode 100644 index 0000000000000000000000000000000000000000..ec5344fa00a5afcfa79c000bd704e922d36beadf --- /dev/null +++ b/compiler/src/interop/src/fast_build/system_api/api_check_permission.ts @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ts from 'typescript'; + +enum PermissionVaildTokenState { + Init, + LeftParenthesis, + RightParenthesis, + PermissionChar, + And, + Or, +} + +interface PermissionVaildCalcInfo { + valid: boolean; + currentToken: PermissionVaildTokenState; + finish: boolean; + currentPermissionMatch: boolean; +} + +interface PermissionVaildCalcGroup { + subQueue: string[]; + includeParenthesis: boolean; +} + +export interface PermissionModule { + modulePath: string; + testPermissions: string[]; + permissions: string[]; +} + +export class JsDocCheckService { + /** + * STER1. Parse the permission information configured on the API + * STEP2. Recursive queue to obtain whether the current permission configuration supports it + */ + static validPermission(comment: string, permissionsArray: string[]): boolean { + const permissionsItem: string[] = JsDocCheckService.getSplitsArrayWithDesignatedCharAndStr(comment ?? '', ' ') + .filter((item) => { + return item !== ''; + }); + const permissionsQueue: string[] = []; + permissionsItem.forEach((item: string) => { + //STEP1.1 Parse'(' + const leftParenthesisItem: string[] = JsDocCheckService.getSplitsArrayWithDesignatedCharAndArrayStr([item], '('); + //STEP1.2 Parse')' + const rightParenthesisItem: string[] = JsDocCheckService.getSplitsArrayWithDesignatedCharAndArrayStr(leftParenthesisItem, ')'); + permissionsQueue.push(...rightParenthesisItem); + }); + //STEP2 + const calcValidResult: PermissionVaildCalcInfo = { + valid: false, + currentToken: PermissionVaildTokenState.Init, + finish: false, + currentPermissionMatch: true, + }; + JsDocCheckService.validPermissionRecursion(permissionsQueue, permissionsArray, calcValidResult); + return calcValidResult.valid; + } + + private static validPermissionRecursion(permissionsQueue: string[], permissions: string[], calcValidResult: PermissionVaildCalcInfo): void { + if (permissionsQueue.some(item => ['(', ')'].includes(item))) { + const groups: PermissionVaildCalcGroup[] = JsDocCheckService.groupWithParenthesis(permissionsQueue); + const groupJoin: string[] = JsDocCheckService.getGroupItemPermission(groups, calcValidResult, permissions); + JsDocCheckService.getPermissionVaildAtoms(groupJoin, calcValidResult, permissions ?? []); + } else { + JsDocCheckService.getPermissionVaildAtoms(permissionsQueue, calcValidResult, permissions ?? []); + } + } + + private static getSplitsArrayWithDesignatedCharAndStr(permission: string, designatedChar: string): string[] { + return permission.split(designatedChar).map(item => item.trim()); + } + + private static getGroupItemPermission( + groups: PermissionVaildCalcGroup[], + calcValidResult: PermissionVaildCalcInfo, + permissions: string[]): string[] { + const groupJoin: string[] = []; + groups.forEach((groupItem: PermissionVaildCalcGroup) => { + if (groupItem.includeParenthesis) { + const calcValidResultItem: PermissionVaildCalcInfo = { + ...calcValidResult, + }; + const subStack: string[] = groupItem.subQueue.slice(1, groupItem.subQueue.length - 1); + JsDocCheckService.validPermissionRecursion(subStack, permissions, calcValidResultItem); + if (calcValidResultItem.valid) { + groupJoin.push(''); + } else { + groupJoin.push('NA'); + } + } else { + groupJoin.push(...groupItem.subQueue); + } + }); + return groupJoin; + } + + private static groupWithParenthesis(stack: string[]): PermissionVaildCalcGroup[] { + let currentLeftParenthesisCount: number = 0; + const groups: PermissionVaildCalcGroup[] = []; + let currentGroupItem: PermissionVaildCalcGroup = { + subQueue: [], + includeParenthesis: false, + }; + stack.forEach((item: string, index: number) => { + if (item === '(') { + if (currentLeftParenthesisCount === 0) { + groups.push(currentGroupItem); + currentGroupItem = { + subQueue: [item], + includeParenthesis: true + }; + } else { + currentGroupItem.subQueue.push(item); + } + currentLeftParenthesisCount++; + } else if (item === ')') { + currentLeftParenthesisCount--; + currentGroupItem.subQueue.push(item); + if (currentLeftParenthesisCount === 0) { + groups.push(currentGroupItem); + currentGroupItem = { + subQueue: [], + includeParenthesis: false, + }; + } + } else { + currentGroupItem.subQueue.push(item); + if (index === stack.length - 1) { + groups.push(currentGroupItem); + } + } + }); + return groups; + } + + private static getPermissionVaildAtoms(atomStacks: string[], calcValidResult: PermissionVaildCalcInfo, configPermissions: string[]): void { + if (calcValidResult.finish) { + return; + } + if (atomStacks[0] === 'and') { + calcValidResult.currentToken = PermissionVaildTokenState.And; + } else if (atomStacks[0] === 'or') { + calcValidResult.currentToken = PermissionVaildTokenState.Or; + } else { + if (calcValidResult.currentToken === PermissionVaildTokenState.Or) { + if (JsDocCheckService.inValidOrExpression( + atomStacks, + calcValidResult, + configPermissions + )) { + calcValidResult.currentPermissionMatch = false; + } + } else if (calcValidResult.currentToken === PermissionVaildTokenState.And) { + if (JsDocCheckService.inValidAndExpression( + atomStacks, + calcValidResult, + configPermissions + )) { + calcValidResult.currentPermissionMatch = false; + } + } else { + calcValidResult.currentPermissionMatch = + JsDocCheckService.validPermissionItem(atomStacks[0], configPermissions); + } + } + if (atomStacks.length > 1) { + JsDocCheckService.getPermissionVaildAtoms( + atomStacks.slice(1), + calcValidResult, + configPermissions + ); + } else { + calcValidResult.valid = calcValidResult.currentPermissionMatch; + calcValidResult.finish = true; + } + } + + private static inValidOrExpression( + atomStacks: string[], + calcValidResult: PermissionVaildCalcInfo, + configPermissions: string[]): boolean { + if ( + !calcValidResult.currentPermissionMatch && + !JsDocCheckService.validPermissionItem(atomStacks[0], configPermissions) + ) { + calcValidResult.valid = false; + return true; + } + calcValidResult.currentPermissionMatch = true; + return false; + } + private static inValidAndExpression( + atomStacks: string[], + calcValidResult: PermissionVaildCalcInfo, + configPermissions: string[]): boolean { + if ( + !calcValidResult.currentPermissionMatch || + !JsDocCheckService.validPermissionItem(atomStacks[0], configPermissions) + ) { + calcValidResult.valid = false; + return true; + } + calcValidResult.currentPermissionMatch = + JsDocCheckService.validPermissionItem(atomStacks[0], configPermissions); + return false; + } + private static validPermissionItem(atomStackItem: string, configPermissions: string[]): boolean { + return atomStackItem === '' || configPermissions.includes(atomStackItem); + } + + private static getSplitsArrayWithDesignatedCharAndArrayStr( + leftParenthesisItems: string[], + designatedChar: string + ): string[] { + const rightParenthesisItems: string[] = []; + leftParenthesisItems.forEach((leftParenthesisItem: string) => { + if (leftParenthesisItem.includes(designatedChar)) { + const rightParenthesis: string[] = + JsDocCheckService.getSplitsArrayWithDesignatedCharAndStr( + leftParenthesisItem, + designatedChar + ); + rightParenthesis.forEach((item: string) => { + if (item === '') { + rightParenthesisItems.push(designatedChar); + } else { + rightParenthesisItems.push(item); + } + }); + } else { + rightParenthesisItems.push(leftParenthesisItem); + } + }); + return rightParenthesisItems; + } +} diff --git a/compiler/src/interop/src/fast_build/system_api/api_check_utils.ts b/compiler/src/interop/src/fast_build/system_api/api_check_utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..5b6c5f5ee6fe4190b8577164f35f9361fee2529d --- /dev/null +++ b/compiler/src/interop/src/fast_build/system_api/api_check_utils.ts @@ -0,0 +1,666 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ts from 'typescript'; +import path from 'path'; +import fs from 'fs'; + +import { + projectConfig, + extendSdkConfigs, + globalProgram, + ohosSystemModulePaths, + systemModules, + allModulesPaths, + ohosSystemModuleSubDirPaths +} from '../../../main'; +import { + LogType, + LogInfo, + IFileLog +} from '../../utils'; +import { type ResolveModuleInfo } from '../../ets_checker'; +import { + PERMISSION_TAG_CHECK_NAME, + PERMISSION_TAG_CHECK_ERROR, + SYSTEM_API_TAG_CHECK_NAME, + SYSTEM_API_TAG_CHECK_WARNING, + TEST_TAG_CHECK_NAME, + TEST_TAG_CHECK_ERROR, + SYSCAP_TAG_CHECK_NAME, + SYSCAP_TAG_CONDITION_CHECK_WARNING, + SYSCAP_TAG_CHECK_WARNING, + CANIUSE_FUNCTION_NAME, + FORM_TAG_CHECK_NAME, + FORM_TAG_CHECK_ERROR, + FIND_MODULE_WARNING, + CROSSPLATFORM_TAG_CHECK_NAME, + CROSSPLATFORM_TAG_CHECK_ERROER, + DEPRECATED_TAG_CHECK_NAME, + DEPRECATED_TAG_CHECK_WARNING, + FA_TAG_CHECK_NAME, + FA_TAG_HUMP_CHECK_NAME, + FA_TAG_CHECK_ERROR, + STAGE_TAG_CHECK_NAME, + STAGE_TAG_HUMP_CHECK_NAME, + STAGE_TAG_CHECK_ERROR, + STAGE_COMPILE_MODE, + ATOMICSERVICE_BUNDLE_TYPE, + ATOMICSERVICE_TAG_CHECK_NAME, + ATOMICSERVICE_TAG_CHECK_ERROER, + ATOMICSERVICE_TAG_CHECK_VERSION, + RUNTIME_OS_OH, + CONSTANT_STEP_0, + CONSTANT_STEP_1, + CONSTANT_STEP_2, + CONSTANT_STEP_3, + GLOBAL_DECLARE_WHITE_LIST, + SINCE_TAG_NAME, + SINCE_TAG_CHECK_ERROER +} from './api_check_define'; +import { JsDocCheckService } from './api_check_permission'; + +/** + * bundle info + * + * @interface BundleInfo + */ +interface BundleInfo { + bundlePath: string; + bundleVersion: string; +} + +export interface CheckValidCallbackInterface { + (jsDocTag: ts.JSDocTag, config: ts.JsDocNodeCheckConfigItem): boolean; +} + +export interface CheckJsDocSpecialValidCallbackInterface { + (jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean; +} + +interface HasJSDocNode extends ts.Node { + jsDoc?: ts.JSDoc[]; +} + +/** + * get the bundleInfo of ohm + * + * @param {string} modulePath + * @return {BundleInfo} + */ +function parseOhmBundle(modulePath: string): BundleInfo { + const apiCode: string = fs.readFileSync(modulePath, { encoding: 'utf-8' }); + const bundleTags: string[] = apiCode.match(/@bundle.+/g); + const bundleInfo: BundleInfo = { + bundlePath: '', + bundleVersion: '' + }; + if (bundleTags && bundleTags.length > CONSTANT_STEP_0) { + const bundleTag: string = bundleTags[CONSTANT_STEP_0]; + const bundleInfos: string[] = bundleTag.split(' '); + if (bundleInfos.length === CONSTANT_STEP_3) { + bundleInfo.bundlePath = bundleInfos[CONSTANT_STEP_1]; + bundleInfo.bundleVersion = bundleInfos[CONSTANT_STEP_2]; + } + } + return bundleInfo; +} + +/** + * jude a version string , string has two format + * xx:is a number and need greater than 10 + * x.x.x: a string join '.', the first part and second part is number and need greater than 4.1 + * + * @param {string} bundleVersion - version string + * @returns {boolean} + */ +function checkBundleVersion(bundleVersion: string): boolean { + const compatibleSdkVersion: string = projectConfig.compatibleSdkVersion; + let bundleVersionNumber: number = 0; + const bundleVersionArr = bundleVersion.match(/(?<=\().*(?=\))/g); + if (bundleVersionArr && bundleVersionArr.length === 1) { + bundleVersionNumber = Number(bundleVersionArr[CONSTANT_STEP_0]); + } else { + bundleVersionNumber = Number(bundleVersion); + } + if (bundleVersion && bundleVersion !== '' && !isNaN(bundleVersionNumber) && + !isNaN(Number(compatibleSdkVersion)) && Number(compatibleSdkVersion) >= bundleVersionNumber) { + return true; + } + return false; +} + +/** + * get the real path about a list in module path + * + * @param {string[]} apiDirs - file list + * @param {string} moduleName - module dir + * @param {string[]} exts - ext + * @returns {ResolveModuleInfo} + */ +export function getRealModulePath(apiDirs: string[], moduleName: string, exts: string[]): ResolveModuleInfo { + const resolveResult: ResolveModuleInfo = { + modulePath: '', + isEts: true + }; + for (let i = 0; i < apiDirs.length; i++) { + const dir = apiDirs[i]; + for (let i = 0; i < exts.length; i++) { + const ext = exts[i]; + const moduleDir = path.resolve(dir, moduleName + ext); + if (!fs.existsSync(moduleDir)) { + continue; + } + resolveResult.modulePath = moduleDir; + if (ext === '.d.ts') { + resolveResult.isEts = false; + } + break; + } + } + return resolveResult; +} + +/** + * get a request path about ohos + * + * @param {string} moduleRequest - import request path + * @param {string} _ - import request path + * @param {number} moduleType + * @param {string} systemKey + * @returns {string} + */ +export function moduleRequestCallback(moduleRequest: string, _: string, + moduleType: string, systemKey: string): string { + for (const config of extendSdkConfigs.values()) { + if (config.prefix === '@arkui-x') { + continue; + } + if (moduleRequest.startsWith(config.prefix + '.')) { + let compileRequest: string = `${config.prefix}:${systemKey}`; + const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(config.apiPath, moduleRequest, + ['.d.ts', '.d.ets']); + const modulePath: string = resolveModuleInfo.modulePath; + if (!fs.existsSync(modulePath)) { + return compileRequest; + } + const bundleInfo: BundleInfo = parseOhmBundle(modulePath); + if (checkBundleVersion(bundleInfo.bundleVersion)) { + compileRequest = `@bundle:${bundleInfo.bundlePath}`; + } + return compileRequest; + } + } + return ''; +} + +/** + * check arkui dependences in ts files + * api check from sdk + * + * @param {ts.TypeReferenceNode} node - typeReferenceNode + * @param {IFileLog} transformLog - log info + */ +export function checkTypeReference(node: ts.TypeReferenceNode, transformLog: IFileLog): void { + const fileName: string = transformLog.sourceFile.fileName; + const currentTypeName: string = node.getText(); + if (/(? 0) { + sourceFile = ts.getSourceFileOfNode(type.aliasSymbol.declarations[0]); + } else if (type && type.symbol && type.symbol.declarations && type.symbol.declarations.length > 0) { + sourceFile = ts.getSourceFileOfNode(type.symbol.declarations[0]); + } + if (!sourceFile) { + return; + } + const sourceBaseName: string = path.basename(sourceFile.fileName); + if (isArkuiDependence(sourceFile.fileName) && + sourceBaseName !== 'common_ts_ets_api.d.ts' && + sourceBaseName !== 'global.d.ts' + ) { + // TODO: change to error + transformLog.errors.push({ + type: LogType.WARN, + message: `Cannot find name '${currentTypeName}'.`, + pos: node.getStart() + }); + } else if (GLOBAL_DECLARE_WHITE_LIST.has(currentTypeName) && + ohosSystemModulePaths.includes(sourceFile.fileName.replace(/\//g, '\\'))) { + transformLog.errors.push({ + type: LogType.WARN, + message: `Cannot find name '${currentTypeName}'.`, + pos: node.getStart() + }); + } + } +} + +/** + * get jsDocNodeCheckConfigItem object + * + * @param {string[]} tagName - tag name + * @param {string} message - error message + * @param {ts.DiagnosticCategory} type - error type + * @param {boolean} tagNameShouldExisted - tag is required + * @param {CheckValidCallbackInterface} [checkValidCallback] + * @param {CheckJsDocSpecialValidCallbackInterface} [checkJsDocSpecialValidCallback] + * @returns {ts.JsDocNodeCheckConfigItem} + */ +function getJsDocNodeCheckConfigItem(tagName: string[], message: string, needConditionCheck: boolean, + type: ts.DiagnosticCategory, specifyCheckConditionFuncName: string, + tagNameShouldExisted: boolean, checkValidCallback?: CheckValidCallbackInterface, + checkJsDocSpecialValidCallback?: CheckJsDocSpecialValidCallbackInterface): ts.JsDocNodeCheckConfigItem { + return { + tagName: tagName, + message: message, + needConditionCheck: needConditionCheck, + type: type, + specifyCheckConditionFuncName: specifyCheckConditionFuncName, + tagNameShouldExisted: tagNameShouldExisted, + checkValidCallback: checkValidCallback, + checkJsDocSpecialValidCallback: checkJsDocSpecialValidCallback + }; +} + +/** + * judge a file is card file + * + * @param {string} file - file path + * @returns {boolean} + */ +export function isCardFile(file: string): boolean { + for (const key in projectConfig.cardEntryObj) { + if (path.normalize(projectConfig.cardEntryObj[key]) === path.normalize(file)) { + return true; + } + } + return false; +} + +const jsDocNodeCheckConfigCache: Map> = new Map>(); +let permissionsArray: string[] = []; +/** + * get tagName where need to be determined based on the file path + * + * @param {string} fileName - file name + * @param {string} sourceFileName - resource reference path + * @returns {ts.JsDocNodeCheckConfig} + */ +export function getJsDocNodeCheckConfig(fileName: string, sourceFileName: string): ts.JsDocNodeCheckConfig { + let byFileName: Map | undefined = jsDocNodeCheckConfigCache.get(fileName); + if (byFileName === undefined) { + byFileName = new Map(); + jsDocNodeCheckConfigCache.set(fileName, byFileName); + } + let result: ts.JsDocNodeCheckConfig | undefined = byFileName.get(sourceFileName); + if (result !== undefined) { + return result; + } + let needCheckResult: boolean = false; + const checkConfigArray: ts.JsDocNodeCheckConfigItem[] = []; + const apiName: string = path.basename(fileName); + const sourceBaseName: string = path.basename(sourceFileName); + if (/(?= ATOMICSERVICE_TAG_CHECK_VERSION) { + needCheckResult = true; + checkConfigArray.push(getJsDocNodeCheckConfigItem([ATOMICSERVICE_TAG_CHECK_NAME], ATOMICSERVICE_TAG_CHECK_ERROER, + false, ts.DiagnosticCategory.Error, '', true)); + } + } + result = { + nodeNeedCheck: needCheckResult, + checkConfig: checkConfigArray + }; + byFileName.set(sourceFileName, result); + return result; +} + +const arkuiDependenceMap: Map = new Map(); +/** + * return a file path is Arkui path + * + * @param {string} file - file path + * @returns {boolean} + */ +function isArkuiDependence(file: string): boolean { + let exists: boolean | undefined = arkuiDependenceMap.get(file); + if (exists !== undefined) { + return exists; + } + const fileDir: string = path.dirname(file); + const declarationsPath: string = path.resolve(__dirname, '../../../../../declarations').replace(/\\/g, '/'); + const componentPath: string = path.resolve(__dirname, '../../../../../../../component').replace(/\\/g, '/'); + exists = fileDir === declarationsPath || fileDir === componentPath; + arkuiDependenceMap.set(file, exists); + return exists; +} + +/** + * check a secondary directory of Arkui is used in the moduleSpecifier of import + * + * @param {ts.Expression} moduleSpecifier - the moduleSpecifier of import + * @param {LogInfo[]} log - log list + */ +export function validateModuleSpecifier(moduleSpecifier: ts.Expression, log: LogInfo[]): void { + const moduleSpecifierStr: string = moduleSpecifier.getText().replace(/'|"/g, ''); + const hasSubDirPath: boolean = ohosSystemModuleSubDirPaths.some((filePath: string) => { + return filePath === moduleSpecifierStr; + }); + if (hasSubDirPath) { + // TODO: change to error + const error: LogInfo = { + type: LogType.WARN, + message: `Cannot find module '${moduleSpecifierStr}' or its corresponding type declarations.`, + pos: moduleSpecifier.getStart() + }; + log.push(error); + } +} + +interface SystemConfig { + deviceTypesMessage: string, + deviceTypes: string[], + runtimeOS: string, + externalApiPaths: string[], + syscapIntersectionSet: Set, + syscapUnionSet: Set +} + +interface SyscapConfig { + SysCaps: string[] +} + +/** + * configure syscapInfo to this.share.projectConfig + * + * @param config this.share.projectConfig + */ +export function configureSyscapInfo(config: SystemConfig): void { + config.deviceTypesMessage = config.deviceTypes.join(','); + const deviceDir: string = path.resolve(__dirname, '../../../../../api/device-define/'); //??? + const deviceInfoMap: Map = new Map(); + const syscaps: Array = []; + let allSyscaps: string[] = []; + config.deviceTypes.forEach((deviceType: string) => { + collectOhSyscapInfos(deviceType, deviceDir, deviceInfoMap); + }); + if (config.runtimeOS !== RUNTIME_OS_OH) { + collectExternalSyscapInfos(config.externalApiPaths, config.deviceTypes, deviceInfoMap); + } + deviceInfoMap.forEach((value: string[]) => { + syscaps.push(value); + allSyscaps = allSyscaps.concat(value); + }); + const intersectNoRepeatTwice = (arrs: Array) => { + return arrs.reduce(function (prev: string[], cur: string[]) { + return Array.from(new Set(cur.filter((item: string) => { + return prev.includes(item); + }))); + }); + }; + let syscapIntersection: string[] = []; + if (config.deviceTypes.length === 1 || syscaps.length === 1) { + syscapIntersection = syscaps[0]; + } else if (syscaps.length > 1) { + syscapIntersection = intersectNoRepeatTwice(syscaps); + } + config.syscapIntersectionSet = new Set(syscapIntersection); + config.syscapUnionSet = new Set(allSyscaps); +} + +function collectOhSyscapInfos(deviceType: string, deviceDir: string, deviceInfoMap: Map) { + let syscapFilePath: string = ''; + if (deviceType === 'phone') { + syscapFilePath = path.resolve(deviceDir, 'default.json'); + } else { + syscapFilePath = path.resolve(deviceDir, deviceType + '.json'); + } + if (fs.existsSync(syscapFilePath)) { + const content: SyscapConfig = JSON.parse(fs.readFileSync(syscapFilePath, 'utf-8')); + if (deviceInfoMap.get(deviceType)) { + deviceInfoMap.set(deviceType, deviceInfoMap.get(deviceType).concat(content.SysCaps)); + } else { + deviceInfoMap.set(deviceType, content.SysCaps); + } + } +} + +function collectExternalSyscapInfos( + externalApiPaths: string[], + deviceTypes: string[], + deviceInfoMap: Map +) { + const externalDeviceDirs: string[] = []; + externalApiPaths.forEach((externalApiPath: string) => { + const externalDeviceDir: string = path.resolve(externalApiPath, './api/device-define'); + if (fs.existsSync(externalDeviceDir)) { + externalDeviceDirs.push(externalDeviceDir); + } + }); + externalDeviceDirs.forEach((externalDeviceDir: string) => { + deviceTypes.forEach((deviceType: string) => { + let syscapFilePath: string = ''; + const files: string[] = fs.readdirSync(externalDeviceDir); + files.forEach((fileName: string) => { + if (fileName.startsWith(deviceType)) { + syscapFilePath = path.resolve(externalDeviceDir, fileName); + if (fs.existsSync(syscapFilePath)) { + const content: SyscapConfig = JSON.parse(fs.readFileSync(syscapFilePath, 'utf-8')); + if (deviceInfoMap.get(deviceType)) { + deviceInfoMap.set(deviceType, deviceInfoMap.get(deviceType).concat(content.SysCaps)); + } else { + deviceInfoMap.set(deviceType, content.SysCaps); + } + } + } + }); + }); + }); +} + +/** + * Determine the necessity of since check. + * + * @param jsDocTags + * @param config + * @returns + */ +function checkSinceValue(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { + if (!jsDocTags[0]?.parent?.parent) { + return false; + } + const currentNode: HasJSDocNode = jsDocTags[0].parent.parent as HasJSDocNode; + if (!currentNode.jsDoc) { + return false; + } + let minSince: string = ''; + const hasSince: boolean = currentNode.jsDoc.some((doc: ts.JSDoc) => { + return doc.tags?.some((tag: ts.JSDocTag) => { + if (tag.tagName.escapedText.toString() === SINCE_TAG_NAME) { + minSince = tag.comment.toString(); + return true; + } + return false; + }); + }); + const compatibleSdkVersion: string = projectConfig.compatibleSdkVersion; + if (hasSince && Number(minSince) > Number(compatibleSdkVersion)) { + config.message = SINCE_TAG_CHECK_ERROER.replace('$SINCE1', minSince).replace('$SINCE2', compatibleSdkVersion); + return true; + } + return false; +} +/** + * Determine the necessity of syscap check. + * @param jsDocTags + * @param config + * @returns + */ +export function checkSyscapAbility(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { + let currentSyscapValue: string = ''; + for (let i = 0; i < jsDocTags.length; i++) { + const jsDocTag: ts.JSDocTag = jsDocTags[i]; + if (jsDocTag && jsDocTag.tagName.escapedText.toString() === SYSCAP_TAG_CHECK_NAME) { + currentSyscapValue = jsDocTag.comment as string; + break; + } + } + return projectConfig.syscapIntersectionSet && !projectConfig.syscapIntersectionSet.has(currentSyscapValue); +} + +interface ConfigPermission { + requestPermissions: Array<{ name: string }>; + definePermissions: Array<{ name: string }>; +} + +interface PermissionsConfig { + permission: ConfigPermission, + requestPermissions: string[], + definePermissions: string[], +} +/** + * configure permissionInfo to this.share.projectConfig + * + * @param config this.share.projectConfig + */ +export function configurePermission(config: PermissionsConfig): void { + const permission: ConfigPermission = config.permission; + config.requestPermissions = []; + config.definePermissions = []; + if (permission.requestPermissions) { + config.requestPermissions = getNameFromArray(permission.requestPermissions); + } + if (permission.definePermissions) { + config.definePermissions = getNameFromArray(permission.definePermissions); + } +} + +function getNameFromArray(array: Array<{ name: string }>): string[] { + return array.map((item: { name: string }) => { + return String(item.name); + }); +} + +/** + * Determine the necessity of permission check + * + * @param {ts.JSDocTag[]} jsDocTags + * @param {ts.JsDocNodeCheckConfigItem} config + * @returns {boolean} + */ +export function checkPermissionValue(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { + const jsDocTag: ts.JSDocTag = jsDocTags.find((item: ts.JSDocTag) => { + return item.tagName.getText() === PERMISSION_TAG_CHECK_NAME; + }); + if (!jsDocTag) { + return false; + } + const comment: string = typeof jsDocTag.comment === 'string' ? + jsDocTag.comment : + ts.getTextOfJSDocComment(jsDocTag.comment); + config.message = PERMISSION_TAG_CHECK_ERROR.replace('$DT', comment); + return comment !== '' && !JsDocCheckService.validPermission(comment, permissionsArray); +} + +/** + * custom condition check + * @param { ts.FileCheckModuleInfo } jsDocFileCheckedInfo + * @param { ts.JsDocTagInfo[] } jsDocs + * @returns + */ +export function getJsDocNodeConditionCheckResult(jsDocFileCheckedInfo: ts.FileCheckModuleInfo, jsDocs: ts.JsDocTagInfo[]): + ts.ConditionCheckResult { + const result: ts.ConditionCheckResult = { + valid: true + }; + let currentSyscapValue: string = ''; + for (let i = 0; i < jsDocs.length; i++) { + const jsDocTag: ts.JsDocTagInfo = jsDocs[i]; + if (jsDocTag.name === SYSCAP_TAG_CHECK_NAME) { + currentSyscapValue = jsDocTag.text as string; + break; + } + } + if (!projectConfig.syscapIntersectionSet || !projectConfig.syscapUnionSet) { + return result; + } + if (!projectConfig.syscapIntersectionSet.has(currentSyscapValue) && projectConfig.syscapUnionSet.has(currentSyscapValue)) { + result.valid = false; + result.type = ts.DiagnosticCategory.Warning; + result.message = SYSCAP_TAG_CONDITION_CHECK_WARNING; + } else if (!projectConfig.syscapUnionSet.has(currentSyscapValue)) { + result.valid = false; + // TODO: fix to error in the feature + result.type = ts.DiagnosticCategory.Warning; + result.message = SYSCAP_TAG_CHECK_WARNING.replace('$DT', projectConfig.deviceTypesMessage); + } + return result; +} diff --git a/compiler/src/interop/src/fast_build/system_api/rollup-plugin-system-api.ts b/compiler/src/interop/src/fast_build/system_api/rollup-plugin-system-api.ts new file mode 100644 index 0000000000000000000000000000000000000000..41f03c9cb0490d03d5ccedc5f1a1231d37f5a0f9 --- /dev/null +++ b/compiler/src/interop/src/fast_build/system_api/rollup-plugin-system-api.ts @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2023 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 MagicString, { SourceMap } from 'magic-string'; +import { createFilter } from '@rollup/pluginutils'; +import path from 'path'; +import { + NATIVE_MODULE, + ARKUI_X_PLUGIN, + GLOBAL_THIS_REQUIRE_NATIVE_MODULE, + GLOBAL_THIS_REQUIRE_NAPI +} from '../../pre_define'; +import { + systemModules, + projectConfig, + sdkConfigs, + sdkConfigPrefix, + extendSdkConfigs +} from '../../../main'; + +import { + writeUseOSFiles, + writeCollectionFile, + getAllComponentsOrModules +} from '../../utils'; +import { appComponentCollection } from '../../ets_checker'; +import { hasTsNoCheckOrTsIgnoreFiles } from '../ark_compiler/utils'; +import { shouldEmitJsFlagById } from '../ets_ui/rollup-plugin-ets-typescript'; + +const filterCrossplatform: (id: string) => boolean = createFilter(/(? boolean = createFilter(/(? = new Set(); + +export const appImportModuleCollection: Map> = new Map(); +export const kitModules: Map>> = new Map(); + +export function apiTransform() { + const useOSFiles: Set = new Set(); + let needModuleCollection: boolean = false; + let needComponentCollection: boolean = false; + return { + name: 'apiTransform', + load(id: string): void { + allFiles.add(path.join(id)); + }, + buildStart(): void { + if (this.share.projectConfig.isCrossplatform) { + needModuleCollection = true; + needComponentCollection = true; + } else if (this.share.projectConfig.widgetCompile) { + needModuleCollection = false; + needComponentCollection = true; + } + }, + transform(code: string, id: string): { + code: string; + map: SourceMap; + } { + let hiresStatus: boolean = this.share.projectConfig.needCompleteSourcesMap; + const shouldEmitJsFlag: boolean = id.endsWith('.js') || + shouldEmitJsFlagById(id) || projectConfig.compileMode !== 'esmodule'; + if (!shouldEmitJsFlag && + !this.share.projectConfig.isCrossplatform && + !this.share.projectConfig.widgetCompile) { + return null; + } + + if (projectConfig.isCrossplatform ? filterCrossplatform(id) : filter(id)) { + if (projectConfig.compileMode === 'esmodule') { + code = processSystemApiAndLibso(code, id, useOSFiles); + hiresStatus = hiresStatus || hasTsNoCheckOrTsIgnoreFiles.includes(id) ? + true : + false; + } else { + code = processSystemApi(code, id); + code = processLibso(code, id, useOSFiles); + hiresStatus = true; + } + } + if (!shouldEmitJsFlag) { + return null; + } + const magicString: MagicString = new MagicString(code); + return { + code: code, + map: magicString.generateMap({ hires: hiresStatus }) + }; + }, + beforeBuildEnd(): void { + this.share.allFiles = allFiles; + if (process.env.watchMode !== 'true' && !projectConfig.xtsMode && needComponentCollection) { + let widgetPath: string; + if (projectConfig.widgetCompile) { + widgetPath = path.resolve(projectConfig.aceModuleBuild, 'widget'); + } + this.share.allComponents = getAllComponentsOrModules(allFiles, 'component_collection.json'); + writeCollectionFile(projectConfig.cachePath, appComponentCollection, + this.share.allComponents, 'component_collection.json', this.share.allFiles, widgetPath); + } + }, + buildEnd(): void { + if (projectConfig.isPreview && projectConfig.aceSoPath && + useOSFiles && useOSFiles.size > 0) { + writeUseOSFiles(useOSFiles); + } + if (process.env.watchMode !== 'true' && !projectConfig.xtsMode && needModuleCollection) { + replaceKitModules(); + const allModules: Map> = getAllComponentsOrModules(allFiles, 'module_collection.json'); + writeCollectionFile(projectConfig.cachePath, appImportModuleCollection, allModules, 'module_collection.json'); + } + }, + cleanUp(): void { + allFiles.clear(); + appImportModuleCollection.clear(); + useOSFiles.clear(); + kitModules.clear(); + } + }; +} + + +function processSystemApi(content: string, sourcePath: string): string { + // 'arkui-x' represents cross platform related APIs, processed as 'ohos' + const REG_SYSTEM: RegExp = + new RegExp(`import\\s+(.+)\\s+from\\s+['"]@(${sdkConfigPrefix})\\.(\\S+)['"]|import\\s+(.+)\\s*=\\s*require\\(\\s*['"]@(${sdkConfigPrefix})\\.(\\S+)['"]\\s*\\)`, 'g'); + appImportModuleCollection.set(path.join(sourcePath), new Set()); + return content.replace(REG_SYSTEM, (item, item1, item2, item3, item4, item5, item6) => { + const moduleType: string = item2 || item5; + const systemKey: string = item3 || item6; + const systemValue: string = item1 || item4; + const systemModule: string = `${moduleType}.${systemKey}`; + appImportModuleCollection.get(path.join(sourcePath)).add(systemModule); + checkModuleExist(systemModule, sourcePath); + const externalModuleParam: string = isExtendModuleType(moduleType) && + moduleType !== ARKUI_X_PLUGIN ? `, false, '', '${moduleType}'` : ``; + if (NATIVE_MODULE.has(systemModule)) { + item = `var ${systemValue} = ${GLOBAL_THIS_REQUIRE_NATIVE_MODULE}('${moduleType}.${systemKey}')`; + } else if (checkModuleType(moduleType)) { + item = `var ${systemValue} = ${GLOBAL_THIS_REQUIRE_NAPI}('${systemKey}'${externalModuleParam})`; + } + return item; + }); +} + +interface SdkConfig { + apiPath: string; + prefix: string; +} + +function isExtendModuleType(moduleType: string): boolean { + for (let i = 0; i < extendSdkConfigs.length; i++) { + const config: SdkConfig = extendSdkConfigs[i]; + if (config.prefix === `@${moduleType}`) { + return true; + } + } + return false; +} + +function checkModuleType(moduleType: string): boolean { + for (let i = 0; i < sdkConfigs.length; i++) { + const config = sdkConfigs[i]; + if (config.prefix === `@${moduleType}`) { + return true; + } + } + return false; +} + +function checkModuleExist(systemModule: string, sourcePath: string): void { + const module: string = `@${systemModule.trim()}.d.ts`; + if (/\.js$/.test(sourcePath) && !systemModules.includes(module)) { + const message: string = + `Cannot find module '${module}' or its corresponding type declarations.`; + console.error(`BUILDERROR File: ${sourcePath}\n ${message}`); + } +} + +function processLibso(content: string, sourcePath: string, useOSFiles: Set): string { + const REG_LIB_SO: RegExp = + /import\s+(.+)\s+from\s+['"]lib(\S+)\.so['"]|import\s+(.+)\s*=\s*require\(\s*['"]lib(\S+)\.so['"]\s*\)/g; + return content.replace(REG_LIB_SO, (_, item1, item2, item3, item4) => { + useOSFiles.add(sourcePath); + const libSoValue: string = item1 || item3; + const libSoKey: string = item2 || item4; + return projectConfig.bundleName && projectConfig.moduleName + ? `var ${libSoValue} = globalThis.requireNapi("${libSoKey}", true, "${projectConfig.bundleName}/${projectConfig.moduleName}");` + : `var ${libSoValue} = globalThis.requireNapi("${libSoKey}", true);`; + }); +} + +// It is rare to use `import xxx = require('module')` for system module and user native library, +// Here keep tackling with this for compatibility concern. +function processSystemApiAndLibso(content: string, sourcePath: string, useOSFiles: Set): string { + // 'arkui-x' represents cross platform related APIs, processed as 'ohos' + const REG_REQUIRE_SYSTEM: RegExp = new RegExp(`import\\s+(.+)\\s*=\\s*require\\(\\s*['"]@(${sdkConfigPrefix})\\.(\\S+)['"]\\s*\\)`, 'g'); + // Import libso should be recored in useOSFiles. + const REG_LIB_SO: RegExp = + /import\s+(.+)\s+from\s+['"]lib(\S+)\.so['"]|import\s+(.+)\s*=\s*require\(\s*['"]lib(\S+)\.so['"]\s*\)/g; + // 'arkui-x' represents cross platform related APIs, processed as 'ohos' + const REG_IMPORT_SYSTEM = new RegExp(`import\\s+(.+)\\s+from\\s+['"]@(${sdkConfigPrefix})\\.(\\S+)['"]`, 'g'); + appImportModuleCollection.set(path.join(sourcePath), new Set()); + content.replace(REG_IMPORT_SYSTEM, (_, item1, item2, item3, item4, item5, item6) => { + const moduleType: string = item2 || item5; + const systemKey: string = item3 || item6; + const systemValue: string = item1 || item4; + const systemModule: string = `${moduleType}.${systemKey}`; + appImportModuleCollection.get(path.join(sourcePath)).add(systemModule); + return _; + }); + return content.replace(REG_REQUIRE_SYSTEM, (_, item1, item2, item3, item4, item5, item6) => { + const moduleType: string = item2 || item5; + const systemKey: string = item3 || item6; + const systemValue: string = item1 || item4; + const systemModule: string = `${moduleType}.${systemKey}`; + appImportModuleCollection.get(path.join(sourcePath)).add(systemModule); + checkModuleExist(systemModule, sourcePath); + return `import ${systemValue} from '@${moduleType}.${systemKey}'`; + }).replace(REG_LIB_SO, (_, item1, item2, item3, item4) => { + useOSFiles.add(sourcePath); + const libSoValue: string = item1 || item3; + const libSoKey: string = item2 || item4; + return `import ${libSoValue} from 'lib${libSoKey}.so'`; + }); +} + +export function collectKitModules(fileName: string, key: string, value: string): void { + key = key.replace('@', ''); + value = value.replace('@', ''); + const currentValue: Map> = kitModules.get(fileName); + if (currentValue) { + const currentModuleName: Set = currentValue.get(key); + if (currentModuleName && !currentModuleName.has(value)) { + currentModuleName.add(value); + currentValue.set(key, currentModuleName); + } else if (!currentModuleName) { + const moduleSet: Set = new Set(); + currentValue.set(key, moduleSet.add(value)); + } + } else { + const moduleMap: Map> = new Map(); + const moduleSet: Set = new Set(); + moduleMap.set(key, moduleSet.add(value)); + kitModules.set(fileName, moduleMap); + } +} + +function compareKitModules(value: Set, kitKey: string, kitValue: Map>): void { + const realModules: Set = new Set(); + value.forEach((element: string) => { + if (kitValue.get(element)) { + kitValue.get(element).forEach((kitModuleRequest: string) => { + realModules.add(kitModuleRequest.replace(/\.d\.e?ts$/, '')); + }); + } else { + realModules.add(element); + } + }); + appImportModuleCollection.set(kitKey, realModules); +} + +function replaceKitModules(): void { + appImportModuleCollection.forEach((value: Set, key: string) => { + kitModules.forEach((kitValue: Map>, kitKey: string) => { + if (key === kitKey && value && value.size > 0) { + compareKitModules(value, kitKey, kitValue); + } + }); + }); +} diff --git a/compiler/src/interop/src/fast_build/visual/rollup-plugin-visual.ts b/compiler/src/interop/src/fast_build/visual/rollup-plugin-visual.ts new file mode 100644 index 0000000000000000000000000000000000000000..22bf031fe0b667462c16e029cb8bddcd1cd20929 --- /dev/null +++ b/compiler/src/interop/src/fast_build/visual/rollup-plugin-visual.ts @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 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 fs from 'fs'; +import { createFilter } from '@rollup/pluginutils'; +import { + findVisualFile, + visualTransform as processVisual +} from '../../process_visual'; +import MagicString from 'magic-string'; +import { PluginContext } from 'rollup'; +import { projectConfig } from '../../../main'; + +const filter: any = createFilter(/(? { + const inputPaths: any = JSON.parse(jsonInput); + for (let i = 0; i < inputPaths.length; ++i) { + const input: string = inputPaths[i].path.replace(/\.temp\.js$/, '_.js'); + const cacheOutputPath: string = inputPaths[i].cacheOutputPath; + const cacheAbcFilePath: string = cacheOutputPath.replace(/\.temp\.js$/, '.abc'); + const sourceFile: string = inputPaths[i].sourceFile; + const singleCmd: string = `${cmd} "${cacheOutputPath}" -o "${cacheAbcFilePath}" --source-file "${sourceFile}"`; + logger.debug('gen abc cmd is: ', singleCmd, ' ,file size is:', inputPaths[i].size, ' byte'); + try { + childProcess.execSync(singleCmd); + } catch (e) { + logger.debug(red, `ArkTS:ERROR Failed to convert file ${input} to abc. Error message: ${e} `, reset); + process.exit(FAIL); + } + } + + return; +} + +logger.debug('worker data is: ', JSON.stringify(process.env)); +logger.debug('gen_abc isWorker is: ', cluster.isWorker); +if (cluster.isWorker && process.env.inputs !== undefined && process.env.cmd !== undefined) { + logger.debug('==>worker #', cluster.worker.id, 'started!'); + js2abcByWorkers(process.env.inputs, process.env.cmd); + process.exit(SUCCESS); +} diff --git a/compiler/src/interop/src/gen_abc_plugin.ts b/compiler/src/interop/src/gen_abc_plugin.ts new file mode 100644 index 0000000000000000000000000000000000000000..0982e15f97321c128904044dd181f20be09b6829 --- /dev/null +++ b/compiler/src/interop/src/gen_abc_plugin.ts @@ -0,0 +1,1365 @@ +/* + * Copyright (c) 2021 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 cluster from 'cluster'; +import process from 'process'; +import os from 'os'; +import events from 'events'; +import Compiler from 'webpack/lib/Compiler'; +import { logger } from './compile_info'; +import * as childProcess from 'child_process'; +import { + toUnixPath, + toHashData, + mkdirsSync, + nodeLargeOrEqualTargetVersion, + removeDir, + validateFilePathLength, + unlinkSync, + isPackageModulesFile, + genTemporaryPath +} from './utils'; +import { + buildCachePath, + genAbcFileName, + genBuildPath, + genMergeProtoFileName, + genProtoFileName, + getOhmUrlByFilepath, + getPackageInfo, + isEs2Abc, + isTs2Abc, + newSourceMaps, + removeDuplicateInfo +} from './ark_utils'; +import { projectConfig } from '../main'; +import { + ESMODULE, + JSBUNDLE, + NODE_MODULES, + ES2ABC, + EXTNAME_D_ETS, + EXTNAME_D_TS, + EXTNAME_ETS, + EXTNAME_JS, + EXTNAME_TS, + EXTNAME_MJS, + EXTNAME_CJS, + EXTNAME_JSON, + EXTNAME_JS_MAP, + FAIL, + MODULELIST_JSON, + MODULES_ABC, + PREBUILDINFO_JSON, + SUCCESS, + SOURCEMAPS_JSON, + SOURCEMAPS, + TEMPORARY, + TS2ABC, + PROTO_FILESINFO_TXT, + NPMENTRIES_TXT, + EXTNAME_PROTO_BIN, + FILESINFO_TXT, + MANAGE_WORKERS_SCRIPT, + MAX_WORKER_NUMBER, + GEN_ABC_SCRIPT, + GEN_MODULE_ABC_SCRIPT, + AOT_FULL, + AOT_PARTIAL, + PACKAGES +} from './pre_define'; +import { + generateMergedAbc, + generateNpmEntriesInfo +} from './gen_merged_abc'; +import { + generateAot, + FaultHandler +} from './gen_aot'; + +let output: string; +let isWin: boolean = false; +let isMac: boolean = false; +let isDebug: boolean = false; +let arkDir: string; +let nodeJs: string; + +interface File { + path: string, + size: number, + cacheOutputPath: string, + sourceFile: string +} +let intermediateJsBundle: Array = []; +let fileterIntermediateJsBundle: Array = []; +let moduleInfos: Array = []; +let filterModuleInfos: Array = []; +let commonJsModuleInfos: Array = []; +let ESMModuleInfos: Array = []; +let entryInfos: Map = new Map(); +let hashJsonObject = {}; +let moduleHashJsonObject = {}; +let buildPathInfo: string = ''; +let buildMapFileList: Set = new Set(); +let isHotReloadFirstBuild: boolean = true; +let protoFilePath: string = ''; + +const red: string = '\u001b[31m'; +const reset: string = '\u001b[39m'; +const blue = '\u001b[34m'; +const hashFile: string = 'gen_hash.json'; +const ARK: string = '/ark/'; + +export class ModuleInfo { + filePath: string; + tempFilePath: string; + buildFilePath: string; + abcFilePath: string; + isCommonJs: boolean; + recordName: string; + sourceFile: string; + packageName: string; + + constructor(filePath: string, tempFilePath: string, buildFilePath: string, + abcFilePath: string, packageName: string, isCommonJs: boolean) { + this.filePath = filePath; + this.tempFilePath = tempFilePath; + this.buildFilePath = buildFilePath; + this.abcFilePath = abcFilePath; + this.packageName = packageName; + this.isCommonJs = isCommonJs; + this.recordName = getOhmUrlByFilepath(filePath, projectConfig, logger); + this.sourceFile = filePath.replace(projectConfig.projectRootPath + path.sep, ''); + } +} + +export class EntryInfo { + npmInfo: string; + buildPath: string; + entry: string; + + constructor(npmInfo: string, buildPath: string, entry: string) { + this.npmInfo = npmInfo; + this.buildPath = buildPath; + this.entry = entry; + } +} + +export class GenAbcPlugin { + constructor(output_, arkDir_, nodeJs_, isDebug_) { + output = output_; + arkDir = arkDir_; + nodeJs = nodeJs_; + isDebug = isDebug_; + } + apply(compiler: Compiler) { + if (fs.existsSync(path.resolve(arkDir, 'build-win'))) { + isWin = true; + } else { + if (fs.existsSync(path.resolve(arkDir, 'build-mac'))) { + isMac = true; + } else { + if (!fs.existsSync(path.resolve(arkDir, 'build'))) { + logger.error(red, 'ArkTS:ERROR find build fail', reset); + process.exitCode = FAIL; + return; + } + } + } + + if (!checkNodeModules()) { + process.exitCode = FAIL; + return; + } + + if (projectConfig.compileMode === ESMODULE) { + if (projectConfig.cachePath && !projectConfig.xtsMode) { + let cachedJson: Object = {}; + const cachePrebuildInfoPath: string = path.join(projectConfig.cachePath, PREBUILDINFO_JSON); + validateFilePathLength(cachePrebuildInfoPath, logger); + cachedJson.buildMode = projectConfig.buildArkMode; + cachedJson.bundleName = projectConfig.bundleName; + cachedJson.moduleName = projectConfig.moduleName; + fs.writeFile(cachePrebuildInfoPath, JSON.stringify(cachedJson, null, 2), 'utf-8', + (err) => { + if (err) { + logger.error(red, `ArkTS:ERROR Failed to write module build info.`, reset); + } + } + ); + } + + // clear output dir + removeDir(output); + removeDir(projectConfig.nodeModulesPath); + } + + if (projectConfig.compileMode === JSBUNDLE && process.env.minPlatformVersion) { + if (projectConfig.cachePath && !projectConfig.xtsMode) { + let cachedJson: Object = {}; + const cachePrebuildInfoPath: string = path.join(projectConfig.cachePath, PREBUILDINFO_JSON); + validateFilePathLength(cachePrebuildInfoPath, logger); + cachedJson.minAPIVersion = process.env.minPlatformVersion; + fs.writeFile(cachePrebuildInfoPath, JSON.stringify(cachedJson, null, 2), 'utf-8', + (err) => { + if (err) { + logger.error(red, `ArkTS:ERROR Failed to write bundle build info.`, reset); + } + } + ); + } + } + + // for preview mode max listeners + events.EventEmitter.defaultMaxListeners = 100; + + compiler.hooks.compilation.tap('GenAbcPlugin', (compilation) => { + if (projectConfig.compileMode === JSBUNDLE || projectConfig.compileMode === undefined) { + return; + } + buildPathInfo = output; + compilation.hooks.finishModules.tap('finishModules', handleFinishModules.bind(this)); + }); + + compiler.hooks.compilation.tap('GenAbcPlugin', (compilation) => { + compilation.hooks.processAssets.tap('processAssets', (assets) => { + if (projectConfig.compileMode === JSBUNDLE || projectConfig.compileMode === undefined) { + return; + } + let newAssets: Object = {}; + Object.keys(compilation.assets).forEach(key => { + if (path.extname(key) !== EXTNAME_JS && path.extname(key) !== EXTNAME_JS_MAP) { + newAssets[key] = assets[key]; + } + }); + compilation.assets = newAssets; + }); + }); + + compiler.hooks.emit.tap('GenAbcPlugin', (compilation) => { + if (projectConfig.compileMode === ESMODULE) { + return; + } + Object.keys(compilation.assets).forEach(key => { + // choose *.js + if (output && path.extname(key) === EXTNAME_JS) { + const newContent: string = compilation.assets[key].source(); + const keyPath: string = key.replace(/\.js$/, '.temp.js'); + writeFileSync(newContent, output, keyPath, key); + } + }); + }); + + compiler.hooks.afterEmit.tap('GenAbcPluginMultiThread', () => { + if (projectConfig.compileMode === ESMODULE) { + return; + } + if (intermediateJsBundle.length === 0) { + return; + } + buildPathInfo = output; + if (isTs2Abc(projectConfig) || process.env.minPlatformVersion === '8') { + invokeWorkersToGenAbc(); + } else if (isEs2Abc(projectConfig)) { + generateAbcByEs2AbcOfBundleMode(intermediateJsBundle); + } else { + logger.error(red, `ArkTS:ERROR please set panda module`, reset); + } + }); + } +} + +function clearGlobalInfo() { + // fix bug of multi trigger + if (process.env.watchMode !== 'true') { + intermediateJsBundle = []; + moduleInfos = []; + entryInfos = new Map(); + } + fileterIntermediateJsBundle = []; + filterModuleInfos = []; + commonJsModuleInfos = []; + ESMModuleInfos = []; + hashJsonObject = {}; + moduleHashJsonObject = {}; + buildMapFileList = new Set(); +} + +function getEntryInfo(filePath: string, resourceResolveData: Object): string { + if (!resourceResolveData.descriptionFilePath) { + return ''; + } + + let isEntry: boolean = false; + let mainFileds: Set = getEntryCandidatesFromPackageJson(resourceResolveData); + for (let value of mainFileds.values()) { + if (toUnixPath(filePath) === value) { + isEntry = true; + break; + } + } + const packageJsonPath: string = resourceResolveData.descriptionFilePath; + let npmInfoPath: string = path.resolve(packageJsonPath, '..'); //??? + + let entry: string = toUnixPath(filePath.replace(npmInfoPath, '')); + if (entry.startsWith('/')) { + entry = entry.slice(1, entry.length); + } + + const fakeEntryPath: string = path.resolve(npmInfoPath, 'fake.js'); //??? + const tempFakeEntryPath: string = genTemporaryPath(fakeEntryPath, projectConfig.projectPath, process.env.cachePath, + projectConfig, undefined); + const buildFakeEntryPath: string = genBuildPath(fakeEntryPath, projectConfig.projectPath, projectConfig.buildPath, + projectConfig); + npmInfoPath = toUnixPath(path.resolve(tempFakeEntryPath, '..')); + const buildNpmInfoPath: string = toUnixPath(path.resolve(buildFakeEntryPath, '..')); + if (!entryInfos.has(npmInfoPath) && isEntry) { + const entryInfo: EntryInfo = new EntryInfo(npmInfoPath, buildNpmInfoPath, entry); + entryInfos.set(npmInfoPath, entryInfo); + } + + return buildNpmInfoPath; +} + +function getEntryCandidatesFromPackageJson(resourceResolveData: Object): Set { + let descriptionFileData: Object = resourceResolveData.descriptionFileData; + let packagePath: string = path.resolve(resourceResolveData.descriptionFilePath, '..'); + let mainFileds: Set = new Set(); + if (descriptionFileData.browser) { + if (typeof descriptionFileData.browser === 'string') { + mainFileds.add(toUnixPath(path.join(packagePath, descriptionFileData.browser))); + } else { + Object.keys(descriptionFileData.browser).forEach(key => { + if (typeof key === 'string' && typeof descriptionFileData.browser[key] === 'string') { + mainFileds.add(toUnixPath(path.join(packagePath, descriptionFileData.browser[key]))); + } + }); + } + } + if (descriptionFileData.module) { + mainFileds.add(toUnixPath(path.join(packagePath, descriptionFileData.module))); + } + if (descriptionFileData.main) { + mainFileds.add(toUnixPath(path.join(packagePath, descriptionFileData.main))); + } + if (mainFileds.size === 0) { + mainFileds.add(toUnixPath(path.join(packagePath, 'index.js'))); + mainFileds.add(toUnixPath(path.join(packagePath, 'index.ets'))); + mainFileds.add(toUnixPath(path.join(packagePath, 'index.ts'))); + } + + return mainFileds; +} + +function processNodeModulesFile(filePath: string, tempFilePath: string, buildFilePath: string, + abcFilePath: string, nodeModulesFile: Array, module: Object): void { + let npmPkgPath: string = getEntryInfo(filePath, module.resourceResolveData); + const buildNpmPkgPath: string = npmPkgPath.replace(toUnixPath(projectConfig.nodeModulesPath), ''); + const npmPkgName: string = toUnixPath(path.join(PACKAGES, buildNpmPkgPath)).replace(new RegExp(NODE_MODULES, 'g'), PACKAGES); + + const descriptionFileData: Object = module.resourceResolveData.descriptionFileData; + if (descriptionFileData && descriptionFileData.type && descriptionFileData.type === 'module') { + const tempModuleInfo: ModuleInfo = new ModuleInfo(filePath, tempFilePath, buildFilePath, abcFilePath, npmPkgName, false); + moduleInfos.push(tempModuleInfo); + nodeModulesFile.push(tempFilePath); + } else if (filePath.endsWith(EXTNAME_MJS)) { + const tempModuleInfo: ModuleInfo = new ModuleInfo(filePath, tempFilePath, buildFilePath, abcFilePath, npmPkgName, false); + moduleInfos.push(tempModuleInfo); + nodeModulesFile.push(tempFilePath); + } else { + const tempModuleInfo: ModuleInfo = new ModuleInfo(filePath, tempFilePath, buildFilePath, abcFilePath, npmPkgName, true); + moduleInfos.push(tempModuleInfo); + nodeModulesFile.push(tempFilePath); + } + if (!filePath.endsWith(EXTNAME_JSON)) { + buildMapFileList.add(toUnixPath(filePath.replace(projectConfig.projectRootPath + path.sep, ''))); + } +} + +function processEtsModule(filePath: string, tempFilePath: string, buildFilePath: string, nodeModulesFile: Array, module: Object): void { + // skip declaration modules + if (filePath.endsWith(EXTNAME_D_ETS)) { + return; + } + if (projectConfig.processTs === true) { + tempFilePath = tempFilePath.replace(/\.ets$/, EXTNAME_TS); + buildFilePath = buildFilePath.replace(/\.ets$/, EXTNAME_TS); + } else { + tempFilePath = tempFilePath.replace(/\.ets$/, EXTNAME_JS); + buildFilePath = buildFilePath.replace(/\.ets$/, EXTNAME_JS); + } + const abcFilePath: string = genAbcFileName(tempFilePath); + if (isPackageModulesFile(filePath, projectConfig)) { + processNodeModulesFile(filePath, tempFilePath, buildFilePath, abcFilePath, nodeModulesFile, module); + } else { + const moduleName: string = getPackageInfo(projectConfig.aceModuleJsonPath)[1]; + const tempModuleInfo: ModuleInfo = new ModuleInfo(filePath, tempFilePath, buildFilePath, abcFilePath, moduleName, false); + moduleInfos.push(tempModuleInfo); + } + buildMapFileList.add(toUnixPath(filePath.replace(projectConfig.projectRootPath + path.sep, ''))); +} + +function processTsModule(filePath: string, tempFilePath: string, buildFilePath: string, nodeModulesFile: Array, module: Object): void { + // skip declaration modules + if (filePath.endsWith(EXTNAME_D_TS)) { + return; + } + if (projectConfig.processTs === false) { + tempFilePath = tempFilePath.replace(/\.ts$/, EXTNAME_JS); + buildFilePath = buildFilePath.replace(/\.ts$/, EXTNAME_JS); + } + const abcFilePath: string = genAbcFileName(tempFilePath); + if (isPackageModulesFile(filePath, projectConfig)) { + processNodeModulesFile(filePath, tempFilePath, buildFilePath, abcFilePath, nodeModulesFile, module); + } else { + const moduleName: string = getPackageInfo(projectConfig.aceModuleJsonPath)[1]; + const tempModuleInfo: ModuleInfo = new ModuleInfo(filePath, tempFilePath, buildFilePath, abcFilePath, moduleName, false); + moduleInfos.push(tempModuleInfo); + } + buildMapFileList.add(toUnixPath(filePath.replace(projectConfig.projectRootPath + path.sep, ''))); +} + +function processJsModule(filePath: string, tempFilePath: string, buildFilePath: string, nodeModulesFile: Array, module: Object): void { + const parent: string = path.join(tempFilePath, '..'); + if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { + mkDir(parent); + } + if (filePath.endsWith(EXTNAME_MJS) || filePath.endsWith(EXTNAME_CJS)) { + fs.copyFileSync(filePath, tempFilePath); + } + const abcFilePath: string = genAbcFileName(tempFilePath); + if (isPackageModulesFile(filePath, projectConfig)) { + processNodeModulesFile(filePath, tempFilePath, buildFilePath, abcFilePath, nodeModulesFile, module); + } else { + const moduleName: string = getPackageInfo(projectConfig.aceModuleJsonPath)[1]; + const tempModuleInfo: ModuleInfo = new ModuleInfo(filePath, tempFilePath, buildFilePath, abcFilePath, moduleName, false); + moduleInfos.push(tempModuleInfo); + } + buildMapFileList.add(toUnixPath(filePath.replace(projectConfig.projectRootPath + path.sep, ''))); +} + +function processJsonModule(filePath: string, tempFilePath: string, buildFilePath: string, nodeModulesFile: Array, module: Object): void { + const abcFilePath: string = 'NA'; + if (isPackageModulesFile(filePath, projectConfig)) { + processNodeModulesFile(filePath, tempFilePath, buildFilePath, abcFilePath, nodeModulesFile, module); + } else { + const moduleName: string = getPackageInfo(projectConfig.aceModuleJsonPath)[1]; + const tempModuleInfo: ModuleInfo = new ModuleInfo(filePath, tempFilePath, buildFilePath, abcFilePath, moduleName, false); + moduleInfos.push(tempModuleInfo); + } +} + +let cachedSourceMaps: Object; + +function updateCachedSourceMaps(): void { + const CACHED_SOURCEMAPS: string = path.join(process.env.cachePath, SOURCEMAPS_JSON); + validateFilePathLength(CACHED_SOURCEMAPS, logger); + if (!fs.existsSync(CACHED_SOURCEMAPS)) { + cachedSourceMaps = {}; + } else { + cachedSourceMaps = JSON.parse(fs.readFileSync(CACHED_SOURCEMAPS).toString()); + } + Object.keys(newSourceMaps).forEach(key => { + cachedSourceMaps[key] = newSourceMaps[key]; + }); +} + +function getCachedModuleList(): Array { + const CACHED_MODULELIST_FILE: string = path.join(process.env.cachePath, MODULELIST_JSON); + validateFilePathLength(CACHED_MODULELIST_FILE, logger); + if (!fs.existsSync(CACHED_MODULELIST_FILE)) { + return []; + } + const data: Object = JSON.parse(fs.readFileSync(CACHED_MODULELIST_FILE).toString()); + const moduleList: Array = data.list; + return moduleList; +} + +function updateCachedModuleList(moduleList: Array): void { + const CACHED_MODULELIST_FILE: string = path.join(process.env.cachePath, MODULELIST_JSON); + validateFilePathLength(CACHED_MODULELIST_FILE, logger); + const CACHED_SOURCEMAPS: string = path.join(process.env.cachePath, SOURCEMAPS_JSON); + validateFilePathLength(CACHED_SOURCEMAPS, logger); + let cachedJson: Object = {}; + cachedJson.list = moduleList; + fs.writeFile(CACHED_MODULELIST_FILE, JSON.stringify(cachedJson, null, 2), 'utf-8', + (err) => { + if (err) { + logger.error(red, `ArkTS:ERROR Failed to write module list.`, reset); + } + } + ); + fs.writeFile(CACHED_SOURCEMAPS, JSON.stringify(cachedSourceMaps, null, 2), 'utf-8', + (err) => { + if (err) { + logger.error(red, `ArkTS:ERROR Failed to write cache sourceMaps json.`, reset); + } + } + ); +} + +function writeSourceMaps(): void { + mkdirsSync(projectConfig.buildPath); + let sourceMapFilePath: string = path.join(projectConfig.buildPath, SOURCEMAPS); + validateFilePathLength(sourceMapFilePath, logger); + fs.writeFile(sourceMapFilePath, JSON.stringify(cachedSourceMaps, null, 2), 'utf-8', + (err) => { + if (err) { + logger.error(red, `ArkTS:ERROR Failed to write sourceMaps.`, reset); + } + } + ); +} + +function eliminateUnusedFiles(moduleList: Array): void { + let cachedModuleList: Array = getCachedModuleList(); + if (cachedModuleList.length !== 0) { + let newCacheSourceMaps: Object = {}; + cachedModuleList.forEach((file) => { + if (moduleList.includes(file)) { + newCacheSourceMaps[file] = cachedSourceMaps[file]; + } + }); + cachedSourceMaps = newCacheSourceMaps; + } +} + +function handleFullModuleFiles(modules, callback): void { + const nodeModulesFile: Array = []; + modules.forEach(module => { + if (module !== undefined && module.resourceResolveData !== undefined) { + const filePath: string = module.resourceResolveData.path; + let tempFilePath = genTemporaryPath(filePath, projectConfig.projectPath, process.env.cachePath, + projectConfig, undefined); + if (tempFilePath.length === 0) { + return; + } + validateFilePathLength(tempFilePath, logger); + let buildFilePath: string = genBuildPath(filePath, projectConfig.projectPath, projectConfig.buildPath, + projectConfig); + validateFilePathLength(buildFilePath, logger); + tempFilePath = toUnixPath(tempFilePath); + buildFilePath = toUnixPath(buildFilePath); + + switch (path.extname(filePath)) { + case EXTNAME_ETS: { + processEtsModule(filePath, tempFilePath, buildFilePath, nodeModulesFile, module); + break; + } + case EXTNAME_TS: { + processTsModule(filePath, tempFilePath, buildFilePath, nodeModulesFile, module); + break; + } + case EXTNAME_JS: + case EXTNAME_MJS: + case EXTNAME_CJS: { + processJsModule(filePath, tempFilePath, buildFilePath, nodeModulesFile, module); + break; + } + case EXTNAME_JSON: { + processJsonModule(filePath, tempFilePath, buildFilePath, nodeModulesFile, module); + break; + } + default: { + logger.error(red, `ArkTS:ERROR Cannot find resolve this file path: ${filePath}`, reset); + process.exitCode = FAIL; + } + } + } + }); + + // for mergeabc source maps + if (projectConfig.buildArkMode === 'debug') { + const moduleList: Array = Array.from(buildMapFileList); + updateCachedSourceMaps(); + eliminateUnusedFiles(moduleList); + updateCachedModuleList(moduleList); + writeSourceMaps(); + } + + if (process.env.panda !== TS2ABC) { + const outputABCPath: string = path.join(projectConfig.buildPath, MODULES_ABC); + validateFilePathLength(outputABCPath, logger); + generateMergedAbc(moduleInfos, entryInfos, outputABCPath); + clearGlobalInfo(); + } else { + invokeWorkersModuleToGenAbc(moduleInfos); + } +} + +function processEntryToGenAbc(entryInfos: Map): void { + if (entryInfos.size <= 0) { + return; + } + generateNpmEntriesInfo(entryInfos); + const npmEntriesInfoPath: string = path.join(process.env.cachePath, NPMENTRIES_TXT); + validateFilePathLength(npmEntriesInfoPath, logger); + let npmEntriesProtoFileName: string = 'npm_entries' + EXTNAME_PROTO_BIN; + const npmEntriesProtoFilePath: string = path.join(process.env.cachePath, 'protos', 'npm_entries', npmEntriesProtoFileName); + validateFilePathLength(npmEntriesProtoFilePath, logger); + mkdirsSync(path.dirname(npmEntriesProtoFilePath)); + let js2Abc: string = path.join(arkDir, 'build', 'bin', 'js2abc'); + if (isWin) { + js2Abc = path.join(arkDir, 'build-win', 'bin', 'js2abc.exe'); + } else if (isMac) { + js2Abc = path.join(arkDir, 'build-mac', 'bin', 'js2abc'); + } + validateFilePathLength(js2Abc, logger); + const singleCmd: string = `"${js2Abc}" --compile-npm-entries "${npmEntriesInfoPath}" "${npmEntriesProtoFilePath}`; + try { + childProcess.execSync(singleCmd); + } catch (e) { + logger.debug(red, `ArkTS:ERROR Failed to generate npm proto file to abc, Error message: ${e}`, reset); + } +} + +function writeFileSync(inputString: string, buildPath: string, keyPath: string, jsBundleFile: string): void { + let output = path.resolve(buildPath, keyPath); + validateFilePathLength(output, logger); + let parent: string = path.join(output, '..'); + if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { + mkDir(parent); + } + let cacheOutputPath: string = ''; + if (process.env.cachePath) { + let buildDirArr: string[] = projectConfig.buildPath.split(path.sep); + let abilityDir: string = buildDirArr[buildDirArr.length - 1]; + cacheOutputPath = path.join(process.env.cachePath, TEMPORARY, abilityDir, keyPath); + } else { + cacheOutputPath = output; + } + validateFilePathLength(cacheOutputPath, logger); + parent = path.join(cacheOutputPath, '..'); + if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { + mkDir(parent); + } + fs.writeFileSync(cacheOutputPath, inputString); + if (fs.existsSync(cacheOutputPath)) { + const fileSize: number = fs.statSync(cacheOutputPath).size; + let sourceFile: string = output.replace(/\.temp\.js$/, '_.js'); + if (projectConfig.projectRootPath) { + sourceFile = toUnixPath(sourceFile.replace(projectConfig.projectRootPath + path.sep, '')); + } else { + sourceFile = toUnixPath(sourceFile); + } + output = toUnixPath(output); + cacheOutputPath = toUnixPath(cacheOutputPath); + + intermediateJsBundle.push({path: output, size: fileSize, cacheOutputPath: cacheOutputPath, sourceFile: sourceFile}); + } else { + logger.debug(red, `ArkTS:ERROR Failed to convert file ${jsBundleFile} to bin. ${output} is lost`, reset); + process.exitCode = FAIL; + } +} + +function mkDir(path_: string): void { + const parent: string = path.join(path_, '..'); + if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { + mkDir(parent); + } + fs.mkdirSync(path_); +} + +function getSmallestSizeGroup(groupSize: Map): any { + const groupSizeArray: any = Array.from(groupSize); + groupSizeArray.sort(function(g1, g2) { + return g1[1] - g2[1]; // sort by size + }); + return groupSizeArray[0][0]; +} + +function splitJsBundlesBySize(bundleArray: Array, groupNumber: number): any { + const result: any = []; + if (bundleArray.length < groupNumber) { + for (const value of bundleArray) { + result.push([value]); + } + return result; + } + + bundleArray.sort(function(f1: File, f2: File) { + return f2.size - f1.size; + }); + const groupFileSize = new Map(); + for (let i = 0; i < groupNumber; ++i) { + result.push([]); + groupFileSize.set(i, 0); + } + + let index = 0; + while (index < bundleArray.length) { + const smallestGroup: any = getSmallestSizeGroup(groupFileSize); + result[smallestGroup].push(bundleArray[index]); + const sizeUpdate: any = groupFileSize.get(smallestGroup) + bundleArray[index].size; + groupFileSize.set(smallestGroup, sizeUpdate); + index++; + } + return result; +} + +function invokeWorkersModuleToGenAbc(moduleInfos: Array): void { + invokeClusterModuleToAbc(); +} + +export function initAbcEnv() : string[] { + let args: string[] = []; + if (process.env.minPlatformVersion === '8') { + process.env.panda = TS2ABC; + let js2abc: string = path.join(arkDir, 'build', 'legacy_api8', 'src', 'index.js'); + if (isWin) { + js2abc = path.join(arkDir, 'build-win', 'legacy_api8', 'src', 'index.js'); + } else if (isMac) { + js2abc = path.join(arkDir, 'build-mac', 'legacy_api8', 'src', 'index.js'); + } + validateFilePathLength(js2abc, logger); + + js2abc = '"' + js2abc + '"'; + args = [ + '--expose-gc', + js2abc + ]; + if (isDebug) { + args.push('--debug'); + } + } else if (process.env.panda === TS2ABC) { + let js2abc: string = path.join(arkDir, 'build', 'src', 'index.js'); + if (isWin) { + js2abc = path.join(arkDir, 'build-win', 'src', 'index.js'); + } else if (isMac) { + js2abc = path.join(arkDir, 'build-mac', 'src', 'index.js'); + } + validateFilePathLength(js2abc, logger); + + js2abc = '"' + js2abc + '"'; + args = [ + '--expose-gc', + js2abc + ]; + if (isDebug) { + args.push('--debug'); + } + } else if (process.env.panda === ES2ABC || process.env.panda === 'undefined' || process.env.panda === undefined) { + let es2abc: string = path.join(arkDir, 'build', 'bin', 'es2abc'); + if (isWin) { + es2abc = path.join(arkDir, 'build-win', 'bin', 'es2abc.exe'); + } else if (isMac) { + es2abc = path.join(arkDir, 'build-mac', 'bin', 'es2abc'); + } + validateFilePathLength(es2abc, logger); + + args = [ + '"' + es2abc + '"' + ]; + if (isDebug) { + args.push('--debug-info'); + } + if (projectConfig.compileMode === ESMODULE) { + args.push('--merge-abc'); + } + } else { + logger.error(red, `ArkTS:ERROR please set panda module`, reset); + } + + return args; +} + +function invokeClusterModuleToAbc(): void { + if (process.env.watchMode === 'true') { + process.exitCode = SUCCESS; + } + filterIntermediateModuleByHashJson(buildPathInfo, moduleInfos); + const abcArgs: string[] = initAbcEnv(); + + const splitedModules: any[] = splitModulesByNumber(filterModuleInfos, MAX_WORKER_NUMBER); + let cmdPrefix: string = `${nodeJs} ${abcArgs.join(' ')}`; + const workerNumber: number = MAX_WORKER_NUMBER < splitedModules.length ? MAX_WORKER_NUMBER : splitedModules.length; + + try { + if (process.env.watchMode === 'true') { + processWorkersOfPreviewMode(splitedModules, cmdPrefix, workerNumber); + } else { + processWorkersOfBuildMode(splitedModules, cmdPrefix, workerNumber); + } + } catch (e) { + logger.debug(red, `ArkTS:ERROR failed to generate abc. Error message: ${e}`, reset); + process.env.abcCompileSuccess = 'false'; + if (process.env.watchMode !== 'true') { + process.exit(FAIL); + } + } +} + +function splitModulesByNumber(moduleInfos: Array, workerNumber: number): any[] { + const result: any = []; + if (moduleInfos.length < workerNumber) { + for (const value of moduleInfos) { + result.push([value]); + } + return result; + } + + for (let i = 0; i < workerNumber; ++i) { + result.push([]); + } + + for (let i = 0; i < moduleInfos.length; i++) { + const chunk = i % workerNumber; + result[chunk].push(moduleInfos[i]); + } + + return result; +} + +function invokeWorkersToGenAbc(): void { + if (process.env.watchMode === 'true') { + process.exitCode = SUCCESS; + } + let cmdPrefix: string = ''; + + const abcArgs: string[] = initAbcEnv(); + if (process.env.panda === TS2ABC) { + cmdPrefix = `${nodeJs} ${abcArgs.join(' ')}`; + } else { + logger.error(red, `ArkTS:ERROR please set panda module`, reset); + } + + filterIntermediateJsBundleByHashJson(buildPathInfo, intermediateJsBundle); + const splitedBundles: any[] = splitJsBundlesBySize(fileterIntermediateJsBundle, MAX_WORKER_NUMBER); + const workerNumber: number = MAX_WORKER_NUMBER < splitedBundles.length ? MAX_WORKER_NUMBER : splitedBundles.length; + + try { + if (process.env.watchMode === 'true') { + processWorkersOfPreviewMode(splitedBundles, cmdPrefix, workerNumber); + } else { + processWorkersOfBuildMode(splitedBundles, cmdPrefix, workerNumber); + } + } catch (e) { + logger.debug(red, `ArkTS:ERROR failed to generate abc. Error message: ${e}`, reset); + process.env.abcCompileSuccess = 'false'; + if (process.env.watchMode !== 'true') { + process.exit(FAIL); + } + } +} + +function filterIntermediateModuleByHashJson(buildPath: string, moduleInfos: Array): void { + const tempModuleInfos = Array(); + moduleInfos.forEach((item) => { + const check = tempModuleInfos.every((newItem) => { + return item.tempFilePath !== newItem.tempFilePath; + }); + if (check) { + tempModuleInfos.push(item); + } + }); + moduleInfos = tempModuleInfos; + + for (let i = 0; i < moduleInfos.length; ++i) { + filterModuleInfos.push(moduleInfos[i]); + } + const hashFilePath: string = genHashJsonPath(buildPath); + if (hashFilePath.length === 0) { + return; + } + const updateJsonObject: Object = {}; + let jsonObject: Object = {}; + let jsonFile: string = ''; + if (fs.existsSync(hashFilePath)) { + jsonFile = fs.readFileSync(hashFilePath).toString(); + jsonObject = JSON.parse(jsonFile); + filterModuleInfos = []; + for (let i = 0; i < moduleInfos.length; ++i) { + const input: string = moduleInfos[i].tempFilePath; + let outputPath: string = genProtoFileName(moduleInfos[i].tempFilePath); + if (!fs.existsSync(input)) { + logger.debug(red, `ArkTS:ERROR ${input} is lost`, reset); + process.exitCode = FAIL; + break; + } + if (fs.existsSync(outputPath)) { + const hashInputContentData: string = toHashData(input); + const hashAbcContentData: string = toHashData(outputPath); + if (jsonObject[input] === hashInputContentData && jsonObject[outputPath] === hashAbcContentData) { + updateJsonObject[input] = hashInputContentData; + updateJsonObject[outputPath] = hashAbcContentData; + } else { + filterModuleInfos.push(moduleInfos[i]); + } + } else { + filterModuleInfos.push(moduleInfos[i]); + } + } + } + + moduleHashJsonObject = updateJsonObject; +} + +function writeModuleHashJson(): void { + for (let i = 0; i < filterModuleInfos.length; ++i) { + const input: string = filterModuleInfos[i].tempFilePath; + let outputPath: string = genProtoFileName(filterModuleInfos[i].tempFilePath); + if (!fs.existsSync(input) || !fs.existsSync(outputPath)) { + logger.debug(red, `ArkTS:ERROR ${input} is lost`, reset); + process.exitCode = FAIL; + break; + } + const hashInputContentData: string = toHashData(input); + const hashOutputContentData: string = toHashData(outputPath); + moduleHashJsonObject[input] = hashInputContentData; + moduleHashJsonObject[outputPath] = hashOutputContentData; + } + const hashFilePath: string = genHashJsonPath(buildPathInfo); + if (hashFilePath.length === 0) { + return; + } + // fix bug of multi trigger + fs.writeFileSync(hashFilePath, JSON.stringify(moduleHashJsonObject)); +} + +function filterIntermediateJsBundleByHashJson(buildPath: string, inputPaths: File[]): void { + inputPaths = removeDuplicateInfoOfBundleList(inputPaths); + + for (let i = 0; i < inputPaths.length; ++i) { + fileterIntermediateJsBundle.push(inputPaths[i]); + } + const hashFilePath: string = genHashJsonPath(buildPath); + if (hashFilePath.length === 0) { + return; + } + const updateJsonObject: Object = {}; + let jsonObject: Object = {}; + let jsonFile: string = ''; + if (fs.existsSync(hashFilePath)) { + jsonFile = fs.readFileSync(hashFilePath).toString(); + jsonObject = JSON.parse(jsonFile); + fileterIntermediateJsBundle = []; + for (let i = 0; i < inputPaths.length; ++i) { + const cacheOutputPath: string = inputPaths[i].cacheOutputPath; + const cacheAbcFilePath: string = cacheOutputPath.replace(/\.temp\.js$/, '.abc'); + if (!fs.existsSync(cacheOutputPath)) { + logger.debug(red, `ArkTS:ERROR ${cacheOutputPath} is lost`, reset); + process.exitCode = FAIL; + break; + } + if (fs.existsSync(cacheAbcFilePath)) { + const hashInputContentData: string = toHashData(cacheOutputPath); + const hashAbcContentData: string = toHashData(cacheAbcFilePath); + if (jsonObject[cacheOutputPath] === hashInputContentData && jsonObject[cacheAbcFilePath] === hashAbcContentData) { + updateJsonObject[cacheOutputPath] = hashInputContentData; + updateJsonObject[cacheAbcFilePath] = hashAbcContentData; + } else { + fileterIntermediateJsBundle.push(inputPaths[i]); + } + } else { + fileterIntermediateJsBundle.push(inputPaths[i]); + } + } + } + + hashJsonObject = updateJsonObject; +} + +function writeHashJson(): void { + for (let i = 0; i < fileterIntermediateJsBundle.length; ++i) { + const cacheOutputPath: string = fileterIntermediateJsBundle[i].cacheOutputPath; + const cacheAbcFilePath: string = cacheOutputPath.replace(/\.temp\.js$/, '.abc'); + if (!fs.existsSync(cacheOutputPath) || !fs.existsSync(cacheAbcFilePath)) { + logger.debug(red, `ArkTS:ERROR ${cacheOutputPath} is lost`, reset); + process.exitCode = FAIL; + break; + } + const hashInputContentData: string = toHashData(cacheOutputPath); + const hashAbcContentData: string = toHashData(cacheAbcFilePath); + hashJsonObject[cacheOutputPath] = hashInputContentData; + hashJsonObject[cacheAbcFilePath] = hashAbcContentData; + } + const hashFilePath: string = genHashJsonPath(buildPathInfo); + if (hashFilePath.length === 0) { + return; + } + fs.writeFileSync(hashFilePath, JSON.stringify(hashJsonObject)); +} + +function genHashJsonPath(buildPath: string): string { + buildPath = toUnixPath(buildPath); + if (process.env.cachePath) { + if (!fs.existsSync(process.env.cachePath) || !fs.statSync(process.env.cachePath).isDirectory()) { + logger.debug(red, `ArkTS:ERROR hash path does not exist`, reset); + return ''; + } + let buildDirArr: string[] = projectConfig.buildPath.split(path.sep); + let abilityDir: string = buildDirArr[buildDirArr.length - 1]; + let hashJsonPath: string = path.join(process.env.cachePath, TEMPORARY, abilityDir, hashFile); + validateFilePathLength(hashJsonPath, logger); + mkdirsSync(path.dirname(hashJsonPath)); + return hashJsonPath; + } else if (buildPath.indexOf(ARK) >= 0) { + const dataTmps: string[] = buildPath.split(ARK); + const hashPath: string = path.join(dataTmps[0], ARK); + if (!fs.existsSync(hashPath) || !fs.statSync(hashPath).isDirectory()) { + logger.debug(red, `ArkTS:ERROR hash path does not exist`, reset); + return ''; + } + let hashJsonPath: string = path.join(hashPath, hashFile); + validateFilePathLength(hashJsonPath, logger); + return hashJsonPath; + } else { + logger.debug(red, `ArkTS:ERROR not cache exist`, reset); + return ''; + } +} + +function checkNodeModules() { + if (process.env.panda === TS2ABC) { + let arkEntryPath: string = path.join(arkDir, 'build'); + if (isWin) { + arkEntryPath = path.join(arkDir, 'build-win'); + } else if (isMac) { + arkEntryPath = path.join(arkDir, 'build-mac'); + } + let nodeModulesPath: string = path.join(arkEntryPath, NODE_MODULES); + validateFilePathLength(nodeModulesPath, logger); + if (!(fs.existsSync(nodeModulesPath) && fs.statSync(nodeModulesPath).isDirectory())) { + logger.error(red, `ERROR: node_modules for ark compiler not found. + Please make sure switch to non-root user before runing "npm install" for safity requirements and try re-run "npm install" under ${arkEntryPath}`, reset); + return false; + } + } + + return true; +} + +function copyFileCachePathToBuildPath() { + for (let i = 0; i < intermediateJsBundle.length; ++i) { + const abcFile: string = intermediateJsBundle[i].path.replace(/\.temp\.js$/, '.abc'); + const cacheOutputPath: string = intermediateJsBundle[i].cacheOutputPath; + const cacheAbcFilePath: string = intermediateJsBundle[i].cacheOutputPath.replace(/\.temp\.js$/, '.abc'); + if (!fs.existsSync(cacheAbcFilePath)) { + logger.debug(red, `ArkTS:ERROR ${cacheAbcFilePath} is lost`, reset); + process.exitCode = FAIL; + break; + } + let parent: string = path.join(abcFile, '..'); + if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { + mkDir(parent); + } + // for preview mode, cache path and old abc file both exist, should copy abc file for updating + if (process.env.cachePath !== undefined) { + fs.copyFileSync(cacheAbcFilePath, abcFile); + } + if (process.env.cachePath === undefined && fs.existsSync(cacheOutputPath)) { + fs.unlinkSync(cacheOutputPath); + } + } +} + +function processExtraAsset() { + if (projectConfig.compileMode === JSBUNDLE || projectConfig.compileMode === undefined) { + writeHashJson(); + copyFileCachePathToBuildPath(); + } else if (projectConfig.compileMode === ESMODULE) { + processEntryToGenAbc(entryInfos); + writeModuleHashJson(); + copyModuleFileCachePathToBuildPath(); + mergeProtoToAbc(); + } + clearGlobalInfo(); +} + +function handleHotReloadChangedFiles() { + if (!fs.existsSync(projectConfig.changedFileList)) { + logger.debug(blue, `ArkTS: Cannot find file: ${projectConfig.changedFileList}, skip hot reload build`, reset); + return; + } + + let changedFileListJson: string = fs.readFileSync(projectConfig.changedFileList).toString(); + let changedFileList: Array = JSON.parse(changedFileListJson).modifiedFiles; + if (typeof(changedFileList) === 'undefined' || changedFileList.length === 0) { + return; + } + + let relativeProjectPath = projectConfig.projectPath.slice(projectConfig.projectRootPath.length + path.sep.length); + const nodeModulesFile: Array = []; + let hotReloadSourceMap: Object = {}; + moduleInfos = []; + + for (let file of changedFileList) { + let filePath: string = path.join(projectConfig.projectPath, file); + validateFilePathLength(filePath, logger); + let tempFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, process.env.cachePath, + projectConfig, undefined); + if (tempFilePath.length === 0) { + return; + } + validateFilePathLength(tempFilePath, logger); + let buildFilePath: string = ''; + tempFilePath = toUnixPath(tempFilePath); + + switch (path.extname(filePath)) { + case EXTNAME_ETS: { + processEtsModule(filePath, tempFilePath, buildFilePath, nodeModulesFile, undefined); + break; + } + case EXTNAME_TS: { + processTsModule(filePath, tempFilePath, buildFilePath, nodeModulesFile, undefined); + break; + } + case EXTNAME_JS: + case EXTNAME_MJS: + case EXTNAME_CJS: { + processJsModule(filePath, tempFilePath, buildFilePath, nodeModulesFile, undefined); + break; + } + case EXTNAME_JSON: { + logger.debug(blue, `ArkTS: json source file: ${filePath} changed, skip hot reload build`, reset); + return; + } + default: { + logger.debug(blue, `ArkTS:ERROR Cannot resolve file path: ${filePath}, stop hot reload build`, reset); + return; + } + } + + let sourceMapPath: string = toUnixPath(path.join(relativeProjectPath, file)); + validateFilePathLength(sourceMapPath, logger); + hotReloadSourceMap[sourceMapPath] = newSourceMaps[sourceMapPath]; + } + + if (!fs.existsSync(projectConfig.patchAbcPath)) { + mkdirsSync(projectConfig.patchAbcPath); + } + + const outputABCPath: string = path.join(projectConfig.patchAbcPath, MODULES_ABC); + validateFilePathLength(outputABCPath, logger); + generateMergedAbc(moduleInfos, entryInfos, outputABCPath); + + // write source maps + let sourceMapFilePath: string = path.join(projectConfig.patchAbcPath, SOURCEMAPS); + validateFilePathLength(sourceMapFilePath, logger); + fs.writeFileSync(sourceMapFilePath, + JSON.stringify(hotReloadSourceMap, null, 2), 'utf-8'); +} + +function handleFinishModules(modules, callback) { + if (projectConfig.hotReload && !isHotReloadFirstBuild) { + handleHotReloadChangedFiles(); + return; + } + + handleFullModuleFiles(modules, callback); + + if (projectConfig.hotReload) { + isHotReloadFirstBuild = false; + } +} + +function copyModuleFileCachePathToBuildPath(): void { + protoFilePath = path.join(path.join(process.env.cachePath, 'protos', PROTO_FILESINFO_TXT)); + validateFilePathLength(protoFilePath, logger); + mkdirsSync(path.dirname(protoFilePath)); + let entriesInfo: string = ''; + moduleInfos = removeDuplicateInfo(moduleInfos); + moduleInfos.sort((m1: ModuleInfo, m2: ModuleInfo) => { + return m1.tempFilePath < m2.tempFilePath ? 1 : -1; + }); + for (let i = 0; i < moduleInfos.length; ++i) { + let protoTempPath: string = genProtoFileName(moduleInfos[i].tempFilePath); + entriesInfo += `${toUnixPath(protoTempPath)}\n`; + } + if (entryInfos.size > 0) { + let npmEntriesProtoFileName: string = 'npm_entries' + EXTNAME_PROTO_BIN; + const npmEntriesProtoFilePath: string = path.join(process.env.cachePath, 'protos', 'npm_entries', npmEntriesProtoFileName); + entriesInfo += `${toUnixPath(npmEntriesProtoFilePath)}\n`; + } + fs.writeFileSync(protoFilePath, entriesInfo, 'utf-8'); +} + +function mergeProtoToAbc(): void { + let mergeAbc: string = path.join(arkDir, 'build', 'bin', 'merge_abc'); + if (isWin) { + mergeAbc = path.join(arkDir, 'build-win', 'bin', 'merge_abc.exe'); + } else if (isMac) { + mergeAbc = path.join(arkDir, 'build-mac', 'bin', 'merge_abc'); + } + mkdirsSync(projectConfig.buildPath); + const singleCmd: string = `"${mergeAbc}" --input "@${protoFilePath}" --outputFilePath "${projectConfig.buildPath}" --output ${MODULES_ABC} --suffix protoBin`; + try { + childProcess.execSync(singleCmd); + } catch (e) { + logger.debug(red, `ArkTS:ERROR Failed to merge proto file to abc. Error message: ${e}`, reset); + } +} + +function generateAbcByEs2AbcOfBundleMode(inputPaths: File[]) { + filterIntermediateJsBundleByHashJson(buildPathInfo, inputPaths); + if (fileterIntermediateJsBundle.length === 0) { + processExtraAsset(); + return; + } + let filesInfoPath = generateFileOfBundle(fileterIntermediateJsBundle); + const fileThreads = os.cpus().length < 16 ? os.cpus().length : 16; + let genAbcCmd: string = + `${initAbcEnv().join(' ')} "@${filesInfoPath}" --file-threads "${fileThreads}"`; + logger.debug('gen abc cmd is: ', genAbcCmd); + try { + if (process.env.watchMode === 'true') { + childProcess.execSync(genAbcCmd); + processExtraAsset(); + } else { + const child = childProcess.exec(genAbcCmd); + child.on('exit', (code: any) => { + if (code === FAIL) { + logger.debug(red, 'ArkTS:ERROR failed to execute es2abc', reset); + process.exit(FAIL); + } + if (process.env.cachePath === undefined) { + unlinkSync(filesInfoPath); + } + processExtraAsset(); + }); + + child.on('error', (err: any) => { + logger.debug(red, err.toString(), reset); + process.exit(FAIL); + }); + + child.stderr.on('data', (data: any) => { + logger.error(red, data.toString(), reset); + }); + } + } catch (e) { + logger.debug(red, `ArkTS:ERROR failed to generate abc with filesInfo ${filesInfoPath}. Error message: ${e} `, reset); + process.env.abcCompileSuccess = 'false'; + if (process.env.watchMode !== 'true') { + process.exit(FAIL); + } + } finally { + if (process.env.watchMode === 'true') { + if (process.env.cachePath === undefined) { + unlinkSync(filesInfoPath); + } + } + } +} + +function generateFileOfBundle(inputPaths: File[]): string { + let filesInfoPath: string = buildCachePath(FILESINFO_TXT, projectConfig, logger); + inputPaths = removeDuplicateInfoOfBundleList(inputPaths); + + let filesInfo: string = ''; + inputPaths.forEach(info => { + const cacheOutputPath: string = info.cacheOutputPath; + const recordName: string = 'null_recordName'; + const moduleType: string = 'script'; + const sourceFile: string = info.sourceFile; + const abcFilePath: string = cacheOutputPath.replace(/\.temp\.js$/, '.abc'); + filesInfo += `${cacheOutputPath};${recordName};${moduleType};${sourceFile};${abcFilePath}\n`; + }); + fs.writeFileSync(filesInfoPath, filesInfo, 'utf-8'); + + return filesInfoPath; +} + +function removeDuplicateInfoOfBundleList(inputPaths: File[]) { + const tempInputPaths = Array(); + inputPaths.forEach((item) => { + const check = tempInputPaths.every((newItem) => { + return item.path !== newItem.path; + }); + if (check) { + tempInputPaths.push(item); + } + }); + inputPaths = tempInputPaths; + + return inputPaths; +} + +function processWorkersOfPreviewMode(splittedData: any, cmdPrefix: string, workerNumber: number) { + let processEnv: any = Object.assign({}, process.env); + let arkEnvParams: any = { + 'splittedData': JSON.stringify(splittedData), + 'cmdPrefix': cmdPrefix, + 'workerNumber': workerNumber.toString(), + }; + if (projectConfig.compileMode === JSBUNDLE || projectConfig.compileMode === undefined) { + arkEnvParams.mode = JSBUNDLE; + } else if (projectConfig.compileMode === ESMODULE) { + arkEnvParams.cachePath = process.env.cachePath; + arkEnvParams.mode = ESMODULE; + } + processEnv.arkEnvParams = JSON.stringify(arkEnvParams); + + let genAbcCmd: string = `${nodeJs} "${path.resolve(__dirname, MANAGE_WORKERS_SCRIPT)}"`; //??? + childProcess.execSync(genAbcCmd, {env: processEnv}); + processExtraAsset(); +} + +function processWorkersOfBuildMode(splittedData: any, cmdPrefix: string, workerNumber: number) { + const useNewApi: boolean = nodeLargeOrEqualTargetVersion(16); + + if (useNewApi && cluster.isPrimary || !useNewApi && cluster.isMaster) { + let genAbcScript: string = GEN_ABC_SCRIPT; + if (projectConfig.compileMode === ESMODULE) { + genAbcScript = GEN_MODULE_ABC_SCRIPT; + } + if (useNewApi) { + cluster.setupPrimary({ + exec: path.resolve(__dirname, genAbcScript) + }); + } else { + cluster.setupMaster({ + exec: path.resolve(__dirname, genAbcScript) + }); + } + + for (let i = 0; i < workerNumber; ++i) { + let workerData: any = { + 'inputs': JSON.stringify(splittedData[i]), + 'cmd': cmdPrefix + }; + if (projectConfig.compileMode === ESMODULE) { + let sn: number = i + 1; + let workerFileName: string = `filesInfo_${sn}.txt`; + workerData.workerFileName = workerFileName; + workerData.cachePath = process.env.cachePath; + } + cluster.fork(workerData); + } + + cluster.on('exit', (worker, code, signal) => { + if (code === FAIL) { + process.exitCode = FAIL; + } + logger.debug(`worker ${worker.process.pid} finished`); + }); + + process.on('exit', (code) => { + if (process.exitCode !== FAIL && process.env.watchMode !== 'true') { + processExtraAsset(); + if (projectConfig.compileMode === ESMODULE && + (projectConfig.anBuildMode === AOT_FULL || projectConfig.anBuildMode === AOT_PARTIAL)) { + let faultHandler: FaultHandler = (error) => { logger.error(error); process.exit(FAIL); }; + let abcArgs: string[] = initAbcEnv(); + abcArgs.unshift(nodeJs); + const appAbc: string = path.join(projectConfig.buildPath, MODULES_ABC); + generateAot(arkDir, appAbc, projectConfig, logger, faultHandler); + } + } + }); + } +} diff --git a/compiler/src/interop/src/gen_aot.ts b/compiler/src/interop/src/gen_aot.ts new file mode 100644 index 0000000000000000000000000000000000000000..5a0b598cf06aae6ef2349f3039e4c1e84732cbcd --- /dev/null +++ b/compiler/src/interop/src/gen_aot.ts @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2023 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 childProcess from 'child_process'; +import * as process from 'process'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { + MODULES_ABC, + TEMPORARY, + AOT_FULL, + AOT_TYPE, + AOT_PARTIAL, + TS2ABC +} from './pre_define'; +import { + isWindows, + mkdirsSync, + toUnixPath, + validateFilePathLength, + validateFilePathLengths +} from './utils'; +import { + getArkBuildDir, + getBuildBinDir +} from './ark_utils'; + +const hostToolKeyWords: string = '[HostTool] '; +const logLevelIndex: number = 4; + +export interface FaultHandler { + (error: string): void +} + +function checkAotPartialConfig(compileMode: string, buildJsonInfo: any, faultHandler: FaultHandler): boolean { + if (buildJsonInfo.anBuildMode !== AOT_PARTIAL && !buildJsonInfo.apPath) { + // no AOT's partial related configuration is hit, pass the configuration to next compile mode + return false; + } + // Aot compiler's partial mode. + return true; +} + +function checkAotFullConfig(compileMode: string, buildJsonInfo: any, faultHandler: FaultHandler): boolean { + if (buildJsonInfo.anBuildMode !== AOT_FULL) { + // no AOT's full related configuration is hit, pass the configuration to next compile mode + return false; + } + // Aot compiler's full mode. + return true; +} + +function checkAotTypeConfig(compileMode: string, buildJsonInfo: any, faultHandler: FaultHandler): boolean { + if (buildJsonInfo.anBuildMode !== AOT_TYPE) { + // no AOT's type related configuration is hit, pass the configuration to next compile mode + return false; + } + // Aot compiler's type mode. + return true; +} + +/** + * Check if the AOT related configuration is hit + * @param compileMode CompileMode for the project, which can be jsbundle or esmodule + * @param buildJsonInfo buildJsonInfo which parsed from projectConfig.aceBuildJson + * @param faultHandler faultHandler for illegal AOT configuration + * @returns {Boolean} false means no AOT related configuration found, else return true + * @api private + */ +export function checkAotConfig(compileMode: string, buildJsonInfo: any, faultHandler: FaultHandler): boolean { + return checkAotTypeConfig(compileMode, buildJsonInfo, faultHandler) || + checkAotFullConfig(compileMode, buildJsonInfo, faultHandler) || + checkAotPartialConfig(compileMode, buildJsonInfo, faultHandler); +} + +export function generateAot(arkDir: string, appAbc: string, + projectConfig: any, logger: any, faultHandler: FaultHandler): void { + let aotCompiler: string = path.join(getBuildBinDir(arkDir), isWindows() ? 'ark_aot_compiler.exe' : 'ark_aot_compiler'); + const appAot: string = path.join(projectConfig.anBuildOutPut, projectConfig.moduleName); + + if (!validateFilePathLengths([aotCompiler, appAbc, appAot], logger)) { + faultHandler('ArkTS:ERROR GenerateAot failed. Invalid file path.'); + } + if (!fs.existsSync(appAbc)) { + faultHandler(`ArkTS:ERROR GenerateAot failed. AppAbc not found in "${appAbc}"`); + } + const singleCmdPrefix: string = `"${aotCompiler}" ` + + `--aot-file="${appAot}" --compiler-target-triple=aarch64-unknown-linux-gnu `; + let singleCmd: string = ''; + if (projectConfig.anBuildMode === AOT_FULL) { + singleCmd = singleCmdPrefix + ` "${appAbc}"`; + } else if (projectConfig.anBuildMode === AOT_PARTIAL) { + const profile: string = projectConfig.apPath; + if (!validateFilePathLength(profile, logger)) { + faultHandler('ArkTS:ERROR GenerateAot failed. Invalid profile file path.'); + } + if (!fs.existsSync(profile)) { + faultHandler(`ArkTS:ERROR GenerateAot failed. Partial mode lost profile in "${profile}"`); + } + singleCmd = singleCmdPrefix + ` --enable-pgo-profiler=true --compiler-pgo-profiler-path="${profile}" "${appAbc}"`; + } else { + faultHandler(`ArkTS:ERROR GenerateAot failed. unknown anBuildMode: ${projectConfig.anBuildMode}`); + } + try { + logger.debug(`generateAot cmd: ${singleCmd}`); + mkdirsSync(projectConfig.anBuildOutPut); + fs.closeSync(fs.openSync(appAot + '.an', 'w')); + fs.closeSync(fs.openSync(appAot + '.ai', 'w')); + } catch (e) { + // Extract HostTool log information from hilog, which outputs to stdout. + let outStream: Buffer = e.stdout; + outStream.toString().split(os.EOL).forEach((stdLog: string) => { + if (!stdLog.includes(hostToolKeyWords)) { + return; + } + let logHeader: string = ''; + let logContent: string = ''; + [logHeader, logContent] = stdLog.split(hostToolKeyWords); + if (!logHeader && !logContent) { + return; + } + let logLevel: string = logHeader.trim().split(/\s+/)[logLevelIndex]; + if (logLevel === 'F' || logLevel === 'E') { + logger.warn(`ArkTS:WARN ${logContent}`); + } + }); + logger.warn(`ArkTS:WARN ${e}`); + logger.warn(`ArkTS:WARN Failed to generate aot file, the module may run with an interpreter`); + } +} diff --git a/compiler/src/interop/src/gen_merged_abc.ts b/compiler/src/interop/src/gen_merged_abc.ts new file mode 100644 index 0000000000000000000000000000000000000000..a861ee1b30bffee69a07d35d3a896f56f3690932 --- /dev/null +++ b/compiler/src/interop/src/gen_merged_abc.ts @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2022 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 os from 'os'; +import * as childProcess from 'child_process'; +import * as process from 'process'; +import { logger } from './compile_info'; +import { projectConfig } from '../main'; +import { + FAIL, + FILESINFO_TXT, + MODULES_CACHE, + NPMENTRIES_TXT, + NODE_MODULES, + PACKAGES, + PATCH_SYMBOL_TABLE, + EXTNAME_PROTO_BIN +} from './pre_define'; +import { + EntryInfo, + ModuleInfo, + initAbcEnv +} from './gen_abc_plugin'; +import { + mkdirsSync, + toUnixPath, + validateFilePathLength +} from './utils'; + +const red: string = '\u001b[31m'; +const reset: string = '\u001b[39m'; + +function generateCompileFilesInfo(moduleInfos: Array) { + const tempModuleInfos: ModuleInfo[] = Array(); + moduleInfos.forEach((item) => { + let check: boolean = tempModuleInfos.every((newItem) => { + return item.tempFilePath !== newItem.tempFilePath; + }); + if (check) { + tempModuleInfos.push(item); + } + }); + moduleInfos = tempModuleInfos; + + const filesInfoPath: string = path.join(process.env.cachePath, FILESINFO_TXT); + validateFilePathLength(filesInfoPath, logger); + let filesInfo: string = ''; + moduleInfos.forEach(info => { + const moduleType: string = info.isCommonJs ? 'commonjs' : 'esm'; + const sourceFile: string = info.filePath.replace(projectConfig.projectRootPath + path.sep, ''); + filesInfo += `${info.tempFilePath};${info.recordName};${moduleType};${toUnixPath(sourceFile)};${info.packageName}\n`; + }); + fs.writeFileSync(filesInfoPath, filesInfo, 'utf-8'); +} + +export function generateNpmEntriesInfo(entryInfos: Map) { + const npmEntriesInfoPath: string = path.join(process.env.cachePath, NPMENTRIES_TXT); + validateFilePathLength(npmEntriesInfoPath, logger); + let entriesInfo: string = ''; + for (const value of entryInfos.values()) { + const buildPath: string = + value.buildPath.replace(toUnixPath(projectConfig.nodeModulesPath), '').replace(new RegExp(NODE_MODULES, 'g'), PACKAGES); + const entryFile: string = toUnixPath(path.join(buildPath, value.entry)); + const entry: string = entryFile.substring(0, entryFile.lastIndexOf('.')).replace(new RegExp(NODE_MODULES, 'g'), PACKAGES); + entriesInfo += + `${toUnixPath(path.join(PACKAGES, buildPath))}:${toUnixPath(path.join(PACKAGES, entry))}\n`; + } + fs.writeFileSync(npmEntriesInfoPath, entriesInfo, 'utf-8'); +} + +function generateAbcCacheFilesInfo(moduleInfos: Array, npmEntriesInfoPath: string, cacheFilePath: string): void { + let abcCacheFilesInfo: string = ''; + + // generate source file cache + moduleInfos.forEach((info) => { + let tempFilePathWithoutExt: string = info.tempFilePath.substring(0, info.tempFilePath.lastIndexOf('.')); + let abcCacheFilePath: string = tempFilePathWithoutExt + EXTNAME_PROTO_BIN; + abcCacheFilesInfo += `${info.tempFilePath};${abcCacheFilePath}\n`; + }); + + // generate npm entries cache + let npmEntriesCacheFileWithoutExt: string = npmEntriesInfoPath.substring(0, npmEntriesInfoPath.lastIndexOf('.')); + let npmEntriesCacheFilePath: string = npmEntriesCacheFileWithoutExt + EXTNAME_PROTO_BIN; + abcCacheFilesInfo += `${npmEntriesInfoPath};${npmEntriesCacheFilePath}\n`; + + fs.writeFileSync(cacheFilePath, abcCacheFilesInfo, 'utf-8'); +} + +export function generateMergedAbc(moduleInfos: Array, entryInfos: Map, outputABCPath: string) { + generateCompileFilesInfo(moduleInfos); + generateNpmEntriesInfo(entryInfos); + + const filesInfoPath: string = path.join(process.env.cachePath, FILESINFO_TXT); + const npmEntriesInfoPath: string = path.join(process.env.cachePath, NPMENTRIES_TXT); + const cacheFilePath: string = path.join(process.env.cachePath, MODULES_CACHE); + validateFilePathLength(cacheFilePath, logger); + generateAbcCacheFilesInfo(moduleInfos, npmEntriesInfoPath, cacheFilePath); + + const fileThreads = os.cpus().length < 16 ? os.cpus().length : 16; + mkdirsSync(projectConfig.buildPath); + let genAbcCmd: string = + `${initAbcEnv().join(' ')} "@${filesInfoPath}" --npm-module-entry-list "${npmEntriesInfoPath}" --output "${outputABCPath}" --file-threads "${fileThreads}"`; + + projectConfig.patch = projectConfig.patch || false; + projectConfig.enableMap = projectConfig.enableMap || false; + projectConfig.inOldSymbolTablePath = projectConfig.inOldSymbolTablePath || projectConfig.projectRootPath; + + if (projectConfig.patch) { + let oldHapSymbolTable = path.join(projectConfig.inOldSymbolTablePath, PATCH_SYMBOL_TABLE); + genAbcCmd += ` --input-symbol-table "${oldHapSymbolTable}" --generate-patch`; + } + + if (!projectConfig.enableMap) { + genAbcCmd += ` --cache-file "@${cacheFilePath}"`; + } else { + // when generating map, cache is forbiden to avoid uncomplete symbol table + let oldHapSymbolTable = path.join(projectConfig.inOldSymbolTablePath, PATCH_SYMBOL_TABLE); + genAbcCmd += ` --dump-symbol-table "${oldHapSymbolTable}"`; + } + + logger.debug('gen abc cmd is: ', genAbcCmd); + try { + if (process.env.watchMode === 'true') { + childProcess.execSync(genAbcCmd); + } else { + const child = childProcess.exec(genAbcCmd); + child.on('exit', (code: any) => { + if (code === 1) { + logger.debug(red, 'ArkTS:ERROR failed to execute es2abc', reset); + process.exit(FAIL); + } + }); + + child.on('error', (err: any) => { + logger.debug(red, err.toString(), reset); + process.exit(FAIL); + }); + + child.stderr.on('data', (data: any) => { + if (projectConfig.patch) { + let patchErr :string[] = + data.split(os.EOL).filter(line => line.includes('[Patch]') || line.includes('Error:')); + logger.error(red, patchErr.join(os.EOL), reset); + } else { + logger.error(red, data.toString(), reset); + } + }); + } + } catch (e) { + logger.debug(red, `ArkTS:ERROR failed to generate abc with filesInfo ${filesInfoPath}. Error message: ${e}`, reset); + process.env.abcCompileSuccess = 'false'; + if (process.env.watchMode !== 'true') { + process.exit(FAIL); + } + } +} diff --git a/compiler/src/interop/src/gen_module_abc.ts b/compiler/src/interop/src/gen_module_abc.ts new file mode 100644 index 0000000000000000000000000000000000000000..7934586aadd8b708eb8f2f513b14060e4fe513f9 --- /dev/null +++ b/compiler/src/interop/src/gen_module_abc.ts @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022 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 childProcess from 'child_process'; +import * as process from 'process'; +import * as fs from 'fs'; +import * as path from 'path'; +import cluster from 'cluster'; +import { logger } from './compile_info'; +import { + SUCCESS, + FAIL +} from './pre_define'; +import { + toUnixPath +} from './utils'; + +const red: string = '\u001b[31m'; +const reset: string = '\u001b[39m'; + +function js2abcByWorkers(jsonInput: string, cmd: string, workerFileName: string): Promise { + const inputPaths: any = JSON.parse(jsonInput); + // cmd `${cmd} --input-file xx --output-proto --merge-abc` + let filePath: string = path.join(process.env.cachePath, workerFileName); + let content: string = ''; + for (let i = 0; i < inputPaths.length; ++i) { + let info: any = inputPaths[i]; + const moduleType: string = info.isCommonJs ? 'commonjs' : 'esm'; + content += + `${info.tempFilePath};${info.recordName};${moduleType};${toUnixPath(info.sourceFile)};${info.packageName}`; + if (i < inputPaths.length - 1) { + content += '\n'; + } + } + fs.writeFileSync(filePath, content, 'utf-8'); + const singleCmd: string = `${cmd} --input-file "${filePath}" --output-proto --merge-abc`; + logger.debug('gen abc cmd is: ', singleCmd); + try { + childProcess.execSync(singleCmd); + } catch (e) { + logger.debug(red, `ArkTS:ERROR Failed to convert file to proto `, reset); + process.exit(FAIL); + } + + return; +} + +logger.debug('worker data is: ', JSON.stringify(process.env)); +logger.debug('gen_abc isWorker is: ', cluster.isWorker); +if (cluster.isWorker && process.env.inputs !== undefined && process.env.cmd !== undefined && + process.env.workerFileName !== undefined && process.env.cachePath !== undefined) { + logger.debug('==>worker #', cluster.worker.id, 'started!'); + js2abcByWorkers(process.env.inputs, process.env.cmd, process.env.workerFileName); + process.exit(SUCCESS); +} diff --git a/compiler/src/interop/src/hvigor_error_code/const/error_code_module.ts b/compiler/src/interop/src/hvigor_error_code/const/error_code_module.ts new file mode 100644 index 0000000000000000000000000000000000000000..210c65ee34450e45e9ad9fb4be8289202fe308ba --- /dev/null +++ b/compiler/src/interop/src/hvigor_error_code/const/error_code_module.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum ErrorCodeModule { + TSC = 0, + LINTER = 1, + UI = 2 +} diff --git a/compiler/src/interop/src/hvigor_error_code/hvigor_error_info.ts b/compiler/src/interop/src/hvigor_error_code/hvigor_error_info.ts new file mode 100644 index 0000000000000000000000000000000000000000..bb70bbae954ccb57d590ca9a7b6512cc4399058b --- /dev/null +++ b/compiler/src/interop/src/hvigor_error_code/hvigor_error_info.ts @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const ERROR_DESCRIPTION = 'ArkTS Compiler Error'; +export const ARKUI_SUBSYSTEM_CODE = '109'; // ArkUI subsystem coding +export const LINTER_SUBSYSTEM_CODE = '106'; // Linter subsystem coding +export const ERROR_TYPE_CODE = '05'; // Error Type Code +export const EXTENSION_CODE = '999'; // Extended Codes Defined by Various Subsystems + +interface MoreInfo { + cn: string; + en: string; +} + +export interface HvigorLogInfo { + code?: string; + description?: string; + cause?: string; + position?: string; + solutions?: string[]; + moreInfo?: MoreInfo; +} + +export class HvigorErrorInfo implements HvigorLogInfo { + code: string = ''; + description: string = ERROR_DESCRIPTION; + cause: string = ''; + position: string = ''; + solutions: string[] = []; + + getCode(): string { + return this.code; + } + + setCode(code: string): HvigorErrorInfo { + this.code = code; + return this; + } + + getDescription(): string { + return this.description; + } + + setDescription(description: string): HvigorErrorInfo { + this.description = description; + return this; + } + + getCause(): string { + return this.cause; + } + + setCause(cause: string): HvigorErrorInfo { + this.cause = cause; + return this; + } + + getPosition(): string { + return this.position; + } + + setPosition(position: string): HvigorErrorInfo { + this.position = position; + return this; + } + + getSolutions(): string[] { + return this.solutions; + } + + setSolutions(solutions: string[]): HvigorErrorInfo { + this.solutions = solutions; + return this; + } +} diff --git a/compiler/src/interop/src/hvigor_error_code/utils.ts b/compiler/src/interop/src/hvigor_error_code/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..a95e0a7f2ae01e3810dff51d14cc0c5d0d461cec --- /dev/null +++ b/compiler/src/interop/src/hvigor_error_code/utils.ts @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ERROR_DESCRIPTION, + HvigorErrorInfo, + HvigorLogInfo +} from './hvigor_error_info'; + +const DIAGNOSTIC_CODE_MAP: Map> = new Map([ + ['28000', { code: '10905128' }], + ['28001', { code: '10905239' }], + ['28002', { code: '10905238' }], + ['28003', { code: '10905127' }], + ['28004', { code: '10905353' }], + ['28006', { code: '10905237' }], + ['28015', { code: '10905236' }], +]); + +export function buildErrorInfoFromDiagnostic( + code: number, + positionMessage: string, + message: string +): HvigorErrorInfo | undefined { + const info: Omit = DIAGNOSTIC_CODE_MAP.get(code.toString()); + if (!info || !info.code) { + return undefined; + } + return new HvigorErrorInfo() + .setCode(info.code) + .setDescription(info.description ?? ERROR_DESCRIPTION) + .setCause(message) + .setPosition(positionMessage) + .setSolutions(info.solutions ?? []); +} \ No newline at end of file diff --git a/compiler/src/interop/src/import_whiteList.ts b/compiler/src/interop/src/import_whiteList.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ab881ba3267f117b8737f8db241e2d6c6201fca --- /dev/null +++ b/compiler/src/interop/src/import_whiteList.ts @@ -0,0 +1,1626 @@ +/* + * 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 whiteList: Set = new Set([ + 'ASTCResource', + 'AbilityComponent', + 'AbilityComponentAttribute', + 'AbstractProperty', + 'AccelerationOptions', + 'AccessibilityAction', + 'AccessibilityActionInterceptResult', + 'AccessibilityCallback', + 'AccessibilityHoverEvent', + 'AccessibilityHoverType', + 'AccessibilityOptions', + 'AccessibilityRoleType', + 'AccessibilitySamePageMode', + 'ActionSheet', + 'ActionSheetButtonOptions', + 'ActionSheetOffset', + 'ActionSheetOptions', + 'AdaptiveColor', + 'AdsBlockedDetails', + 'Affinity', + 'AlertDialog', + 'AlertDialogButtonBaseOptions', + 'AlertDialogButtonOptions', + 'AlertDialogParam', + 'AlertDialogParamWithButtons', + 'AlertDialogParamWithConfirm', + 'AlertDialogParamWithOptions', + 'AlignRuleOption', + 'Alignment', + 'AlphabetIndexer', + 'AlphabetIndexerAttribute', + 'AlphabetIndexerOptions', + 'AnchoredColorMode', + 'AnimatableArithmetic', + 'AnimatableExtend', + 'AnimateParam', + 'AnimationExtender', + 'AnimationMode', + 'AnimationRange', + 'AnimationStatus', + 'Animator', + 'AnimatorAttribute', + 'AnimatorInterface', + 'AppRotation', + 'AppStorage', + 'AppearSymbolEffect', + 'Area', + 'ArrowPointPosition', + 'ArrowPosition', + 'ArrowStyle', + 'AttributeModifier', + 'AutoCapitalizationMode', + 'AutoPlayOptions', + 'AvailableLayoutArea', + 'AvoidanceMode', + 'Axis', + 'AxisAction', + 'AxisEvent', + 'AxisModel', + 'BackgroundBlurStyleOptions', + 'BackgroundBrightnessOptions', + 'BackgroundColorStyle', + 'BackgroundEffectOptions', + 'BackgroundImageOptions', + 'Badge', + 'BadgeAttribute', + 'BadgeParam', + 'BadgeParamWithNumber', + 'BadgeParamWithString', + 'BadgePosition', + 'BadgeStyle', + 'BarGridColumnOptions', + 'BarMode', + 'BarPosition', + 'BarState', + 'BarStyle', + 'BarrierDirection', + 'BarrierStyle', + 'BaseCustomComponent', + 'BaseEvent', + 'BaseGestureEvent', + 'BaseHandlerOptions', + 'BaseShape', + 'BaseSpan', + 'BaselineOffsetStyle', + 'Bias', + 'BindOptions', + 'Blank', + 'BlankAttribute', + 'BlendApplyType', + 'BlendMode', + 'Blender', + 'BlurOptions', + 'BlurStyle', + 'BlurStyleActivePolicy', + 'BlurStyleOptions', + 'BoardStyle', + 'BorderImageOption', + 'BorderOptions', + 'BorderRadiuses', + 'BorderStyle', + 'BottomTabBarStyle', + 'BounceSymbolEffect', + 'BreakPoints', + 'BreakpointsReference', + 'Builder', + 'BuilderAttachment', + 'BuilderAttachmentInterface', + 'BuilderParam', + 'BusinessError', + 'Button', + 'ButtonAttribute', + 'ButtonConfiguration', + 'ButtonIconOptions', + 'ButtonOptions', + 'ButtonRole', + 'ButtonStyle', + 'ButtonStyleMode', + 'ButtonTriggerClickCallback', + 'ButtonType', + 'CacheMode', + 'Calendar', + 'CalendarAlign', + 'CalendarAttribute', + 'CalendarController', + 'CalendarDay', + 'CalendarDialogOptions', + 'CalendarOptions', + 'CalendarPicker', + 'CalendarPickerAttribute', + 'CalendarPickerDialog', + 'CalendarRequestedData', + 'CalendarSelectedDate', + 'Callback', + 'CallbackBuffer', + 'CallbackKind', + 'CallbackResource', + 'CallbackResourceHolder', + 'CancelButtonOptions', + 'CancelButtonStyle', + 'CancelButtonSymbolOptions', + 'Canvas', + 'CanvasAttribute', + 'CanvasDirection', + 'CanvasFillRule', + 'CanvasGradient', + 'CanvasLineCap', + 'CanvasLineJoin', + 'CanvasOptions', + 'CanvasPath', + 'CanvasPattern', + 'CanvasRenderer', + 'CanvasRenderingContext2D', + 'CanvasTextAlign', + 'CanvasTextBaseline', + 'CapsuleStyleOptions', + 'CaretOffset', + 'CaretStyle', + 'ChainAnimationOptions', + 'ChainEdgeEffect', + 'ChainStyle', + 'ChainWeightOptions', + 'CheckBoxConfiguration', + 'CheckBoxShape', + 'Checkbox', + 'CheckboxAttribute', + 'CheckboxGroup', + 'CheckboxGroupAttribute', + 'CheckboxGroupOptions', + 'CheckboxGroupResult', + 'CheckboxOptions', + 'ChildHitFilterOption', + 'ChildrenMainSize', + 'Circle', + 'CircleAttribute', + 'CircleOptions', + 'CircleShape', + 'CircleStyleOptions', + 'ClickEffect', + 'ClickEffectLevel', + 'ClickEvent', + 'ClientAuthenticationHandler', + 'CloseSwipeActionOptions', + 'Color', + 'ColorContent', + 'ColorFilter', + 'ColorMetrics', + 'ColorMode', + 'ColorStop', + 'ColoringStrategy', + 'Column', + 'ColumnAttribute', + 'ColumnOptions', + 'ColumnOptionsV2', + 'ColumnSplit', + 'ColumnSplitAttribute', + 'ColumnSplitDividerStyle', + 'CommonAttribute', + 'CommonConfiguration', + 'CommonMethod', + 'CommonProgressStyleOptions', + 'CommonShape', + 'CommonShapeMethod', + 'CommonTransition', + 'Component', + 'Component3D', + 'Component3DAttribute', + 'ComponentContent', + 'ComponentOptions', + 'ComponentRoot', + 'ComponentV2', + 'Computed', + 'ComputedBarAttribute', + 'Concurrent', + 'Configuration', + 'ConsoleMessage', + 'ConstraintSizeOptions', + 'Consume', + 'Consumer', + 'ContainerSpan', + 'ContainerSpanAttribute', + 'Content', + 'ContentClipMode', + 'ContentCoverOptions', + 'ContentDidScrollCallback', + 'ContentModifier', + 'ContentSlot', + 'ContentSlotAttribute', + 'ContentType', + 'Context', + 'ContextMenu', + 'ContextMenuAnimationOptions', + 'ContextMenuEditStateFlags', + 'ContextMenuInputFieldType', + 'ContextMenuMediaType', + 'ContextMenuOptions', + 'ContextMenuSourceType', + 'ControlSize', + 'ControllerHandler', + 'CopyEvent', + 'CopyOptions', + 'Counter', + 'CounterAttribute', + 'CrownAction', + 'CrownEvent', + 'CrownSensitivity', + 'CurrentDayStyle', + 'Curve', + 'CustomBuilder', + 'CustomComponent', + 'CustomComponentV2', + 'CustomDialog', + 'CustomDialogController', + 'CustomDialogControllerOptions', + 'CustomNodeBuilder', + 'CustomPopupOptions', + 'CustomSpan', + 'CustomSpanDrawInfo', + 'CustomSpanMeasureInfo', + 'CustomSpanMetrics', + 'CustomTheme', + 'CutEvent', + 'DataAddOperation', + 'DataChangeListener', + 'DataChangeOperation', + 'DataDeleteOperation', + 'DataExchangeOperation', + 'DataMoveOperation', + 'DataOperation', + 'DataOperationType', + 'DataPanel', + 'DataPanelAttribute', + 'DataPanelConfiguration', + 'DataPanelOptions', + 'DataPanelShadowOptions', + 'DataPanelType', + 'DataReloadOperation', + 'DataResubmissionHandler', + 'DatePicker', + 'DatePickerAttribute', + 'DatePickerDialog', + 'DatePickerDialogOptions', + 'DatePickerMode', + 'DatePickerOptions', + 'DatePickerResult', + 'DateRange', + 'DateTimeOptions', + 'DecorationStyle', + 'DecorationStyleInterface', + 'DecorationStyleResult', + 'Degree', + 'DeleteValue', + 'Deserializer', + 'DialogAlignment', + 'DialogButtonDirection', + 'DialogButtonStyle', + 'DialogDisplayMode', + 'DigitIndicator', + 'Dimension', + 'Direction', + 'DirectionalEdgesT', + 'DisableSymbolEffect', + 'DisappearSymbolEffect', + 'DismissContentCoverAction', + 'DismissContinueReason', + 'DismissDialogAction', + 'DismissFollowUpAction', + 'DismissMenuAction', + 'DismissPopupAction', + 'DismissReason', + 'DismissSheetAction', + 'DistributionType', + 'DisturbanceFieldOptions', + 'DisturbanceFieldShape', + 'Divider', + 'DividerAttribute', + 'DividerMode', + 'DividerOptions', + 'DividerStyle', + 'DividerStyleOptions', + 'DotIndicator', + 'DoubleAnimationParam', + 'DpiFollowStrategy', + 'DragBehavior', + 'DragEvent', + 'DragInteractionOptions', + 'DragItemInfo', + 'DragPointCoordinate', + 'DragPreviewLiftingScale', + 'DragPreviewMode', + 'DragPreviewOptions', + 'DragResult', + 'DraggingSizeChangeEffect', + 'DrawContext', + 'DrawModifier', + 'DrawableDescriptor', + 'DrawingCanvas', + 'DrawingColorFilter', + 'DrawingLattice', + 'DrawingRenderingContext', + 'DropOptions', + 'DynamicNode', + 'DynamicRangeMode', + 'EclipseStyleOptions', + 'Edge', + 'EdgeColors', + 'EdgeEffect', + 'EdgeEffectOptions', + 'EdgeOutlineStyles', + 'EdgeOutlineWidths', + 'EdgeStyles', + 'EdgeWidth', + 'EdgeWidths', + 'Edges', + 'EditMenuOptions', + 'EditMode', + 'EditableTextChangeValue', + 'EditableTextOnChangeCallback', + 'EffectComponent', + 'EffectComponentAttribute', + 'EffectDirection', + 'EffectEdge', + 'EffectFillStyle', + 'EffectScope', + 'EffectType', + 'Ellipse', + 'EllipseAttribute', + 'EllipseOptions', + 'EllipseShape', + 'EllipsisMode', + 'EmbeddedComponent', + 'EmbeddedComponentAttribute', + 'EmbeddedDpiFollowStrategy', + 'EmbeddedOptions', + 'EmbeddedType', + 'EmbeddedWindowModeFollowStrategy', + 'EmitterOptions', + 'EmitterParticleOptions', + 'EmitterProperty', + 'EnterKeyType', + 'Entry', + 'EntryOptions', + 'EnvPropsOptions', + 'Environment', + 'ErrorCallback', + 'Event', + 'EventEmulator', + 'EventLocationInfo', + 'EventQueryType', + 'EventResult', + 'EventTarget', + 'EventTargetInfo', + 'ExchangeIndex', + 'ExchangeKey', + 'ExpandedMenuItemOptions', + 'ExpectedFrameRateRange', + 'Extend', + 'FP', + 'FadingEdgeOptions', + 'FileSelectorMode', + 'FileSelectorParam', + 'FileSelectorResult', + 'FillMode', + 'Filter', + 'FingerInfo', + 'FinishCallbackType', + 'FirstMeaningfulPaint', + 'Flex', + 'FlexAlign', + 'FlexAttribute', + 'FlexDirection', + 'FlexOptions', + 'FlexSpaceOptions', + 'FlexWrap', + 'FlowItem', + 'FlowItemAttribute', + 'FocusAxisEvent', + 'FocusBoxStyle', + 'FocusController', + 'FocusDrawLevel', + 'FocusMovement', + 'FocusPriority', + 'FocusWrapMode', + 'FoldStatus', + 'FolderStack', + 'FolderStackAttribute', + 'FolderStackOptions', + 'Font', + 'FontInfo', + 'FontOptions', + 'FontSettingOptions', + 'FontStyle', + 'FontWeight', + 'ForEach', + 'ForEachAttribute', + 'ForegroundBlurStyleOptions', + 'ForegroundEffectOptions', + 'FormCallbackInfo', + 'FormComponent', + 'FormComponentAttribute', + 'FormDimension', + 'FormInfo', + 'FormLink', + 'FormLinkAttribute', + 'FormLinkOptions', + 'FormRenderingMode', + 'FormShape', + 'FractionStop', + 'FrameNode', + 'FrictionMotion', + 'FullScreenEnterEvent', + 'FullScreenExitHandler', + 'FullscreenInfo', + 'FunctionKey', + 'Gauge', + 'GaugeAttribute', + 'GaugeConfiguration', + 'GaugeIndicatorOptions', + 'GaugeOptions', + 'GaugeShadowOptions', + 'GeometryInfo', + 'GeometryTransitionOptions', + 'Gesture', + 'GestureControl', + 'GestureEvent', + 'GestureGroup', + 'GestureGroupGestureHandlerOptions', + 'GestureGroupHandler', + 'GestureHandler', + 'GestureInfo', + 'GestureJudgeResult', + 'GestureMask', + 'GestureMode', + 'GestureModifier', + 'GesturePriority', + 'GestureRecognizer', + 'GestureRecognizerJudgeBeginCallback', + 'GestureRecognizerState', + 'GestureStyle', + 'GestureType', + 'GetItemMainSizeByIndex', + 'GradientDirection', + 'Grid', + 'GridAttribute', + 'GridCol', + 'GridColAttribute', + 'GridColColumnOption', + 'GridColOptions', + 'GridContainer', + 'GridContainerAttribute', + 'GridContainerOptions', + 'GridDirection', + 'GridItem', + 'GridItemAlignment', + 'GridItemAttribute', + 'GridItemOptions', + 'GridItemStyle', + 'GridLayoutOptions', + 'GridRow', + 'GridRowAttribute', + 'GridRowColumnOption', + 'GridRowDirection', + 'GridRowOptions', + 'GridRowSizeOption', + 'GuideLinePosition', + 'GuideLineStyle', + 'GutterOption', + 'HapticFeedbackMode', + 'Header', + 'HeightBreakpoint', + 'HierarchicalSymbolEffect', + 'HistoricalPoint', + 'HitTestMode', + 'HitTestType', + 'HorizontalAlign', + 'HoverCallback', + 'HoverEffect', + 'HoverEvent', + 'HoverEventParam', + 'HoverModeAreaType', + 'HttpAuthHandler', + 'Hyperlink', + 'HyperlinkAttribute', + 'ICurve', + 'IDataSource', + 'IMonitor', + 'IMonitorValue', + 'IPropertySubscriber', + 'ISinglePropertyChangeSubscriber', + 'IconOptions', + 'IlluminatedType', + 'Image', + 'ImageAIOptions', + 'ImageAnalyzerConfig', + 'ImageAnalyzerController', + 'ImageAnalyzerType', + 'ImageAnimator', + 'ImageAnimatorAttribute', + 'ImageAttachment', + 'ImageAttachmentInterface', + 'ImageAttachmentLayoutStyle', + 'ImageAttribute', + 'ImageBitmap', + 'ImageCompleteCallback', + 'ImageContent', + 'ImageData', + 'ImageError', + 'ImageErrorCallback', + 'ImageFit', + 'ImageFrameInfo', + 'ImageInterpolation', + 'ImageLoadResult', + 'ImageModifier', + 'ImageParticleParameters', + 'ImageRenderMode', + 'ImageRepeat', + 'ImageRotateOrientation', + 'ImageSize', + 'ImageSmoothingQuality', + 'ImageSourceSize', + 'ImageSpan', + 'ImageSpanAlignment', + 'ImageSpanAttribute', + 'IndexerAlign', + 'Indicator', + 'IndicatorComponent', + 'IndicatorComponentAttribute', + 'IndicatorComponentController', + 'IndicatorStyle', + 'InputCounterOptions', + 'InputType', + 'InsertValue', + 'IntelligentTrackingPreventionDetails', + 'IntentionCode', + 'InteractionHand', + 'InterceptionModeCallback', + 'InterceptionShowCallback', + 'Interop', + 'InvertOptions', + 'IsolatedComponent', + 'IsolatedComponentAttribute', + 'IsolatedOptions', + 'ItemAlign', + 'ItemDragEventHandler', + 'ItemDragInfo', + 'ItemState', + 'JavaScriptProxy', + 'JsGeolocation', + 'JsResult', + 'KVMContext', + 'KeyEvent', + 'KeyProcessingMode', + 'KeySource', + 'KeyType', + 'KeyboardAppearance', + 'KeyboardAvoidMode', + 'KeyboardOptions', + 'KeyframeAnimateParam', + 'KeyframeState', + 'LPX', + 'LabelStyle', + 'LargestContentfulPaint', + 'LaunchMode', + 'LayoutBorderInfo', + 'LayoutChild', + 'LayoutDirection', + 'LayoutInfo', + 'LayoutManager', + 'LayoutMode', + 'LayoutPolicy', + 'LayoutSafeAreaEdge', + 'LayoutSafeAreaType', + 'LayoutStyle', + 'Layoutable', + 'LazyForEach', + 'LazyForEachAttribute', + 'LazyForEachOps', + 'LazyGridLayoutAttribute', + 'LazyVGridLayout', + 'LazyVGridLayoutAttribute', + 'LeadingMarginPlaceholder', + 'Length', + 'LengthConstrain', + 'LengthMetrics', + 'LengthMetricsUnit', + 'LengthUnit', + 'LetterSpacingStyle', + 'LightSource', + 'Line', + 'LineAttribute', + 'LineBreakStrategy', + 'LineCapStyle', + 'LineHeightStyle', + 'LineJoinStyle', + 'LineMetrics', + 'LineOptions', + 'LineSpacingOptions', + 'LinearGradient', + 'LinearGradientBlurOptions', + 'LinearGradientOptions', + 'LinearIndicator', + 'LinearIndicatorAttribute', + 'LinearIndicatorController', + 'LinearIndicatorStartOptions', + 'LinearIndicatorStyle', + 'LinearStyleOptions', + 'Link', + 'List', + 'ListAttribute', + 'ListDividerOptions', + 'ListItem', + 'ListItemAlign', + 'ListItemAttribute', + 'ListItemGroup', + 'ListItemGroupArea', + 'ListItemGroupAttribute', + 'ListItemGroupOptions', + 'ListItemGroupStyle', + 'ListItemOptions', + 'ListItemStyle', + 'ListOptions', + 'ListScroller', + 'LoadCommittedDetails', + 'Loader', + 'LoadingProgress', + 'LoadingProgressAttribute', + 'LoadingProgressConfiguration', + 'LoadingProgressStyle', + 'Local', + 'LocalBuilder', + 'LocalStorage', + 'LocalStorageLink', + 'LocalStorageProp', + 'LocalizedAlignRuleOptions', + 'LocalizedAlignment', + 'LocalizedBarrierDirection', + 'LocalizedBarrierStyle', + 'LocalizedBorderRadiuses', + 'LocalizedDragPointCoordinate', + 'LocalizedEdgeColors', + 'LocalizedEdgeWidths', + 'LocalizedEdges', + 'LocalizedHorizontalAlignParam', + 'LocalizedMargin', + 'LocalizedPadding', + 'LocalizedPosition', + 'LocalizedVerticalAlignParam', + 'LocationButton', + 'LocationButtonAttribute', + 'LocationButtonOnClickResult', + 'LocationButtonOptions', + 'LocationDescription', + 'LocationIconStyle', + 'LongPressGesture', + 'LongPressGestureEvent', + 'LongPressGestureHandler', + 'LongPressGestureHandlerOptions', + 'LongPressGestureParams', + 'LongPressRecognizer', + 'LunarSwitchStyle', + 'Margin', + 'MarkStyle', + 'Marquee', + 'MarqueeAttribute', + 'MarqueeOptions', + 'MarqueeStartPolicy', + 'MarqueeState', + 'MarqueeUpdateStrategy', + 'Materialized', + 'Matrix2D', + 'MaxLinesMode', + 'MaxLinesOptions', + 'Measurable', + 'MeasureOptions', + 'MeasureResult', + 'MediaCachedImage', + 'MediaCachedImageAttribute', + 'Memo', + 'Menu', + 'MenuAlignType', + 'MenuAttribute', + 'MenuElement', + 'MenuItem', + 'MenuItemAttribute', + 'MenuItemConfiguration', + 'MenuItemGroup', + 'MenuItemGroupAttribute', + 'MenuItemGroupOptions', + 'MenuItemOptions', + 'MenuItemOptionsV2', + 'MenuMaskType', + 'MenuOnAppearCallback', + 'MenuOptions', + 'MenuOutlineOptions', + 'MenuPolicy', + 'MenuPreviewMode', + 'MenuType', + 'MessageLevel', + 'MixedMode', + 'ModalMode', + 'ModalTransition', + 'ModelType', + 'ModifierKey', + 'Monitor', + 'MonthData', + 'MoreButtonOptions', + 'MotionBlurAnchor', + 'MotionBlurOptions', + 'MotionPathOptions', + 'MouseAction', + 'MouseButton', + 'MouseEvent', + 'MoveIndex', + 'MultiShadowOptions', + 'MutableStyledString', + 'NativeEmbedDataInfo', + 'NativeEmbedInfo', + 'NativeEmbedStatus', + 'NativeEmbedTouchInfo', + 'NativeEmbedVisibilityInfo', + 'NativeMediaPlayerConfig', + 'NativeXComponentParameters', + 'NavBar', + 'NavBarPosition', + 'NavContentInfo', + 'NavDestination', + 'NavDestinationActiveReason', + 'NavDestinationAttribute', + 'NavDestinationCommonTitle', + 'NavDestinationContext', + 'NavDestinationCustomTitle', + 'NavDestinationInfo', + 'NavDestinationMode', + 'NavDestinationTransition', + 'NavExtender', + 'NavPathInfo', + 'NavPathStack', + 'NavRouteMode', + 'NavRouter', + 'NavRouterAttribute', + 'Navigation', + 'NavigationAnimatedTransition', + 'NavigationAttribute', + 'NavigationCommonTitle', + 'NavigationCustomTitle', + 'NavigationDividerStyle', + 'NavigationInfo', + 'NavigationInterception', + 'NavigationMenuItem', + 'NavigationMenuOptions', + 'NavigationMode', + 'NavigationOperation', + 'NavigationOptions', + 'NavigationSystemTransitionType', + 'NavigationTitleMode', + 'NavigationTitleOptions', + 'NavigationToolbarOptions', + 'NavigationTransitionProxy', + 'NavigationType', + 'Navigator', + 'NavigatorAttribute', + 'NestedScrollInfo', + 'NestedScrollMode', + 'NestedScrollOptions', + 'NestedScrollOptionsExt', + 'Node', + 'NodeContainer', + 'NodeContainerAttribute', + 'NodeController', + 'NonCurrentDayStyle', + 'Nullable', + 'ObjectLink', + 'ObscuredReasons', + 'Observed', + 'ObservedV2', + 'OffscreenCanvas', + 'OffscreenCanvasRenderingContext2D', + 'Offset', + 'OffsetOptions', + 'OffsetResult', + 'OnAdsBlockedCallback', + 'OnAlertEvent', + 'OnAlphabetIndexerPopupSelectCallback', + 'OnAlphabetIndexerRequestPopupDataCallback', + 'OnAlphabetIndexerSelectCallback', + 'OnAudioStateChangedEvent', + 'OnBeforeUnloadEvent', + 'OnCheckboxChangeCallback', + 'OnCheckboxGroupChangeCallback', + 'OnClientAuthenticationEvent', + 'OnConfirmEvent', + 'OnConsoleEvent', + 'OnContentScrollCallback', + 'OnContextMenuHideCallback', + 'OnContextMenuShowEvent', + 'OnDataResubmittedEvent', + 'OnDidChangeCallback', + 'OnDownloadStartEvent', + 'OnErrorReceiveEvent', + 'OnFaviconReceivedEvent', + 'OnFirstContentfulPaintEvent', + 'OnFirstMeaningfulPaintCallback', + 'OnFoldStatusChangeCallback', + 'OnFoldStatusChangeInfo', + 'OnFullScreenEnterCallback', + 'OnGeolocationShowEvent', + 'OnHoverStatusChangeCallback', + 'OnHttpAuthRequestEvent', + 'OnHttpErrorReceiveEvent', + 'OnIntelligentTrackingPreventionCallback', + 'OnInterceptRequestEvent', + 'OnLargestContentfulPaintCallback', + 'OnLinearIndicatorChangeCallback', + 'OnLoadInterceptEvent', + 'OnMoveHandler', + 'OnNativeEmbedVisibilityChangeCallback', + 'OnNativeLoadCallback', + 'OnNavigationEntryCommittedCallback', + 'OnOverScrollEvent', + 'OnOverrideUrlLoadingCallback', + 'OnPageBeginEvent', + 'OnPageEndEvent', + 'OnPageVisibleEvent', + 'OnPasteCallback', + 'OnPermissionRequestEvent', + 'OnProgressChangeEvent', + 'OnPromptEvent', + 'OnRefreshAccessedHistoryEvent', + 'OnRenderExitedEvent', + 'OnRenderProcessNotRespondingCallback', + 'OnRenderProcessRespondingCallback', + 'OnResourceLoadEvent', + 'OnSafeBrowsingCheckResultCallback', + 'OnScaleChangeEvent', + 'OnScreenCaptureRequestEvent', + 'OnScrollCallback', + 'OnScrollEdgeCallback', + 'OnScrollEvent', + 'OnScrollFrameBeginCallback', + 'OnScrollFrameBeginHandlerResult', + 'OnScrollVisibleContentChangeCallback', + 'OnSearchResultReceiveEvent', + 'OnShowFileSelectorEvent', + 'OnSslErrorEventCallback', + 'OnSslErrorEventReceiveEvent', + 'OnSubmitCallback', + 'OnSwiperAnimationEndCallback', + 'OnSwiperAnimationStartCallback', + 'OnSwiperGestureSwipeCallback', + 'OnTabsAnimationEndCallback', + 'OnTabsAnimationStartCallback', + 'OnTabsContentWillChangeCallback', + 'OnTabsGestureSwipeCallback', + 'OnTextSelectionChangeCallback', + 'OnTitleReceiveEvent', + 'OnTouchIconUrlReceivedEvent', + 'OnViewportFitChangedCallback', + 'OnWillScrollCallback', + 'OnWindowNewEvent', + 'Once', + 'OptionWidthMode', + 'Optional', + 'OutlineOptions', + 'OutlineRadiuses', + 'OutlineStyle', + 'OverScrollMode', + 'OverlayOffset', + 'OverlayOptions', + 'PX', + 'Padding', + 'PageFlipMode', + 'PageTransitionCallback', + 'PageTransitionEnter', + 'PageTransitionExit', + 'PageTransitionOptions', + 'PanDirection', + 'PanGesture', + 'PanGestureEvent', + 'PanGestureHandler', + 'PanGestureHandlerOptions', + 'PanGestureOptions', + 'PanGestureParams', + 'PanRecognizer', + 'Panel', + 'PanelAttribute', + 'PanelHeight', + 'PanelMode', + 'PanelType', + 'ParagraphStyle', + 'ParagraphStyleInterface', + 'Param', + 'Particle', + 'ParticleAnnulusRegion', + 'ParticleAttribute', + 'ParticleColorOptions', + 'ParticleColorPropertyOptions', + 'ParticleColorPropertyUpdaterConfigs', + 'ParticleColorUpdaterOptions', + 'ParticleConfigs', + 'ParticleEmitterShape', + 'ParticleOptions', + 'ParticlePropertyAnimation', + 'ParticlePropertyOptions', + 'ParticlePropertyUpdaterConfigs', + 'ParticleTuple', + 'ParticleType', + 'ParticleUpdater', + 'ParticleUpdaterOptions', + 'Particles', + 'PasswordIcon', + 'PasteButton', + 'PasteButtonAttribute', + 'PasteButtonOnClickResult', + 'PasteButtonOptions', + 'PasteDescription', + 'PasteEvent', + 'PasteEventCallback', + 'PasteIconStyle', + 'Path', + 'Path2D', + 'PathAttribute', + 'PathOptions', + 'PathShape', + 'PathShapeOptions', + 'PatternLock', + 'PatternLockAttribute', + 'PatternLockChallengeResult', + 'PatternLockController', + 'Percentage', + 'PerfMonitorActionType', + 'PerfMonitorSourceType', + 'PermissionRequest', + 'PersistPropsOptions', + 'PersistentStorage', + 'PickerBackgroundStyle', + 'PickerDialogButtonStyle', + 'PickerTextStyle', + 'PinchGesture', + 'PinchGestureEvent', + 'PinchGestureHandler', + 'PinchGestureHandlerOptions', + 'PinchGestureParams', + 'PinchRecognizer', + 'PixelMap', + 'PixelMapMock', + 'PixelRoundCalcPolicy', + 'PixelRoundMode', + 'PixelRoundPolicy', + 'PixelStretchEffectOptions', + 'PlaceholderStyle', + 'Placement', + 'PlayMode', + 'PlaybackInfo', + 'PlaybackSpeed', + 'PluginComponent', + 'PluginComponentAttribute', + 'PluginComponentOptions', + 'PluginComponentTemplate', + 'PluginErrorCallback', + 'PluginErrorData', + 'Point', + 'PointLightStyle', + 'PointParticleParameters', + 'PointerStyle', + 'Polygon', + 'PolygonAttribute', + 'PolygonOptions', + 'Polyline', + 'PolylineAttribute', + 'PolylineOptions', + 'PopInfo', + 'PopupBorderLinearGradient', + 'PopupCommonOptions', + 'PopupMaskType', + 'PopupMessageOptions', + 'PopupOptions', + 'PopupStateChangeParam', + 'Position', + 'PositionT', + 'PositionWithAffinity', + 'PosterOptions', + 'PreDragStatus', + 'PreparedInfo', + 'Preview', + 'PreviewConfiguration', + 'PreviewMenuOptions', + 'PreviewParams', + 'PreviewText', + 'Profiler', + 'Progress', + 'ProgressAttribute', + 'ProgressConfiguration', + 'ProgressMask', + 'ProgressOptions', + 'ProgressStatus', + 'ProgressStyle', + 'ProgressStyleMap', + 'ProgressStyleOptions', + 'ProgressType', + 'Prop', + 'ProtectedResourceType', + 'Provide', + 'ProvideOptions', + 'Provider', + 'PulseSymbolEffect', + 'QRCode', + 'QRCodeAttribute', + 'QuickReplaceSymbolEffect', + 'RRect', + 'RadialGradientOptions', + 'Radio', + 'RadioAttribute', + 'RadioConfiguration', + 'RadioIndicatorType', + 'RadioOptions', + 'RadioStyle', + 'Rating', + 'RatingAttribute', + 'RatingConfiguration', + 'RatingOptions', + 'RawFileDescriptor', + 'ReceiveCallback', + 'Rect', + 'RectAttribute', + 'RectHeightStyle', + 'RectOptions', + 'RectResult', + 'RectShape', + 'RectShapeOptions', + 'RectWidthStyle', + 'Rectangle', + 'Refresh', + 'RefreshAttribute', + 'RefreshOptions', + 'RefreshStatus', + 'RelateType', + 'RelativeContainer', + 'RelativeContainerAttribute', + 'RemoteWindow', + 'RemoteWindowAttribute', + 'RenderExitReason', + 'RenderFit', + 'RenderMode', + 'RenderProcessNotRespondingData', + 'RenderProcessNotRespondingReason', + 'RenderingContextSettings', + 'RepeatAttribute', + 'RepeatItem', + 'RepeatMode', + 'ReplaceSymbolEffect', + 'Require', + 'ResizableOptions', + 'ResolutionQuality', + 'Resource', + 'ResourceColor', + 'ResourceImageAttachmentOptions', + 'ResourceStr', + 'ResponseType', + 'RestrictedWorker', + 'Reusable', + 'ReusableV2', + 'ReuseOptions', + 'RichEditor', + 'RichEditorAttribute', + 'RichEditorBaseController', + 'RichEditorBuilderSpanOptions', + 'RichEditorChangeValue', + 'RichEditorController', + 'RichEditorDeleteDirection', + 'RichEditorDeleteValue', + 'RichEditorGesture', + 'RichEditorImageSpan', + 'RichEditorImageSpanOptions', + 'RichEditorImageSpanResult', + 'RichEditorImageSpanStyle', + 'RichEditorImageSpanStyleResult', + 'RichEditorInsertValue', + 'RichEditorLayoutStyle', + 'RichEditorOptions', + 'RichEditorParagraphResult', + 'RichEditorParagraphStyle', + 'RichEditorParagraphStyleOptions', + 'RichEditorRange', + 'RichEditorResponseType', + 'RichEditorSelection', + 'RichEditorSpan', + 'RichEditorSpanPosition', + 'RichEditorSpanStyleOptions', + 'RichEditorSpanType', + 'RichEditorStyledStringController', + 'RichEditorStyledStringOptions', + 'RichEditorSymbolSpanOptions', + 'RichEditorSymbolSpanStyle', + 'RichEditorSymbolSpanStyleResult', + 'RichEditorTextSpan', + 'RichEditorTextSpanOptions', + 'RichEditorTextSpanResult', + 'RichEditorTextStyle', + 'RichEditorTextStyleResult', + 'RichEditorUpdateImageSpanStyleOptions', + 'RichEditorUpdateSymbolSpanStyleOptions', + 'RichEditorUpdateTextSpanStyleOptions', + 'RichEditorUrlStyle', + 'RichText', + 'RichTextAttribute', + 'RingStyleOptions', + 'Root', + 'RootScene', + 'RootSceneAttribute', + 'RootSceneSession', + 'RotateOptions', + 'RotationGesture', + 'RotationGestureEvent', + 'RotationGestureHandler', + 'RotationGestureHandlerOptions', + 'RotationGestureParams', + 'RotationRecognizer', + 'RoundRectShapeOptions', + 'RoundedRectOptions', + 'RouteInfo', + 'RouteMapConfig', + 'RouteType', + 'RouterPageInfo', + 'Row', + 'RowAttribute', + 'RowOptions', + 'RowOptionsV2', + 'RowSplit', + 'RowSplitAttribute', + 'RuntimeType', + 'SafeAreaEdge', + 'SafeAreaType', + 'SaveButton', + 'SaveButtonAttribute', + 'SaveButtonOnClickResult', + 'SaveButtonOptions', + 'SaveDescription', + 'SaveIconStyle', + 'ScaleOptions', + 'ScaleRingStyleOptions', + 'ScaleSymbolEffect', + 'ScanEffectOptions', + 'Scene', + 'SceneOptions', + 'Screen', + 'ScreenAttribute', + 'ScreenCaptureConfig', + 'ScreenCaptureHandler', + 'ScriptItem', + 'Scroll', + 'ScrollAlign', + 'ScrollAnimationOptions', + 'ScrollAttribute', + 'ScrollBar', + 'ScrollBarAttribute', + 'ScrollBarDirection', + 'ScrollBarMargin', + 'ScrollBarOptions', + 'ScrollDirection', + 'ScrollEdgeOptions', + 'ScrollMotion', + 'ScrollOnScrollCallback', + 'ScrollOnWillScrollCallback', + 'ScrollOptions', + 'ScrollPageOptions', + 'ScrollResult', + 'ScrollSizeMode', + 'ScrollSnapAlign', + 'ScrollSnapOptions', + 'ScrollSource', + 'ScrollState', + 'ScrollToIndexOptions', + 'ScrollableBarModeOptions', + 'ScrollableCommonMethod', + 'ScrollableTargetInfo', + 'Scroller', + 'Search', + 'SearchAttribute', + 'SearchButtonOptions', + 'SearchController', + 'SearchOptions', + 'SearchSubmitCallback', + 'SearchType', + 'SectionOptions', + 'SecurityComponentLayoutDirection', + 'SecurityComponentMethod', + 'SeekMode', + 'Select', + 'SelectAttribute', + 'SelectOption', + 'SelectStatus', + 'SelectedMode', + 'SelectionMenuOptions', + 'SelectionMenuOptionsExt', + 'SelectionOptions', + 'Sendable', + 'Serializer', + 'ShadowOptions', + 'ShadowStyle', + 'ShadowType', + 'Shape', + 'ShapeAttribute', + 'ShapeSize', + 'SharedTransitionEffectType', + 'SheetDismiss', + 'SheetInfo', + 'SheetKeyboardAvoidMode', + 'SheetMode', + 'SheetOptions', + 'SheetSize', + 'SheetTitleOptions', + 'SheetType', + 'ShouldBuiltInRecognizerParallelWithCallback', + 'SideBarContainer', + 'SideBarContainerAttribute', + 'SideBarContainerType', + 'SideBarPosition', + 'Size', + 'SizeChangeCallback', + 'SizeOptions', + 'SizeResult', + 'SizeT', + 'SizeType', + 'SlideEffect', + 'SlideRange', + 'Slider', + 'SliderAttribute', + 'SliderBlockStyle', + 'SliderBlockType', + 'SliderChangeMode', + 'SliderConfiguration', + 'SliderCustomContentOptions', + 'SliderInteraction', + 'SliderOptions', + 'SliderPrefixOptions', + 'SliderShowStepOptions', + 'SliderStepItemAccessibility', + 'SliderStyle', + 'SliderSuffixOptions', + 'SliderTriggerChangeCallback', + 'SnapshotOptions', + 'SourceTool', + 'SourceType', + 'Span', + 'SpanAttribute', + 'SpanStyle', + 'SpringBackAction', + 'SpringMotion', + 'SpringProp', + 'SslError', + 'SslErrorEvent', + 'SslErrorHandler', + 'Stack', + 'StackAttribute', + 'StackOptions', + 'StarStyleOptions', + 'State', + 'StateStyles', + 'Stepper', + 'StepperAttribute', + 'StepperItem', + 'StepperItemAttribute', + 'Sticky', + 'StickyStyle', + 'Storage', + 'StorageLink', + 'StorageProp', + 'StyleOptions', + 'StyledString', + 'StyledStringChangeValue', + 'StyledStringChangedListener', + 'StyledStringController', + 'StyledStringKey', + 'StyledStringValue', + 'Styles', + 'SubMenuExpandingMode', + 'SubTabBarStyle', + 'SubmitCallback', + 'SubmitEvent', + 'SubscribaleAbstract', + 'SubscribedAbstractProperty', + 'Summary', + 'SuperscriptStyle', + 'SurfaceRect', + 'SurfaceRotationOptions', + 'SweepGradientOptions', + 'SwipeActionItem', + 'SwipeActionOptions', + 'SwipeActionState', + 'SwipeDirection', + 'SwipeEdgeEffect', + 'SwipeGesture', + 'SwipeGestureEvent', + 'SwipeGestureHandler', + 'SwipeGestureHandlerOptions', + 'SwipeGestureParams', + 'SwipeRecognizer', + 'Swiper', + 'SwiperAnimationEvent', + 'SwiperAnimationMode', + 'SwiperAttribute', + 'SwiperAutoFill', + 'SwiperContentAnimatedTransition', + 'SwiperContentTransitionProxy', + 'SwiperContentWillScrollResult', + 'SwiperController', + 'SwiperDisplayMode', + 'SwiperNestedScrollMode', + 'SwitchStyle', + 'SymbolEffect', + 'SymbolEffectStrategy', + 'SymbolGlyph', + 'SymbolGlyphAttribute', + 'SymbolGlyphModifier', + 'SymbolRenderingStrategy', + 'SymbolSpan', + 'SymbolSpanAttribute', + 'SyncedPropertyOneWay', + 'SyncedPropertyTwoWay', + 'SystemAdaptiveOptions', + 'SystemBarStyle', + 'SystemOps', + 'TabBarIconStyle', + 'TabBarOptions', + 'TabBarSymbol', + 'TabContent', + 'TabContentAnimatedTransition', + 'TabContentAttribute', + 'TabContentTransitionProxy', + 'Tabs', + 'TabsAnimationEvent', + 'TabsAttribute', + 'TabsCacheMode', + 'TabsController', + 'TabsCustomContentTransitionCallback', + 'TabsOptions', + 'Tag', + 'TapGesture', + 'TapGestureEvent', + 'TapGestureHandler', + 'TapGestureHandlerOptions', + 'TapGestureParameters', + 'TapGestureParams', + 'TapRecognizer', + 'TemplateOptions', + 'TerminationInfo', + 'Test', + 'Text', + 'TextAlign', + 'TextArea', + 'TextAreaAttribute', + 'TextAreaController', + 'TextAreaOptions', + 'TextAreaSubmitCallback', + 'TextAreaType', + 'TextAttribute', + 'TextBackgroundStyle', + 'TextBaseController', + 'TextBox', + 'TextCascadePickerRangeContent', + 'TextCase', + 'TextChangeOptions', + 'TextChangeReason', + 'TextClock', + 'TextClockAttribute', + 'TextClockConfiguration', + 'TextClockController', + 'TextClockOptions', + 'TextContentControllerBase', + 'TextContentControllerOptions', + 'TextContentStyle', + 'TextController', + 'TextDataDetectorConfig', + 'TextDataDetectorType', + 'TextDecorationOptions', + 'TextDecorationStyle', + 'TextDecorationType', + 'TextDeleteDirection', + 'TextEditControllerEx', + 'TextHeightAdaptivePolicy', + 'TextInput', + 'TextInputAttribute', + 'TextInputController', + 'TextInputOptions', + 'TextInputStyle', + 'TextMarqueeOptions', + 'TextMenuItem', + 'TextMenuItemId', + 'TextMenuOptions', + 'TextMenuShowMode', + 'TextMetrics', + 'TextModifier', + 'TextOptions', + 'TextOverflow', + 'TextOverflowOptions', + 'TextPicker', + 'TextPickerAttribute', + 'TextPickerDialog', + 'TextPickerDialogOptions', + 'TextPickerOptions', + 'TextPickerRangeContent', + 'TextPickerResult', + 'TextPickerTextStyle', + 'TextRange', + 'TextResponseType', + 'TextSelectableMode', + 'TextShadowStyle', + 'TextSpanType', + 'TextStyle', + 'TextTimer', + 'TextTimerAttribute', + 'TextTimerConfiguration', + 'TextTimerController', + 'TextTimerOptions', + 'Theme', + 'ThemeColorMode', + 'ThreatType', + 'TimePicker', + 'TimePickerAttribute', + 'TimePickerDialog', + 'TimePickerDialogOptions', + 'TimePickerFormat', + 'TimePickerOptions', + 'TimePickerResult', + 'TipsOptions', + 'TitleHeight', + 'TodayStyle', + 'Toggle', + 'ToggleAttribute', + 'ToggleConfiguration', + 'ToggleOptions', + 'ToggleType', + 'ToolBarItemAttribute', + 'ToolBarItemInterface', + 'ToolBarItemOptions', + 'ToolBarItemPlacement', + 'ToolbarItem', + 'ToolbarItemStatus', + 'TouchEvent', + 'TouchObject', + 'TouchPoint', + 'TouchResult', + 'TouchTestInfo', + 'TouchTestStrategy', + 'TouchType', + 'Trace', + 'Track', + 'TransitionEdge', + 'TransitionEffect', + 'TransitionEffects', + 'TransitionFinishCallback', + 'TransitionHierarchyStrategy', + 'TransitionOptions', + 'TransitionType', + 'TranslateOptions', + 'UICommonEvent', + 'UIContext', + 'UIExtensionComponent', + 'UIExtensionComponentAttribute', + 'UIExtensionOptions', + 'UIExtensionProxy', + 'UIGestureEvent', + 'UnderlineColor', + 'UnifiedData', + 'UniformDataType', + 'UrlStyle', + 'UserDataSpan', + 'VMContext', + 'VP', + 'VelocityOptions', + 'VerticalAlign', + 'Video', + 'VideoAttribute', + 'VideoController', + 'VideoOptions', + 'View', + 'ViewportFit', + 'ViewportRect', + 'VirtualScrollOptions', + 'Visibility', + 'VisibleAreaChangeCallback', + 'VisibleAreaEventOptions', + 'VisibleListContentInfo', + 'VisualEffect', + 'VoiceButtonOptions', + 'VoidCallback', + 'Want', + 'Watch', + 'WaterFlow', + 'WaterFlowAttribute', + 'WaterFlowLayoutMode', + 'WaterFlowOptions', + 'WaterFlowSections', + 'Web', + 'WebAttribute', + 'WebCaptureMode', + 'WebContextMenuParam', + 'WebContextMenuResult', + 'WebController', + 'WebCookie', + 'WebDarkMode', + 'WebElementType', + 'WebHeader', + 'WebKeyboardAvoidMode', + 'WebKeyboardCallback', + 'WebKeyboardCallbackInfo', + 'WebKeyboardController', + 'WebKeyboardOptions', + 'WebLayoutMode', + 'WebMediaOptions', + 'WebNavigationType', + 'WebOptions', + 'WebResourceError', + 'WebResourceRequest', + 'WebResourceResponse', + 'WebResponseType', + 'WebviewController', + 'Week', + 'WeekStyle', + 'WidthBreakpoint', + 'WindowAnimationTarget', + 'WindowModeFollowStrategy', + 'WindowScene', + 'WindowSceneAttribute', + 'WindowStatusType', + 'WithTheme', + 'WithThemeAttribute', + 'WithThemeOptions', + 'WordBreak', + 'WorkStateStyle', + 'WrappedBuilder', + 'XComponent', + 'XComponentAttribute', + 'XComponentController', + 'XComponentOptions', + 'XComponentType', + 'animateTo', + 'animateToImmediately', + 'cursorControl', + 'focusControl', + 'fp2px', + 'getContext', + 'getInspectorNodeById', + 'getInspectorNodes', + 'lpx2px', + 'postCardAction', + 'px2fp', + 'px2lpx', + 'px2vp', + 'setAppBgColor', + 'sharedTransitionOptions', + 'vp2px', +]); + +export const decoratorsWhiteList: string[] = [ + 'State', + 'Prop', + 'Link', + 'Observed', + 'Track', + 'ObjectLink', + 'StorageProp', + 'StorageLink', + 'LocalStorageProp', + 'LocalStorageLink', + 'Provide', + 'Consume', + 'Watch', + 'Require', +]; diff --git a/compiler/src/interop/src/log_message_collection.ts b/compiler/src/interop/src/log_message_collection.ts new file mode 100644 index 0000000000000000000000000000000000000000..00b2882ee007d26524735b0a2cfe5bcc202b69c9 --- /dev/null +++ b/compiler/src/interop/src/log_message_collection.ts @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ts from 'typescript'; + +import { + addLog, + LogType, + LogInfo +} from './utils'; +import { ParentType } from './process_custom_component'; +import constantDefine from './constant_define'; + +function checkLocalBuilderDecoratorCount(node: ts.Node, sourceFileNode: ts.SourceFile, checkDecoratorCount: number, log: LogInfo[]): void { + if (checkDecoratorCount > 0) { + const message: string = 'The member property or method can not be decorated by multiple decorators.'; + addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode, { code: '10905125' }); + } +} + +function checkTwoWayComputed(node: ts.PropertyAccessExpression, symbol: ts.Symbol, log: LogInfo[]): void { + if (symbol && symbol.declarations) { + symbol.declarations.forEach((declaration: ts.Declaration) => { + if (ts.isGetAccessor(declaration) && declaration.modifiers && + isTagWithDecorator(declaration.modifiers, constantDefine.COMPUTED)) { + log.push({ + type: LogType.ERROR, + message: `A property decorated by '${constantDefine.COMPUTED_DECORATOR}' cannot be used with two-bind syntax.`, + pos: node.getStart(), + code: '10905129' + }); + } + }); + } +} + +function checkComputedGetter(symbol: ts.Symbol, declaration: ts.Declaration, log: LogInfo[]): void { + if (ts.isSetAccessor(declaration) && declaration.name && ts.isIdentifier(declaration.name) && + symbol.escapedName.toString() === declaration.name.escapedText.toString()) { + log.push({ + type: LogType.ERROR, + message: `A property decorated by '${constantDefine.COMPUTED_DECORATOR}' cannot define a set method.`, + pos: declaration.getStart(), + code: '10905130' + }); + } +} + +function checkIfNeedDollarEvent(doubleExclamationCollection: string[], dollarPropertyCollection: string[], + node: ts.CallExpression, log: LogInfo[]): void { + for (const item of doubleExclamationCollection) { + if (dollarPropertyCollection.some((value: string) => value === '$' + item)) { + log.push({ + type: LogType.ERROR, + message: `When the two-way binding syntax is used, do not assign a value to '${constantDefine.EVENT_DECORATOR}'` + + ` variable '${'$' + item}' because the framework generates the default assignment.`, + pos: node.getStart(), + code: '10905358' + }); + } + } +} + +function checkIfAssignToStaticProps(node: ts.ObjectLiteralElementLike, propName: string, + staticCollection: Set, log: LogInfo[]): void { + if (staticCollection.has(propName)) { + log.push({ + type: LogType.WARN, + message: `Static property '${propName}' can not be initialized through the component constructor.`, + pos: node.getStart() + }); + } +} + +function checkNestedComponents(parentComponentType: ParentType, isRecycleChild: boolean, isReuseV2Child: boolean, + node: ts.ExpressionStatement, log: LogInfo[]): void { + if (parentComponentType === ParentType.NormalComponentV1 && isReuseV2Child) { + log.push({ + type: LogType.ERROR, + message: `A custom component decorated with @Component cannot contain child components decorated with @ReusableV2.`, + pos: node.getStart(), + code: '10905244' + }); + } + if (parentComponentType === ParentType.ReuseComponentV1 && isReuseV2Child) { + log.push({ + type: LogType.ERROR, + message: `A custom component decorated with @Reusable cannot contain child components decorated with @ReusableV2.`, + pos: node.getStart(), + code: '10905245' + }); + } + if (parentComponentType === ParentType.ReuseComponentV2 && isRecycleChild) { + log.push({ + type: LogType.ERROR, + message: `A custom component decorated with @ReusableV2 cannot contain child components decorated with @Reusable.`, + pos: node.getStart(), + code: '10905246' + }); + } + if (parentComponentType === ParentType.NormalComponentV2 && isRecycleChild) { + log.push({ + type: LogType.WARN, + message: `When a custom component is decorated with @ComponentV2 and contains a child decorated with @Reusable, ` + + `the child component will not create.`, + pos: node.getStart() + }); + } +} + +function checkIfReuseV2InRepeatTemplate(isInRepeatTemplate: boolean, isReuseV2Child: boolean, + node: ts.ExpressionStatement, log: LogInfo[]): void { + if (isInRepeatTemplate && isReuseV2Child) { + log.push({ + type: LogType.ERROR, + message: `The template attribute of the Repeat component cannot contain any custom component decorated with @ReusableV2.`, + pos: node.getStart(), + code: '10905247' + }); + } +} + +function checkUsageOfReuseAttribute(node: ts.CallExpression, isReusableV2NodeAttr: boolean, log: LogInfo[]): void { + if (!isReusableV2NodeAttr) { + log.push({ + type: LogType.ERROR, + message: `The reuse attribute is only applicable to custom components decorated with both @ComponentV2 and @ReusableV2.`, + pos: node.getStart(), + code: '10905248' + }); + } +} + +function checkUsageOfReuseIdAttribute(node: ts.CallExpression, isReusableV2NodeAttr: boolean, log: LogInfo[]): void { + if (isReusableV2NodeAttr) { + log.push({ + type: LogType.ERROR, + message: `The reuseId attribute is not applicable to custom components decorated with both @ComponentV2 and @ReusableV2.`, + pos: node.getStart(), + code: '10905249' + }); + } +} + +function isTagWithDecorator(node: ts.NodeArray, decoratorName: string): boolean { + return node.some((item: ts.Decorator) => ts.isDecorator(item) && + ts.isIdentifier(item.expression) && item.expression.escapedText.toString() === decoratorName); +} + +export default { + checkLocalBuilderDecoratorCount, + checkTwoWayComputed, + checkComputedGetter, + checkIfNeedDollarEvent, + checkIfAssignToStaticProps, + checkNestedComponents, + checkIfReuseV2InRepeatTemplate, + checkUsageOfReuseAttribute, + checkUsageOfReuseIdAttribute +}; diff --git a/compiler/src/interop/src/manage_workers.ts b/compiler/src/interop/src/manage_workers.ts new file mode 100644 index 0000000000000000000000000000000000000000..99291d8fd51bc37961585b357ab47403756df15e --- /dev/null +++ b/compiler/src/interop/src/manage_workers.ts @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022 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 path from 'path'; +import cluster from 'cluster'; +import process from 'process'; +import { + ESMODULE, + FAIL, + GEN_ABC_SCRIPT, + GEN_MODULE_ABC_SCRIPT, + JSBUNDLE +} from './pre_define'; + +if (process.env.arkEnvParams === undefined) { + process.exit(FAIL); +} + +let arkEnvParams = JSON.parse(process.env.arkEnvParams); +if (arkEnvParams.mode !== JSBUNDLE && arkEnvParams.mode !== ESMODULE) { + process.exit(FAIL); +} + +if (arkEnvParams.workerNumber !== undefined && + arkEnvParams.splittedData !== undefined && + arkEnvParams.cmdPrefix !== undefined) { + let workerNumber: number = parseInt(arkEnvParams.workerNumber); + let splittedData: Object = JSON.parse(arkEnvParams.splittedData); + let cmdPrefix: string = arkEnvParams.cmdPrefix; + + const clusterNewApiVersion: number = 16; + const currentNodeVersion: number = parseInt(process.version.split('.')[0]); + const useNewApi: boolean = currentNodeVersion >= clusterNewApiVersion; + + if ((useNewApi && cluster.isPrimary) || (!useNewApi && cluster.isMaster)) { + let genAbcScript: string = GEN_ABC_SCRIPT; + if (arkEnvParams.mode === ESMODULE) { + genAbcScript = GEN_MODULE_ABC_SCRIPT; + } + if (useNewApi) { + cluster.setupPrimary({ + exec: path.resolve(__dirname, genAbcScript) + }); + } else { + cluster.setupMaster({ + exec: path.resolve(__dirname, genAbcScript) + }); + } + + for (let i = 0; i < workerNumber; ++i) { + let workerData: Object = { + 'inputs': JSON.stringify(splittedData[i]), + 'cmd': cmdPrefix + }; + if (arkEnvParams.mode === ESMODULE) { + let sn: number = i + 1; + let workerFileName: string = `filesInfo_${sn}.txt`; + workerData.workerFileName = workerFileName; + workerData.cachePath = arkEnvParams.cachePath; + } + cluster.fork(workerData); + } + + cluster.on('exit', (worker, code, signal) => { + if (code === FAIL) { + process.exit(FAIL); + } + }); + } +} diff --git a/compiler/src/interop/src/pre_define.ts b/compiler/src/interop/src/pre_define.ts new file mode 100644 index 0000000000000000000000000000000000000000..54d3402f6817f9ed983d27ca63b2774495b52737 --- /dev/null +++ b/compiler/src/interop/src/pre_define.ts @@ -0,0 +1,647 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import path from 'path'; + +export const NATIVE_MODULE: Set = new Set( + ['system.app', 'ohos.app', 'system.router', 'system.curves', 'ohos.curves', 'system.matrix4', 'ohos.matrix4']); +export const VALIDATE_MODULE: string[] = ['application', 'util', 'screen', 'mediaquery']; +export const SYSTEM_PLUGIN: string = 'system'; +export const OHOS_PLUGIN: string = 'ohos'; +// 'arkui-x' represents cross platform related APIs, processed as 'ohos' +export const ARKUI_X_PLUGIN: string = 'arkui-x'; + +export const COMPONENT_DECORATOR_ENTRY: string = '@Entry'; +export const COMPONENT_DECORATOR_PREVIEW: string = '@Preview'; +export const COMPONENT_DECORATOR_COMPONENT: string = '@Component'; +export const COMPONENT_DECORATOR_CUSTOM_DIALOG: string = '@CustomDialog'; +export const COMPONENT_DECORATOR_REUSEABLE: string = '@Reusable'; +export const DECORATOR_REUSEABLE: string = 'Reusable'; + +export const COMPONENT_RECYCLE: string = '__Recycle__'; +export const RECYCLE_REUSE_ID: string = 'reuseId'; + +export const COMPONENT_NON_DECORATOR: string = 'regular'; +export const COMPONENT_STATE_DECORATOR: string = '@State'; +export const COMPONENT_PROP_DECORATOR: string = '@Prop'; +export const COMPONENT_LINK_DECORATOR: string = '@Link'; +export const COMPONENT_STORAGE_PROP_DECORATOR: string = '@StorageProp'; +export const COMPONENT_STORAGE_LINK_DECORATOR: string = '@StorageLink'; +export const COMPONENT_PROVIDE_DECORATOR: string = '@Provide'; +export const COMPONENT_CONSUME_DECORATOR: string = '@Consume'; +export const COMPONENT_OBJECT_LINK_DECORATOR: string = '@ObjectLink'; +export const COMPONENT_WATCH_DECORATOR: string = '@Watch'; +export const COMPONENT_BUILDERPARAM_DECORATOR: string = '@BuilderParam'; +export const COMPONENT_LOCAL_STORAGE_LINK_DECORATOR: string = '@LocalStorageLink'; +export const COMPONENT_LOCAL_STORAGE_PROP_DECORATOR: string = '@LocalStorageProp'; +export const COMPONENT_CUSTOM_DECORATOR: string = 'COMPONENT_CUSTOM_DECORATOR'; +export const COMPONENT_REQUIRE_DECORATOR: string = '@Require'; + +export const CLASS_TRACK_DECORATOR: string = 'Track'; +export const CLASS_MIN_TRACK_DECORATOR: string = 'Trace'; +export const COMPONENT_DECORATOR_COMPONENT_V2: string = '@ComponentV2'; +export const COMPONENT_DECORATOR_REUSABLE_V2: string = '@ReusableV2'; +export const DECORATOR_REUSABLE_V2: string = 'ReusableV2'; +export const REUSABLE_V2_INNER_DECORATOR: string = '__ReusableV2_Inner_Decorator__'; +export const REUSE_ATTRIBUTE: string = 'reuse'; + +export const COMPONENT_DECORATORS_PARAMS: Set = new Set([COMPONENT_CONSUME_DECORATOR, + COMPONENT_STORAGE_PROP_DECORATOR, COMPONENT_STORAGE_LINK_DECORATOR, COMPONENT_PROVIDE_DECORATOR, + COMPONENT_WATCH_DECORATOR]); +export const INNER_COMPONENT_DECORATORS: Set = new Set([COMPONENT_DECORATOR_ENTRY, + COMPONENT_DECORATOR_PREVIEW, COMPONENT_DECORATOR_COMPONENT, COMPONENT_DECORATOR_CUSTOM_DIALOG, + COMPONENT_DECORATOR_REUSEABLE, COMPONENT_DECORATOR_COMPONENT_V2, COMPONENT_DECORATOR_REUSABLE_V2]); +export const INNER_COMPONENT_MEMBER_DECORATORS: Set = new Set([COMPONENT_STATE_DECORATOR, + COMPONENT_PROP_DECORATOR, COMPONENT_LINK_DECORATOR, COMPONENT_STORAGE_PROP_DECORATOR, + COMPONENT_STORAGE_LINK_DECORATOR, COMPONENT_PROVIDE_DECORATOR, COMPONENT_CONSUME_DECORATOR, + COMPONENT_OBJECT_LINK_DECORATOR, COMPONENT_WATCH_DECORATOR, COMPONENT_BUILDERPARAM_DECORATOR, + COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, COMPONENT_LOCAL_STORAGE_PROP_DECORATOR, + COMPONENT_REQUIRE_DECORATOR]); +export const STRUCT_DECORATORS: Set = new Set([...INNER_COMPONENT_DECORATORS, + ...INNER_COMPONENT_MEMBER_DECORATORS]); + +export const COMPONENT_OBSERVED_DECORATOR: string = '@Observed'; +export const COMPONENT_OBSERVEDV2_DECORATOR: string = '@ObservedV2'; +export const OBSERVED: string = 'Observed'; +export const MIN_OBSERVED: string = 'ObservedV2'; +export const SENDABLE: string = 'Sendable'; +export const TYPE: string = 'Type'; +export const COMPONENT_BUILDER_DECORATOR: string = '@Builder'; +export const COMPONENT_LOCAL_BUILDER_DECORATOR: string = '@LocalBuilder'; +export const COMPONENT_EXTEND_DECORATOR: string = '@Extend'; +export const COMPONENT_STYLES_DECORATOR: string = '@Styles'; +export const COMPONENT_ANIMATABLE_EXTEND_DECORATOR: string = '@AnimatableExtend'; +export const COMPONENT_CONCURRENT_DECORATOR: string = '@Concurrent'; +export const COMPONENT_SENDABLE_DECORATOR: string = '@Sendable'; +export const CHECK_COMPONENT_EXTEND_DECORATOR: string = 'Extend'; +export const STRUCT_CONTEXT_METHOD_DECORATORS: Set = new Set([COMPONENT_BUILDER_DECORATOR, + COMPONENT_STYLES_DECORATOR, COMPONENT_LOCAL_BUILDER_DECORATOR]); +export const CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR: string = 'AnimatableExtend'; +export const EXTEND_DECORATORS: string[] = [ + COMPONENT_EXTEND_DECORATOR, + COMPONENT_ANIMATABLE_EXTEND_DECORATOR +]; +export const CHECK_EXTEND_DECORATORS: string[] = [ + CHECK_COMPONENT_EXTEND_DECORATOR, + CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR +]; + +export const CREATE_ANIMATABLE_PROPERTY: string = 'createAnimatableProperty'; +export const UPDATE_ANIMATABLE_PROPERTY: string = 'updateAnimatableProperty'; +export const GET_AND_PUSH_FRAME_NODE: string = 'GetAndPushFrameNode'; +export const FINISH_UPDATE_FUNC: string = 'finishUpdateFunc'; + +export const OBSERVED_PROPERTY_SIMPLE: string = 'ObservedPropertySimple'; +export const OBSERVED_PROPERTY_OBJECT: string = 'ObservedPropertyObject'; +export const SYNCHED_PROPERTY_SIMPLE_ONE_WAY: string = 'SynchedPropertySimpleOneWay'; +export const SYNCHED_PROPERTY_SIMPLE_TWO_WAY: string = 'SynchedPropertySimpleTwoWay'; +export const SYNCHED_PROPERTY_OBJECT_TWO_WAY: string = 'SynchedPropertyObjectTwoWay'; +export const SYNCHED_PROPERTY_NESED_OBJECT: string = 'SynchedPropertyNesedObject'; +export const DECORATOR_TYPE_ANY: string = 'any'; + +export const INITIALIZE_CONSUME_FUNCTION: string = 'initializeConsume'; +export const ADD_PROVIDED_VAR: string = 'addProvidedVar'; + +export const APP_STORAGE: string = 'AppStorage'; +export const APP_STORAGE_SET_AND_PROP: string = 'SetAndProp'; +export const APP_STORAGE_SET_AND_LINK: string = 'SetAndLink'; + +export const PAGE_ENTRY_FUNCTION_NAME: string = 'loadDocument'; +export const STORE_PREVIEW_COMPONENTS: string = 'storePreviewComponents'; +export const PREVIEW_COMPONENT_FUNCTION_NAME: string = 'previewComponent'; +export const GET_PREVIEW_FLAG_FUNCTION_NAME: string = 'getPreviewComponentFlag'; + +export const COMPONENT_DECORATOR_NAME_COMPONENT: string = 'Component'; +export const XCOMPONENT_SINGLE_QUOTATION: string = `'component'`; +export const XCOMPONENT_DOUBLE_QUOTATION: string = `"component"`; +export const XCOMPONENTTYPE: string = 'XComponentType'; +export const XCOMPONENTTYPE_CONTAINER: string = 'COMPONENT'; +export const COMPONENT_DECORATOR_NAME_CUSTOMDIALOG: string = 'CustomDialog'; +export const CUSTOM_DECORATOR_NAME: Set = new Set([ + COMPONENT_DECORATOR_NAME_COMPONENT, COMPONENT_DECORATOR_NAME_CUSTOMDIALOG, + DECORATOR_REUSEABLE, 'Entry', 'Preview' +]); + +export const EXTNAME_ETS: string = '.ets'; +export const NODE_MODULES: string = 'node_modules'; +export const PACKAGES: string = 'pkg_modules'; +export const INDEX_ETS: string = 'index.ets'; +export const INDEX_TS: string = 'index.ts'; +export const PACKAGE_JSON: string = 'package.json'; +export const CUSTOM_COMPONENT_DEFAULT: string = 'default'; +export const TS_WATCH_END_MSG: string = 'TS Watch End'; + +export const BASE_COMPONENT_NAME: string = 'View'; +export const STRUCT: string = 'struct'; +export const CLASS: string = 'class'; +export const COMPONENT_BUILD_FUNCTION: string = 'build'; +export const COMPONENT_RENDER_FUNCTION: string = 'render'; +export const COMPONENT_TRANSITION_FUNCTION: string = 'pageTransition'; +export const COMPONENT_TRANSITION_NAME: string = 'PageTransition'; +export const CUSTOM_COMPONENT: string = 'CustomComponent'; +export const GLOBAL_THIS_REQUIRE_NATIVE_MODULE: string = 'globalThis.requireNativeModule'; +export const GLOBAL_THIS_REQUIRE_NAPI: string = 'globalThis.requireNapi'; + +export const COMPONENT_BUTTON: string = 'Button'; +export const COMPONENT_FOREACH: string = 'ForEach'; +export const COMPONENT_LAZYFOREACH: string = 'LazyForEach'; +export const COMPONENT_REPEAT: string = 'Repeat'; +export const REPEAT_EACH: string = 'each'; +export const REPEAT_TEMPLATE: string = 'template'; +export const IS_RENDERING_IN_PROGRESS: string = 'isRenderingInProgress'; +export const FOREACH_OBSERVED_OBJECT: string = 'ObservedObject'; +export const FOREACH_GET_RAW_OBJECT: string = 'GetRawObject'; +export const COMPONENT_IF: string = 'If'; +export const COMPONENT_IF_BRANCH_ID_FUNCTION: string = 'branchId'; +export const COMPONENT_IF_UNDEFINED: string = 'undefined'; +export const GLOBAL_CONTEXT: string = 'Context'; +export const ATTRIBUTE_ANIMATION: string = 'animation'; +export const ATTRIBUTE_ANIMATETO_SET: Set = new Set(['animateTo', 'animateToImmediately']); +export const ATTRIBUTE_STATESTYLES: string = 'stateStyles'; +export const ATTRIBUTE_ID: string = 'id'; +export const ATTRIBUTE_ATTRIBUTE_MODIFIER: string = 'attributeModifier'; +export const ATTRIBUTE_CONTENT_MODIFIER: string = 'contentModifier'; +export const ATTRIBUTE_MENUITEM_CONTENT_MODIFIER: string = 'menuItemContentModifier'; +export const TRUE: string = 'true'; +export const FALSE: string = 'false'; +export const NULL: string = 'null'; +export const FOREACH_LAZYFOREACH: Set = new Set([ + COMPONENT_FOREACH, COMPONENT_LAZYFOREACH +]); + +export const COMPONENT_CONSTRUCTOR_ID: string = 'compilerAssignedUniqueChildId'; +export const COMPONENT_CONSTRUCTOR_PARENT: string = 'parent'; +export const COMPONENT_CONSTRUCTOR_PARAMS: string = 'params'; +export const COMPONENT_PARAMS_FUNCTION: string = 'paramsGenerator_'; +export const COMPONENT_PARAMS_LAMBDA_FUNCTION: string = 'paramsLambda'; +export const COMPONENT_CONSTRUCTOR_UNDEFINED: string = 'undefined'; +export const COMPONENT_CONSTRUCTOR_LOCALSTORAGE: string = 'localStorage'; +export const COMPONENT_ABOUTTOREUSEINTERNAL_FUNCTION: string = 'aboutToReuseInternal'; +export const COMPONENT_SET_AND_LINK: string = 'setAndLink'; +export const COMPONENT_SET_AND_PROP: string = 'setAndProp'; + +export const BUILD_ON: string = 'on'; +export const BUILD_OFF: string = 'off'; + +export const START: string = 'start'; +export const END: string = 'end'; + +export const COMPONENT_CREATE_FUNCTION: string = 'create'; +export const COMPONENT_CREATE_LABEL_FUNCTION: string = 'createWithLabel'; +export const COMPONENT_CREATE_CHILD_FUNCTION: string = 'createWithChild'; +export const COMPONENT_POP_FUNCTION: string = 'pop'; +export const COMPONENT_DEBUGLINE_FUNCTION: string = 'debugLine'; +export const COMPONENT_COMMON: string = '__Common__'; + +export const OBSERVE_RECYCLE_COMPONENT_CREATION: string = 'observeRecycleComponentCreation'; +export const COMPONENT_CREATE_RECYCLE: string = 'createRecycle'; +export const RECYCLE_NODE: string = 'recycleNode'; +export const ABOUT_TO_REUSE: string = 'aboutToReuse'; +export const COMPONENT_UPDATE_ELMT_ID: string = 'updateElmtId'; +export const UPDATE_RECYCLE_ELMT_ID: string = 'updateRecycleElmtId'; +export const OLD_ELMT_ID: string = 'oldElmtId'; +export const NEW_ELMT_ID: string = 'newElmtId'; +export const NAVIGATION: string = 'Navigation'; +export const NAV_DESTINATION: string = 'NavDestination'; +export const NAV_PATH_STACK: string = 'NavPathStack'; +export const CREATE_ROUTER_COMPONENT_COLLECT: Set = new Set([NAVIGATION, NAV_DESTINATION]); + +export const COMPONENT_CONSTRUCTOR_UPDATE_PARAMS: string = 'updateWithValueParams'; +export const COMPONENT_CONSTRUCTOR_DELETE_PARAMS: string = 'aboutToBeDeleted'; + +export const CREATE_GET_METHOD: string = 'get'; +export const CREATE_SET_METHOD: string = 'set'; +export const CREATE_NEWVALUE_IDENTIFIER: string = 'newValue'; +export const CREATE_CONSTRUCTOR_PARAMS: string = 'params'; +export const CREATE_CONSTRUCTOR_SUBSCRIBER_MANAGER: string = 'SubscriberManager'; +export const CREATE_CONSTRUCTOR_GET_FUNCTION: string = 'Get'; +export const CREATE_CONSTRUCTOR_DELETE_FUNCTION: string = 'delete'; +export const ABOUT_TO_BE_DELETE_FUNCTION_ID: string = 'id'; +export const COMPONENT_WATCH_FUNCTION: string = 'declareWatch'; +export const CONTEXT_STACK: string = 'contextStack'; + +export const CREATE_STATE_METHOD: string = 'createState'; +export const CREATE_PROP_METHOD: string = 'createProp'; +export const CREATE_LINK_METHOD: string = 'createLink'; +export const CREATE_OBSERVABLE_OBJECT_METHOD: string = 'createObservableObject'; + +export const CUSTOM_COMPONENT_EARLIER_CREATE_CHILD: string = 'earlierCreatedChild_'; +export const CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID: string = 'findChildById'; +export const CUSTOM_COMPONENT_NEEDS_UPDATE_FUNCTION: string = 'needsUpdate'; +export const CUSTOM_COMPONENT_MARK_STATIC_FUNCTION: string = 'markStatic'; +export const CUSTOM_COMPONENT_EXTRAINFO: string = 'extraInfo'; + +export const COMPONENT_GESTURE: string = 'Gesture'; +export const COMPONENT_GESTURE_GROUP: string = 'GestureGroup'; +export const GESTURE_ATTRIBUTE: string = 'gesture'; +export const PARALLEL_GESTURE_ATTRIBUTE: string = 'parallelGesture'; +export const PRIORITY_GESTURE_ATTRIBUTE: string = 'priorityGesture'; +export const GESTURE_ENUM_KEY: string = 'GesturePriority'; +export const GESTURE_ENUM_VALUE_HIGH: string = 'High'; +export const GESTURE_ENUM_VALUE_LOW: string = 'Low'; +export const GESTURE_ENUM_VALUE_PARALLEL: string = 'Parallel'; + +export const RESOURCE: string = '$r'; +export const RESOURCE_RAWFILE: string = '$rawfile'; +export const RESOURCE_NAME_ID: string = 'id'; +export const RESOURCE_NAME_TYPE: string = 'type'; +export const RESOURCE_NAME_PARAMS: string = 'params'; +export const RESOURCE_NAME_BUNDLE: string = 'bundleName'; +export const RESOURCE_NAME_MODULE: string = 'moduleName'; +export const RESOURCE_TYPE = { + 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 WORKERS_DIR: string = 'workers'; +export const WORKER_OBJECT: string = 'Worker'; + +export const TEST_RUNNER_DIR_SET: Set = new Set(['TestRunner', 'testrunner']); + +export const SET_CONTROLLER_METHOD: string = 'setController'; +export const SET_CONTROLLER_CTR: string = 'ctr'; +export const SET_CONTROLLER_CTR_TYPE: string = 'CustomDialogController'; +export const JS_DIALOG: string = 'jsDialog'; +export const CUSTOM_DIALOG_CONTROLLER_BUILDER: string = 'builder'; + +export const BUILDER_ATTR_NAME: string = 'builder'; +export const BUILDER_ATTR_BIND: string = 'bind'; + +export const GEOMETRY_VIEW: string = 'GeometryView'; + +export const MODULE_SHARE_PATH: string = 'src' + path.sep + 'main' + path.sep + 'ets' + path.sep + 'share'; +export const BUILD_SHARE_PATH: string = '../share'; //??? +export const MODULE_ETS_PATH: string = 'src' + path.sep + 'main' + path.sep + 'ets'; +export const MODULE_VISUAL_PATH: string = 'src' + path.sep + 'main' + path.sep + 'supervisual'; + +export const THIS: string = 'this'; +export const STYLES: string = 'Styles'; +export const VISUAL_STATE: string = 'visualState'; +export const VIEW_STACK_PROCESSOR: string = 'ViewStackProcessor'; + +export const BIND_POPUP: string = 'bindPopup'; +export const BIND_POPUP_SET: Set = new Set(['bindPopup']); +export const BIND_DRAG_SET: Set = new Set(['onDragStart', 'onItemDragStart']); +export const ALL_COMPONENTS: string = 'AllComponents'; +export const BIND_OBJECT_PROPERTY: Map> = new Map([ + ['Navigation', new Set(['title'])], + ['NavDestination', new Set(['title'])], + ['ListItem', new Set(['swipeAction'])], + ['MenuItem', new Set([COMPONENT_CREATE_FUNCTION])], + ['MenuItemGroup', new Set([COMPONENT_CREATE_FUNCTION])], + ['Refresh', new Set([COMPONENT_CREATE_FUNCTION])], + ['WaterFlow', new Set([COMPONENT_CREATE_FUNCTION])], + ['Radio', new Set([COMPONENT_CREATE_FUNCTION])], + ['Checkbox', new Set([COMPONENT_CREATE_FUNCTION])], + ['Web', new Set(['bindSelectionMenu'])], + [ALL_COMPONENTS, new Set(['bindMenu', 'bindContextMenu', 'bindSheet', 'dragPreview'])] +]); + +export const CHECKED: string = 'checked'; +export const RADIO: string = 'Radio'; +export const $$_VALUE: string = 'value'; +export const $$_CHANGE_EVENT: string = 'changeEvent'; +export const $_VALUE: string = '$value'; +export const TEXT_TIMER: string = 'TextTimer'; +export const REFRESH: string = 'Refresh'; +export const REFRESHING: string = 'refreshing'; +export const FORMAT: string = 'format'; +export const IS_COUNT_DOWN: string = 'isCountDown'; +export const COUNT: string = 'count'; +export const $$_THIS: string = '$$this'; +export const $$_NEW_VALUE: string = 'newValue'; +export const $$: string = '$$'; +export const $$_VISIBILITY: string = 'visibility'; +export const BIND_CONTENT_COVER: string = 'bindContentCover'; +export const BIND_SHEET: string = 'bindSheet'; +export const DATE_PICKER: string = 'DatePicker'; +export const TIME_PICKER: string = 'TimePicker'; +export const RATING: string = 'Rating'; +export const SEAECH: string = 'Search'; +export const CALENDAR: string = 'Calendar'; +export const MODE: string = 'mode'; +export const SHOW_SIDE_BAR: string = 'showSideBar'; +export const SIDE_BAR_WIDTH: string = 'sideBarWidth'; +export const CHECK_BOX: string = 'Checkbox'; +export const SELECT_LOW: string = 'select'; +export const CHECKBOX_GROUP: string = 'CheckboxGroup'; +export const SELECT_ALL: string = 'selectAll'; +export const SELECTED: string = 'selected'; +export const MENU_ITEM: string = 'MenuItem'; +export const PANEL: string = 'Panel'; +export const RATING_LOW: string = 'rating'; +export const VALUE: string = 'value'; +export const SIDE_BAR_CONTAINER: string = 'SideBarContainer'; +export const SLIDER: string = 'Slider'; +export const STEPPER: string = 'Stepper'; +export const INDEX: string = 'index'; +export const SWIPER: string = 'Swiper'; +export const TABS: string = 'Tabs'; +export const TEXT_AREA: string = 'TextArea'; +export const TEXT: string = 'text'; +export const SELECT: string = 'Select'; +export const TEXT_INPUT: string = 'TextInput'; +export const TEXT_PICKER: string = 'TextPicker'; +export const TOGGLE: string = 'Toggle'; +export const ALPHABET_INDEXER: string = 'AlphabetIndexer'; +export const ARC_ALPHABET_INDEXER: string = 'ArcAlphabetIndexer'; +export const IS_ON: string = 'isOn'; +export const DATE: string = 'date'; +export const GRID_ITEM: string = 'GridItem'; +export const LIST_ITEM: string = 'ListItem'; +export const UPDATE_FUNC_BY_ELMT_ID: string = 'updateFuncByElmtId'; +export const BIND_MENU: string = 'bindMenu'; +export const BIND_CONTEXT_MENU: string = 'bindContextMenu'; +export const NAV_BAR_WIDTH: string = 'navBarWidth'; +export const ARC_LIST_ITEM: string = 'ArcListItem'; + +export const DOLLAR_BLOCK_INTERFACE: Set = new Set([ + CHECK_BOX, CHECKBOX_GROUP, DATE_PICKER, TIME_PICKER, MENU_ITEM, PANEL, RATING, SIDE_BAR_CONTAINER, STEPPER, SWIPER, TABS, TEXT_PICKER, TOGGLE, SELECT, + REFRESH, CALENDAR, GRID_ITEM, LIST_ITEM, TEXT_TIMER, SEAECH, TEXT_INPUT, SLIDER, TEXT_AREA, ALPHABET_INDEXER, ARC_ALPHABET_INDEXER]); +export const STYLE_ADD_DOUBLE_DOLLAR: Set = new Set([ + BIND_POPUP, $$_VISIBILITY, BIND_CONTENT_COVER, BIND_SHEET]); +export const STYLE_ADD_DOUBLE_EXCLAMATION: Set = new Set([ + BIND_MENU, BIND_CONTEXT_MENU, BIND_POPUP, BIND_CONTENT_COVER, BIND_SHEET]); +export const PROPERTIES_ADD_DOUBLE_DOLLAR: Map> = new Map([ + [RADIO, new Set([CHECKED])], + [TEXT_TIMER, new Set([FORMAT, COUNT, IS_COUNT_DOWN])], + [REFRESH, new Set([REFRESHING])], + [CHECK_BOX, new Set([SELECT_LOW])], + [CHECKBOX_GROUP, new Set([SELECT_ALL])], + [DATE_PICKER, new Set([SELECTED])], + [TIME_PICKER, new Set([SELECTED])], + [MENU_ITEM, new Set([SELECTED])], + [PANEL, new Set([MODE])], + [RATING, new Set([RATING_LOW])], + [SEAECH, new Set([VALUE])], + [SIDE_BAR_CONTAINER, new Set([SHOW_SIDE_BAR])], + [SLIDER, new Set([VALUE])], + [STEPPER, new Set([INDEX])], + [SWIPER, new Set([INDEX])], + [TABS, new Set([INDEX])], + [TEXT_AREA, new Set([TEXT])], + [TEXT_INPUT, new Set([TEXT])], + [TEXT_PICKER, new Set([VALUE, SELECTED])], + [TOGGLE, new Set([IS_ON])], + [ALPHABET_INDEXER, new Set([SELECTED])], + [SELECT, new Set([SELECTED, VALUE])], + [CALENDAR, new Set([DATE])], + [GRID_ITEM, new Set([SELECTED])], + [LIST_ITEM, new Set([SELECTED])] +]); +export const PROPERTIES_ADD_DOUBLE_EXCLAMATION: Map> = new Map([ + [RADIO, new Set([CHECKED])], + [TEXT_TIMER, new Set([FORMAT, COUNT, IS_COUNT_DOWN])], + [REFRESH, new Set([REFRESHING])], + [CHECK_BOX, new Set([SELECT_LOW])], + [CHECKBOX_GROUP, new Set([SELECT_ALL])], + [DATE_PICKER, new Set([SELECTED])], + [TIME_PICKER, new Set([SELECTED])], + [MENU_ITEM, new Set([SELECTED])], + [NAVIGATION, new Set([NAV_BAR_WIDTH])], + [PANEL, new Set([MODE])], + [RATING, new Set([RATING_LOW])], + [SEAECH, new Set([VALUE])], + [SIDE_BAR_CONTAINER, new Set([SHOW_SIDE_BAR, SIDE_BAR_WIDTH])], + [SLIDER, new Set([VALUE])], + [STEPPER, new Set([INDEX])], + [SWIPER, new Set([INDEX])], + [TABS, new Set([INDEX])], + [TEXT_AREA, new Set([TEXT])], + [TEXT_INPUT, new Set([TEXT])], + [TEXT_PICKER, new Set([VALUE, SELECTED])], + [TOGGLE, new Set([IS_ON])], + [ALPHABET_INDEXER, new Set([SELECTED])], + [ARC_ALPHABET_INDEXER, new Set([SELECTED])], + [SELECT, new Set([SELECTED, VALUE])], + [CALENDAR, new Set([DATE])], + [GRID_ITEM, new Set([SELECTED])], + [LIST_ITEM, new Set([SELECTED])] +]); + +export const CREATE_BIND_COMPONENT: Set = new Set(['ListItemGroup', REFRESH]); +export const HEADER: string = 'header'; +export const INDICATORBUILDER: string = 'indicatorBuilder'; +export const FOOTER: string = 'footer'; + +export const INTERFACE_NAME_SUFFIX: string = '_Params'; +export const OBSERVED_PROPERTY_ABSTRACT: string = 'ObservedPropertyAbstract'; + +export const SUPERVISUAL: string = './supervisual'; +export const SUPERVISUAL_SOURCEMAP_EXT: string = '.visual.js.map'; + +export const INSTANCE: string = 'Instance'; + +export const COMPONENT_TOGGLE: string = 'Toggle'; +export const TTOGGLE_CHECKBOX: string = 'Checkbox'; +export const TOGGLE_SWITCH: string = 'Switch'; + +export const FILESINFO_TXT: string = 'filesInfo.txt'; +export const NPMENTRIES_TXT: string = 'npmEntries.txt'; +export const MODULES_CACHE: string = 'modules.cache'; +export const MODULES_ABC: string = 'modules.abc'; +export const MODULELIST_JSON: string = 'moduleList.json'; +export const PREBUILDINFO_JSON: string = 'preBuildInfo.json'; +export const SOURCEMAPS_JSON: string = 'sourceMaps.json'; +export const SOURCEMAPS: string = 'sourceMaps.map'; +export const PROTO_FILESINFO_TXT: string = 'protoFilesInfo.txt'; +export const AOT_FULL: string = 'full'; +export const AOT_TYPE: string = 'type'; +export const AOT_PARTIAL: string = 'partial'; +export const AOT_PROFILE_SUFFIX: string = '.ap'; + +export const ESMODULE: string = 'esmodule'; +export const JSBUNDLE: string = 'jsbundle'; +export const ARK: string = 'ark'; +export const TEMPORARY: string = 'temporary'; +export const MAIN: string = 'main'; +export const AUXILIARY: string = 'auxiliary'; +export const ZERO: string = '0'; +export const ONE: string = '1'; +export const EXTNAME_JS: string = '.js'; +export const EXTNAME_TS: string = '.ts'; +export const EXTNAME_JS_MAP: string = '.js.map'; +export const EXTNAME_TS_MAP: string = '.ts.map'; +export const EXTNAME_MJS: string = '.mjs'; +export const EXTNAME_CJS: string = '.cjs'; +export const EXTNAME_D_TS: string = '.d.ts'; +export const EXTNAME_D_ETS: string = '.d.ets'; +export const EXTNAME_JSON: string = '.json'; +export const EXTNAME_ABC: string = '.abc'; +export const EXTNAME_PROTO_BIN: string = '.protoBin'; +export const PATCH_SYMBOL_TABLE: string = 'symbol.txt'; +export const MANAGE_WORKERS_SCRIPT: string = 'manage_workers.js'; +export const GEN_ABC_SCRIPT: string = 'gen_abc.js'; +export const GEN_MODULE_ABC_SCRIPT: string = 'gen_module_abc.js'; + +export const SUCCESS: number = 0; +export const FAIL: number = 1; + +export const TS2ABC: string = 'ts2abc'; +export const ES2ABC: string = 'es2abc'; + +export const MAX_WORKER_NUMBER: number = 3; + +export const GENERATE_ID = 'generateId'; +export const _GENERATE_ID = '__generate__Id'; + +export const COMPONENT_CONSTRUCTOR_INITIAL_PARAMS: string = 'setInitiallyProvidedValue'; +export const COMPONENT_UPDATE_STATE_VARS: string = 'updateStateVars'; +export const COMPONENT_RERENDER_FUNCTION: string = 'rerender'; +export const COMPONENT_CONSTRUCTOR_PURGE_VARIABLE_DEP: string = 'purgeVariableDependenciesOnElmtId'; +export const MARKDEPENDENTELEMENTSDIRTY: string = 'markDependentElementsDirty'; +export const ABOUT_TO_BE_DELETE_FUNCTION_ID__: string = 'id__'; +export const RMELMTID: string = 'rmElmtId'; +export const PURGEDEPENDENCYONELMTID: string = 'purgeDependencyOnElmtId'; +export const SETPROPERTYUNCHANGED: string = 'SetPropertyUnchanged'; +export const ABOUTTOBEDELETEDINTERNAL: string = 'aboutToBeDeletedInternal'; +export const UPDATEDIRTYELEMENTS: string = 'updateDirtyElements'; +export const BASICDECORATORS: Set = new Set([COMPONENT_STATE_DECORATOR, COMPONENT_PROP_DECORATOR, + COMPONENT_LINK_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR, COMPONENT_PROVIDE_DECORATOR, COMPONENT_CONSUME_DECORATOR, + COMPONENT_STORAGE_LINK_DECORATOR, COMPONENT_STORAGE_PROP_DECORATOR, COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, + COMPONENT_LOCAL_STORAGE_PROP_DECORATOR]); +export const LINKS_DECORATORS: Set = new Set([COMPONENT_LINK_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]); +export const ISINITIALRENDER: string = 'isInitialRender'; +export const ELMTID: string = 'elmtId'; +export const STARTGETACCESSRECORDINGFOR: string = 'StartGetAccessRecordingFor'; +export const STOPGETACCESSRECORDING: string = 'StopGetAccessRecording'; +export const UPDATE_STATE_VARS_OF_CHIND_BY_ELMTID: string = 'updateStateVarsOfChildByElmtId'; +export const VIEWSTACKPROCESSOR: string = 'ViewStackProcessor'; +export const OBSERVECOMPONENTCREATION: string = 'observeComponentCreation'; +export const OBSERVECOMPONENTCREATION2: string = 'observeComponentCreation2'; +export const ISLAZYCREATE: string = 'isLazyCreate'; +export const DEEPRENDERFUNCTION: string = 'deepRenderFunction'; +export const ITEMCREATION: string = 'itemCreation'; +export const ITEMCREATION2: string = 'itemCreation2'; +export const OBSERVEDSHALLOWRENDER: string = 'observedShallowRender'; +export const OBSERVEDDEEPRENDER: string = 'observedDeepRender'; +export const ItemComponents: string[] = ['ListItem', 'GridItem', 'ArcListItem']; +export const FOREACHITEMGENFUNCTION: string = 'forEachItemGenFunction'; +export const __LAZYFOREACHITEMGENFUNCTION: string = '__lazyForEachItemGenFunction'; +export const _ITEM: string = '_item'; +export const FOREACHITEMIDFUNC: string = 'forEachItemIdFunc'; +export const __LAZYFOREACHITEMIDFUNC: string = '__lazyForEachItemIdFunc'; +export const FOREACHUPDATEFUNCTION: string = 'forEachUpdateFunction'; +export const ALLOCATENEWELMETIDFORNEXTCOMPONENT: string = 'AllocateNewElmetIdForNextComponent'; +export const STATE_OBJECTLINK_DECORATORS: string[] = [COMPONENT_STATE_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]; +export const COMPONENT_INITIAL_RENDER_FUNCTION: string = 'initialRender'; +export const DECORATOR_COMPONENT_FREEZEWHENINACTIVE: string = 'freezeWhenInactive'; +export const INIT_ALLOW_COMPONENT_FREEZE: string = 'initAllowComponentFreeze'; +export const GRID_COMPONENT: string = 'Grid'; +export const GRIDITEM_COMPONENT: string = 'GridItem'; +export const WILLUSEPROXY: string = 'willUseProxy'; +export const BASE_COMPONENT_NAME_PU: string = 'ViewPU'; +export const PUV2_VIEW_BASE: string = 'PUV2ViewBase'; +export const GLOBAL_THIS: string = 'globalThis'; +export const OBSERVED_PROPERTY_SIMPLE_PU: string = 'ObservedPropertySimplePU'; +export const OBSERVED_PROPERTY_OBJECT_PU: string = 'ObservedPropertyObjectPU'; +export const SYNCHED_PROPERTY_SIMPLE_ONE_WAY_PU: string = 'SynchedPropertySimpleOneWayPU'; +export const SYNCHED_PROPERTY_OBJECT_ONE_WAY_PU: string = 'SynchedPropertyObjectOneWayPU'; +export const SYNCHED_PROPERTY_SIMPLE_TWO_WAY_PU: string = 'SynchedPropertySimpleTwoWayPU'; +export const SYNCHED_PROPERTY_OBJECT_TWO_WAY_PU: string = 'SynchedPropertyObjectTwoWayPU'; +export const SYNCHED_PROPERTY_NESED_OBJECT_PU: string = 'SynchedPropertyNesedObjectPU'; +export const OBSERVED_PROPERTY_ABSTRACT_PU: string = 'ObservedPropertyAbstractPU'; +export const COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU: string = '__localStorage'; +export const COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU: string = 'LocalStorage'; +export const IFELSEBRANCHUPDATEFUNCTION = 'ifElseBranchUpdateFunction'; +export const CREATE_STORAGE_LINK = 'createStorageLink'; +export const CREATE_STORAGE_PROP = 'createStorageProp'; +export const CREATE_LOCAL_STORAGE_LINK = 'createLocalStorageLink'; +export const CREATE_LOCAL_STORAGE_PROP = 'createLocalStorageProp'; +export const GET_ENTRYNAME: string = 'getEntryName'; +export const SUPER_ARGS: string = 'args'; +export const FINALIZE_CONSTRUCTION: string = 'finalizeConstruction'; +export const PROTOTYPE: string = 'prototype'; +export const REFLECT: string = 'Reflect'; +export const DEFINE_PROPERTY: string = 'defineProperty'; +export const BASE_CLASS: string = 'BaseClass'; +export const IS_REUSABLE_: string = 'isReusable_'; +export const GET_ATTRIBUTE: string = 'get'; + +export const CARD_ENTRY_FUNCTION_NAME: string = 'loadEtsCard'; +export const CARD_ENABLE_DECORATORS: Set = new Set([ + '@StorageLink', '@StorageProp', '@LocalStorageLink', '@LocalStorageProp' +]); +export const CARD_ENABLE_COMPONENTS: Set = new Set([ + 'AbilityComponent', 'PluginComponent', 'FormComponent', 'RemoteWindow', + 'XComponent', 'Web', 'RichText' +]); +export const TabContentAndNavDestination: Set = new Set(['TabContent', + 'NavDestination']); +export const CARD_LOG_TYPE_DECORATORS = 1; +export const CARD_LOG_TYPE_COMPONENTS = 2; +export const CARD_LOG_TYPE_IMPORT = 3; + +export const CALL = 'call'; +export const RESERT = 'reset'; + +export const TS_NOCHECK: string = '// @ts-nocheck'; + +export const BUILDER_PARAM_PROXY: string = 'makeBuilderParameterProxy'; +export const BUILDER_TYPE: string = 'BuilderType'; + +export const FUNCTION: string = 'function'; +export const NAME: string = 'name'; + +export const ROUTENAME_NODE: string = 'routeNameNode'; +export const STORAGE_NODE: string = 'storageNode'; +export const STORAGE: string = 'storage'; +export const REGISTER_NAMED_ROUTE: string = 'registerNamedRoute'; +export const ROUTE_NAME: string = 'routeName'; +export const PAGE_PATH: string = 'pagePath'; +export const PAGE_FULL_PATH: string = 'pageFullPath'; +export const IS_USER_CREATE_STACK: string = 'isUserCreateStack'; + +export const CAN_RETAKE: string = 'canRetake'; +export const DECORATOR_SUFFIX: string = '@'; +export const TRANSFORMED_MOCK_CONFIG: string = 'mock-config.json'; +export const USER_DEFINE_MOCK_CONFIG: string = 'mock-config.json5'; + +export const WRAPBUILDER_FUNCTION: string = 'wrapBuilder'; +export const WRAPPEDBUILDER_CLASS: string = 'WrappedBuilder'; +export const WRAPBUILDER_BUILDERPROP: string = 'builder'; +export const LENGTH: string = 'length'; + +export const PREVIEW: string = 'preview'; +export const TITLE: string = 'title'; + +export const IDS: string = 'ids'; +export const PUSH: string = 'push'; +export const UPDATE_LAZY_FOREACH_ELEMENTS = 'UpdateLazyForEachElements'; +export const IS_INITIAL_ITEM = 'isInitialItem'; +export const MY_IDS = 'myIds'; +export const COMPONENT_CALL: string = 'componentCall'; + +export const TS_BUILD_INFO_SUFFIX = '.tsbuildinfo'; +export const ARKTS_LINTER_BUILD_INFO_SUFFIX = 'inversedArkTsLinter.tsbuildinfo'; +export const HOT_RELOAD_BUILD_INFO_SUFFIX = 'hotReload.tsbuildinfo'; +export const WATCH_COMPILER_BUILD_INFO_SUFFIX = 'watchCompiler.tsbuildinfo'; + +export const GET_SHARED: string = 'getShared'; +export const USE_SHARED_STORAGE: string = 'useSharedStorage'; +export const ARKTS_MODULE_PREFIX: string = '@arkts'; +export const ARKTS_MODULE_NAME: string = 'arkts'; +export const COLD_RELOAD_MODE: string = 'coldReload'; +export const INTEGRATED_HSP: string = 'integratedHsp'; diff --git a/compiler/src/interop/src/pre_process.ts b/compiler/src/interop/src/pre_process.ts new file mode 100644 index 0000000000000000000000000000000000000000..d0d01b06210e131a1ebefc5c3a188d5b8e5be951 --- /dev/null +++ b/compiler/src/interop/src/pre_process.ts @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 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 { + validateUISyntax, + processSystemApi, + ReplaceResult, + sourceReplace, + componentCollection +} from './validate_ui_syntax'; +import { + LogInfo, + emitLogInfo, + storedFileInfo +} from './utils'; +import { BUILD_ON } from './pre_define'; +import { parseVisual } from './process_visual'; +import { + CUSTOM_BUILDER_METHOD, + GLOBAL_CUSTOM_BUILDER_METHOD, + INNER_CUSTOM_BUILDER_METHOD, + INNER_CUSTOM_LOCALBUILDER_METHOD +} from './component_map'; + +function preProcess(source: string): string { + process.env.compiler = BUILD_ON; + if (/\.ets$/.test(this.resourcePath)) { + storedFileInfo.setCurrentArkTsFile(); + clearCollection(); + const result: ReplaceResult = sourceReplace(source, this.resourcePath); + let newContent: string = result.content; + const log: LogInfo[] = result.log.concat(validateUISyntax(source, newContent, + this.resourcePath, this.resourceQuery)); + newContent = parseVisual(this.resourcePath, this.resourceQuery, newContent, log, source); + if (log.length) { + emitLogInfo(this, log); + } + return newContent; + } else { + return processSystemApi(source, false, this.resourcePath); + } +} + +function clearCollection(): void { + componentCollection.customComponents.clear(); + CUSTOM_BUILDER_METHOD.clear(); + INNER_CUSTOM_LOCALBUILDER_METHOD.clear(); + GLOBAL_CUSTOM_BUILDER_METHOD.clear(); + INNER_CUSTOM_BUILDER_METHOD.clear(); +} + +module.exports = preProcess; diff --git a/compiler/src/interop/src/process_component_build.ts b/compiler/src/interop/src/process_component_build.ts new file mode 100644 index 0000000000000000000000000000000000000000..89bc5be1dc67feae2300c3641237e354f5c3d4f3 --- /dev/null +++ b/compiler/src/interop/src/process_component_build.ts @@ -0,0 +1,3683 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ts from 'typescript'; +import path from 'path'; + +import { + COMPONENT_RENDER_FUNCTION, + COMPONENT_CREATE_FUNCTION, + COMPONENT_POP_FUNCTION, + COMPONENT_BUTTON, + COMPONENT_CREATE_LABEL_FUNCTION, + COMPONENT_CREATE_CHILD_FUNCTION, + COMPONENT_FOREACH, + COMPONENT_LAZYFOREACH, + IS_RENDERING_IN_PROGRESS, + FOREACH_OBSERVED_OBJECT, + FOREACH_GET_RAW_OBJECT, + COMPONENT_IF, + COMPONENT_IF_BRANCH_ID_FUNCTION, + COMPONENT_IF_UNDEFINED, + ATTRIBUTE_ANIMATION, + GLOBAL_CONTEXT, + COMPONENT_GESTURE, + COMPONENT_GESTURE_GROUP, + GESTURE_ATTRIBUTE, + PARALLEL_GESTURE_ATTRIBUTE, + PRIORITY_GESTURE_ATTRIBUTE, + GESTURE_ENUM_KEY, + GESTURE_ENUM_VALUE_HIGH, + GESTURE_ENUM_VALUE_LOW, + GESTURE_ENUM_VALUE_PARALLEL, + COMPONENT_TRANSITION_NAME, + COMPONENT_DEBUGLINE_FUNCTION, + ATTRIBUTE_STATESTYLES, + THIS, + VISUAL_STATE, + VIEW_STACK_PROCESSOR, + STYLE_ADD_DOUBLE_DOLLAR, + STYLE_ADD_DOUBLE_EXCLAMATION, + $$_VALUE, + $$_CHANGE_EVENT, + $$_THIS, + $$_NEW_VALUE, + $_VALUE, + BUILDER_ATTR_NAME, + BUILDER_ATTR_BIND, + CUSTOM_DIALOG_CONTROLLER_BUILDER, + BIND_DRAG_SET, + BIND_POPUP_SET, + BIND_POPUP, + CUSTOM_COMPONENT_DEFAULT, + $$, + PROPERTIES_ADD_DOUBLE_DOLLAR, + PROPERTIES_ADD_DOUBLE_EXCLAMATION, + ATTRIBUTE_ID, + RESOURCE, + ISINITIALRENDER, + ELMTID, + VIEWSTACKPROCESSOR, + STOPGETACCESSRECORDING, + STARTGETACCESSRECORDINGFOR, + OBSERVECOMPONENTCREATION, + OBSERVECOMPONENTCREATION2, + DEEPRENDERFUNCTION, + ITEMCREATION, + ITEMCREATION2, + OBSERVEDDEEPRENDER, + ItemComponents, + FOREACHITEMGENFUNCTION, + __LAZYFOREACHITEMGENFUNCTION, + _ITEM, + FOREACHITEMIDFUNC, + __LAZYFOREACHITEMIDFUNC, + FOREACHUPDATEFUNCTION, + COMPONENT_INITIAL_RENDER_FUNCTION, + COMPONENT_REPEAT, + REPEAT_EACH, + REPEAT_TEMPLATE, + LIST_ITEM, + IFELSEBRANCHUPDATEFUNCTION, + CARD_ENABLE_COMPONENTS, + CARD_LOG_TYPE_COMPONENTS, + COMPONENT_CONSTRUCTOR_PARENT, + RESOURCE_NAME_TYPE, + XCOMPONENT_SINGLE_QUOTATION, + XCOMPONENT_DOUBLE_QUOTATION, + XCOMPONENTTYPE, + XCOMPONENTTYPE_CONTAINER, + BIND_OBJECT_PROPERTY, + TRUE, + FALSE, + HEADER, + INDICATORBUILDER, + FOOTER, + CALL, + CREATE_BIND_COMPONENT, + TabContentAndNavDestination, + START, + END, + BUILDER_PARAM_PROXY, + BUILDER_TYPE, + CHECK_COMPONENT_EXTEND_DECORATOR, + CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR, + RECYCLE_REUSE_ID, + UPDATE_FUNC_BY_ELMT_ID, + CREATE_SET_METHOD, + CAN_RETAKE, + PREVIEW, + IDS, + PUSH, + UPDATE_LAZY_FOREACH_ELEMENTS, + INDEX, + IS_INITIAL_ITEM, + MY_IDS, + WRAPBUILDER_BUILDERPROP, + WRAPPEDBUILDER_CLASS, + ALL_COMPONENTS, + ATTRIBUTE_ATTRIBUTE_MODIFIER, + ATTRIBUTE_CONTENT_MODIFIER, + ATTRIBUTE_MENUITEM_CONTENT_MODIFIER, + TITLE, + PAGE_PATH, + RESOURCE_NAME_MODULE, + NAV_DESTINATION, + NAVIGATION, + CREATE_ROUTER_COMPONENT_COLLECT, + NAV_PATH_STACK, + IS_USER_CREATE_STACK, + REUSE_ATTRIBUTE +} from './pre_define'; +import { + INNER_COMPONENT_NAMES, + BUILDIN_CONTAINER_COMPONENT, + BUILDIN_STYLE_NAMES, + CUSTOM_BUILDER_METHOD, + GESTURE_ATTRS, + GESTURE_TYPE_NAMES, + EXTEND_ATTRIBUTE, + NO_DEBUG_LINE_COMPONENT, + NEEDPOP_COMPONENT, + INNER_STYLE_FUNCTION, + GLOBAL_STYLE_FUNCTION, + CUSTOM_BUILDER_PROPERTIES, + CUSTOM_BUILDER_PROPERTIES_WITHOUTKEY, + CUSTOM_BUILDER_CONSTRUCTORS, + ID_ATTRS, + SPECIFIC_PARENT_COMPONENT, + STYLES_ATTRIBUTE, + INNER_CUSTOM_LOCALBUILDER_METHOD, + COMMON_ATTRS +} from './component_map'; +import { + componentCollection, + builderParamObjectCollection, + checkAllNode, + enumCollection +} from './validate_ui_syntax'; +import { + processCustomComponent, + createConditionParent, + isRecycle, + isReuseInV2 +} from './process_custom_component'; +import { + LogType, + LogInfo, + componentInfo, + storedFileInfo +} from './utils'; +import { + globalProgram, + partialUpdateConfig, + projectConfig +} from '../main'; +import { + transformLog, + contextGlobal, + validatorCard, + builderTypeParameter, + resourceFileName +} from './process_ui_syntax'; +import { regularCollection, getSymbolIfAliased } from './validate_ui_syntax'; +import { contextStackPushOrPop } from './process_component_class'; +import processStructComponentV2, { StructInfo } from './process_struct_componentV2'; +import logMessageCollection from './log_message_collection'; + +export function processComponentBuild(node: ts.MethodDeclaration, + log: LogInfo[]): ts.MethodDeclaration { + let newNode: ts.MethodDeclaration; + let renderNode: ts.Identifier; + if (!partialUpdateConfig.partialUpdateMode) { + renderNode = ts.factory.createIdentifier(COMPONENT_RENDER_FUNCTION); + } else { + renderNode = ts.factory.createIdentifier(COMPONENT_INITIAL_RENDER_FUNCTION); + } + if (node.body && node.body.statements && node.body.statements.length && + validateRootNode(node, log)) { + const componentBlock: ts.Block = processComponentBlock(node.body, false, log); + newNode = ts.factory.updateMethodDeclaration(node, ts.getModifiers(node), + node.asteriskToken, renderNode, node.questionToken, node.typeParameters, node.parameters, + node.type, componentBlock); + if (partialUpdateConfig.partialUpdateMode && storedFileInfo.hasLocalBuilderInFile) { + componentBlock.statements.unshift(contextStackPushOrPop(ts.factory.createIdentifier(PUSH), [ts.factory.createThis()])); + componentBlock.statements.push(contextStackPushOrPop(ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), [])); + } + } else { + newNode = ts.factory.updateMethodDeclaration(node, ts.getModifiers(node), + node.asteriskToken, renderNode, node.questionToken, node.typeParameters, node.parameters, + node.type, node.body); + } + return newNode; +} + +function createLazyForEachBlockNode(newStatements: ts.Statement[]): ts.IfStatement { + return ts.factory.createIfStatement( + ts.factory.createBinaryExpression( + ts.factory.createBinaryExpression( + ts.factory.createIdentifier(IS_INITIAL_ITEM), + ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED) + ), + ts.factory.createToken(ts.SyntaxKind.BarBarToken), + ts.factory.createIdentifier(IS_INITIAL_ITEM) + ), + ts.factory.createBlock(newStatements, true), + ts.factory.createBlock([ + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(UPDATE_LAZY_FOREACH_ELEMENTS) + ), undefined, [ + ts.factory.createIdentifier(IDS), + storedFileInfo.lazyForEachInfo.forEachParameters.name as ts.Identifier + ] + )) + ], true) + ); +} + +export type BuilderParamsResult = { + firstParam: ts.ParameterDeclaration; +}; + +export function parseGlobalBuilderParams(parameters: ts.NodeArray, + builderParamsResult: BuilderParamsResult) : void { + if (partialUpdateConfig.partialUpdateMode && parameters.length && parameters.length === 1 && + ts.isIdentifier(parameters[0].name)) { + builderParamsResult.firstParam = parameters[0]; + } +} + +export function processComponentBlock(node: ts.Block, isLazy: boolean, log: LogInfo[], + isTransition: boolean = false, isBuilder: boolean = false, parent: string = undefined, + forEachParameters: ts.NodeArray = undefined, + isGlobalBuilder: boolean = false, builderParamsResult: BuilderParamsResult = null, + rootGlobalBuilder: boolean = false, isInRepeatTemplate: boolean = false): ts.Block { + const newStatements: ts.Statement[] = []; + processComponentChild(node, newStatements, log, {isAcceleratePreview: false, line: 0, column: 0, fileName: ''}, + isBuilder, parent, forEachParameters, isGlobalBuilder, isTransition, builderParamsResult, isInRepeatTemplate); + if (isLazy && !partialUpdateConfig.partialUpdateMode) { + newStatements.unshift(createRenderingInProgress(true)); + } + if (isTransition) { + if (!partialUpdateConfig.partialUpdateMode) { + newStatements.unshift(ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_TRANSITION_NAME), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null))); + } else { + newStatements.unshift(createComponentCreationStatement(ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_TRANSITION_NAME), + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null)), [ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_TRANSITION_NAME), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null))], COMPONENT_TRANSITION_NAME, false, isTransition)); + } + newStatements.push(ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_TRANSITION_NAME), + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); + } + if (isLazy && !partialUpdateConfig.partialUpdateMode) { + newStatements.push(createRenderingInProgress(false)); + } + if (rootGlobalBuilder && isGlobalBuilder && builderParamsResult && builderParamsResult.firstParam) { + newStatements.unshift(forkBuilderParamNode(builderParamsResult.firstParam)); + } + if (isLazy && projectConfig.optLazyForEach && storedFileInfo.processLazyForEach && + storedFileInfo.lazyForEachInfo.forEachParameters) { + return ts.factory.updateBlock(node, [ + createMyIdsNode(), + createLazyForEachBlockNode(newStatements), + ts.factory.createReturnStatement(ts.factory.createIdentifier(MY_IDS)) + ]); + } + return ts.factory.updateBlock(node, newStatements); +} + +function createMyIdsNode(): ts.Statement { + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(MY_IDS), + undefined, + undefined, + ts.factory.createArrayLiteralExpression( + [], + false + ) + )], + ts.NodeFlags.Const + ) + ); +} + +function visitComponent(node: ts.Node): void { + if (storedFileInfo.lazyForEachInfo && !ts.isBlock(node)) { + ts.forEachChild(node, (child: ts.Node) => { + if (storedFileInfo.lazyForEachInfo.isDependItem) { + return; + } + if (ts.isIdentifier(child)) { + const symbol: ts.Symbol = globalProgram.checker.getSymbolAtLocation(child); + if (symbol && symbol.valueDeclaration === storedFileInfo.lazyForEachInfo.forEachParameters) { + storedFileInfo.lazyForEachInfo.isDependItem = true; + return; + } + } + visitComponent(child); + }); + } +} + +function forkBuilderParamNode(node: ts.ParameterDeclaration): ts.Statement { + const paramNode: ts.Identifier = node.name as ts.Identifier; + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(`__${paramNode.escapedText.toString()}__`), + undefined, + undefined, + paramNode + )], + ts.NodeFlags.Const + ) + ); +} + +function validateRootNode(node: ts.MethodDeclaration, log: LogInfo[]): boolean { + let isValid: boolean = false; + if (node.body.statements.length === 1) { + const statement: ts.Statement = node.body.statements[0]; + if (ts.isIfStatement(statement) || validateFirstNode(statement)) { + isValid = true; + } + } else { + isValid = false; + } + if (!isValid) { + log.push({ + type: LogType.ERROR, + message: `There should have a root container component.`, + pos: node.body.statements.pos, + code: '10905210' + }); + } + return isValid; +} + +function validateFirstNode(node: ts.Statement): boolean { + const isEntryComponent: boolean = + componentCollection.entryComponent === componentCollection.currentClassName; + if (isEntryComponent && !validateContainerComponent(node)) { + return false; + } + return true; +} + +function validateContainerComponent(node: ts.Statement): boolean { + if (ts.isExpressionStatement(node) && node.expression && + (ts.isEtsComponentExpression(node.expression) || ts.isCallExpression(node.expression))) { + const nameResult: NameResult = { name: null, node: null, arguments: [] }; + validateEtsComponentNode(node.expression, nameResult); + if (nameResult.name && checkContainer(nameResult.name, nameResult.node)) { + return true; + } + } + return false; +} + +interface supplementType { + isAcceleratePreview: boolean, + line: number, + column: number, + fileName: string +} + +let newsupplement: supplementType = { + isAcceleratePreview: false, + line: 0, + column: 0, + fileName: '' +}; + +type NameResult = { + name: string, + arguments: ts.NodeArray | [], + node?: ts.Node +}; + +function validateEtsComponentNode(node: ts.CallExpression | ts.EtsComponentExpression, result?: NameResult) { + let childNode: ts.Node = node; + result.name = null; + while (ts.isCallExpression(childNode) && childNode.expression && + ts.isPropertyAccessExpression(childNode.expression) && childNode.expression.expression) { + childNode = childNode.expression.expression; + } + if (ts.isEtsComponentExpression(childNode)) { + if (ts.isIdentifier(childNode.expression)) { + result.name = childNode.expression.getText(); + result.node = childNode; + result.arguments = childNode.arguments || []; + } + return true; + } else { + return false; + } +} + +let sourceNode: ts.SourceFile; + +export function processComponentChild(node: ts.Block | ts.SourceFile, newStatements: ts.Statement[], + log: LogInfo[], supplement: supplementType = {isAcceleratePreview: false, line: 0, column: 0, fileName: ''}, + isBuilder: boolean = false, parent: string = undefined, + forEachParameters: ts.NodeArray = undefined, isGlobalBuilder: boolean = false, + isTransition: boolean = false, builderParamsResult: BuilderParamsResult = null, + isInRepeatTemplate: boolean = false): void { + if (supplement.isAcceleratePreview) { + newsupplement = supplement; + const compilerOptions = ts.readConfigFile( + path.resolve(__dirname, '../../../tsconfig.json'), ts.sys.readFile).config.compilerOptions; + Object.assign(compilerOptions, { + 'sourceMap': false + }); + sourceNode = ts.createSourceFile('', node.getText(), ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS, compilerOptions); + } + if (node.statements.length) { + // Save parent component + const savedParent: string = parent; + node.statements.forEach((item) => { + if (ts.isExpressionStatement(item)) { + assignParameter(forEachParameters, item); + checkEtsComponent(item, log); + const name: string = getName(item); + if (CARD_ENABLE_COMPONENTS.has(name)) { + validatorCard(log, CARD_LOG_TYPE_COMPONENTS, item.getStart(), name); + } + switch (getComponentType(item, log, name, parent, forEachParameters)) { + case ComponentType.innerComponent: { + const [etsExpression, idName]: [ts.EtsComponentExpression, ts.Expression] = + checkEtsAndIdInIf(item, savedParent); + if (ts.isIdentifier(etsExpression.expression)) { + parent = etsExpression.expression.escapedText.toString(); + } + processInnerComponent(item, newStatements, log, parent, isBuilder, isGlobalBuilder, + isTransition, idName, savedParent, builderParamsResult, isInRepeatTemplate); + break; + } + case ComponentType.customComponent: { + const idName: ts.Expression = checkIdInIf(item, savedParent); + parent = undefined; + if (!newsupplement.isAcceleratePreview) { + if (item.expression && ts.isEtsComponentExpression(item.expression) && item.expression.body) { + const expressionResult: ts.ExpressionStatement = + processExpressionStatementChange(item, item.expression.body, log); + if (expressionResult) { + item = expressionResult; + } + } + processCustomComponent(item as ts.ExpressionStatement, newStatements, log, name, + isBuilder, isGlobalBuilder, idName, builderParamsResult, isInRepeatTemplate); + } + break; + } + case ComponentType.forEachComponent: + parent = undefined; + if (!partialUpdateConfig.partialUpdateMode) { + processForEachComponent(item, newStatements, log, isBuilder, isGlobalBuilder); + } else { + processForEachComponentNew(item, newStatements, log, name, isGlobalBuilder, builderParamsResult, isInRepeatTemplate); + } + break; + case ComponentType.repeatComponent: + parent = undefined; + processRepeatComponent(item, newStatements, log, isBuilder, isGlobalBuilder, isTransition, builderParamsResult, isInRepeatTemplate); + break; + case ComponentType.customBuilderMethod: + parent = undefined; + if (partialUpdateConfig.partialUpdateMode) { + newStatements.push(transferBuilderCall(item, name, isBuilder)); + } else { + newStatements.push(addInnerBuilderParameter(item, isGlobalBuilder)); + } + break; + case ComponentType.builderParamMethod: + parent = undefined; + if (partialUpdateConfig.partialUpdateMode) { + newStatements.push(transferBuilderCall(item, name, isBuilder)); + } else { + newStatements.push(addInnerBuilderParameter(item)); + } + break; + case ComponentType.builderTypeFunction: + parent = undefined; + if (partialUpdateConfig.partialUpdateMode) { + newStatements.push(transferBuilderCall(item, name, isBuilder)); + } else { + newStatements.push(addInnerBuilderParameter(item)); + } + break; + case ComponentType.function: + parent = undefined; + newStatements.push(item); + break; + } + } else if (ts.isIfStatement(item)) { + assignParameter(forEachParameters, item); + processIfStatement(item, newStatements, log, isBuilder, isGlobalBuilder, builderParamsResult, isInRepeatTemplate); + } else if (!ts.isBlock(item)) { + log.push({ + type: LogType.ERROR, + message: `Only UI component syntax can be written in build method.`, + pos: item.getStart(), + code: '10905209' + }); + } + storedFileInfo.lazyForEachInfo.isDependItem = false; + }); + } + if (supplement.isAcceleratePreview) { + newsupplement = { + isAcceleratePreview: false, + line: 0, + column: 0, + fileName: '' + }; + } +} + +function assignParameter(forEachParameters: ts.NodeArray, item: ts.Node): void { + if (partialUpdateConfig.partialUpdateMode && projectConfig.optLazyForEach && + storedFileInfo.processLazyForEach) { + if (forEachParameters && forEachParameters[0]) { + storedFileInfo.lazyForEachInfo.forEachParameters = forEachParameters[0]; + } + if (storedFileInfo.lazyForEachInfo.forEachParameters) { + visitComponent(item); + } + } +} + +export function transferBuilderCall(node: ts.ExpressionStatement, name: string, + isBuilder: boolean = false): ts.ExpressionStatement { + if (node.expression && ts.isCallExpression(node.expression)) { + let newNode: ts.Expression = builderCallNode(node.expression); + newNode.expression.questionDotToken = node.expression.questionDotToken; + if (node.expression.arguments && node.expression.arguments.length === 1 && ts.isObjectLiteralExpression(node.expression.arguments[0])) { + return ts.factory.createExpressionStatement(ts.factory.updateCallExpression( + node.expression, + newNode, + undefined, + [ts.factory.createCallExpression( + ts.factory.createIdentifier(BUILDER_PARAM_PROXY), + undefined, + [ + ts.factory.createStringLiteral(name), + traverseBuilderParams(node.expression.arguments[0], isBuilder) + ] + )] + )); + } else { + return ts.factory.createExpressionStatement(ts.factory.updateCallExpression( + node.expression, + newNode, + undefined, + !(projectConfig.optLazyForEach && (storedFileInfo.processLazyForEach && + storedFileInfo.lazyForEachInfo.forEachParameters || isBuilder)) ? node.expression.arguments : + [...node.expression.arguments, ts.factory.createNull(), ts.factory.createIdentifier(MY_IDS)] + )); + } + } + return undefined; +} + +function builderCallNode(node: ts.CallExpression): ts.Expression { + let newNode: ts.Expression; + if (node.expression && ts.isPropertyAccessExpression(node.expression) && + node.expression.questionDotToken && node.expression.questionDotToken.kind === ts.SyntaxKind.QuestionDotToken) { + newNode = ts.factory.createCallChain( + ts.factory.createPropertyAccessChain( + node.expression, + node.questionDotToken, + ts.factory.createIdentifier(BUILDER_ATTR_BIND) + ), + undefined, + undefined, + [ts.factory.createThis()] + ); + } else { + newNode = ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + node.expression, + ts.factory.createIdentifier(BUILDER_ATTR_BIND) + ), + undefined, + [ts.factory.createThis()] + ); + } + return newNode; +} + +function traverseBuilderParams(node: ts.ObjectLiteralExpression, + isBuilder: boolean): ts.ObjectLiteralExpression { + const properties: ts.ObjectLiteralElementLike[] = []; + if (node.properties && node.properties.length) { + node.properties.forEach(property => { + if (ts.isPropertyAssignment(property) && property.initializer && + ts.isPropertyAccessExpression(property.initializer) && property.initializer.expression && + property.initializer.name && ts.isIdentifier(property.initializer.name)) { + const name: string = property.initializer.name.escapedText.toString(); + if (!storedFileInfo.processGlobalBuilder && property.initializer.expression.kind === ts.SyntaxKind.ThisKeyword || + isBuilder && ts.isIdentifier(property.initializer.expression) && + property.initializer.expression.escapedText.toString() === $$) { + const useThis: boolean = property.initializer.expression.kind === ts.SyntaxKind.ThisKeyword; + addProperties(properties, property, name, isBuilder, useThis); + } else { + addBuilderParamsProperties(properties, property); + } + } else { + addBuilderParamsProperties(properties, property); + } + }); + } + return ts.factory.createObjectLiteralExpression(properties); +} + +function addBuilderParamsProperties(properties: ts.ObjectLiteralElementLike[], + property: ts.ObjectLiteralElementLike): void { + const initializer: ts.Expression = ts.isShorthandPropertyAssignment(property) ? + property.name : property.initializer; + properties.push(ts.factory.createPropertyAssignment( + property.name, + ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + initializer + ) + )); +} + +function addProperties(properties: ts.ObjectLiteralElementLike[], property: ts.ObjectLiteralElementLike, + name: string, isBuilder: boolean, useThis: boolean): void { + properties.push(ts.factory.createPropertyAssignment( + property.name, + ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createParenthesizedExpression(ts.factory.createConditionalExpression( + ts.factory.createElementAccessExpression( + (isBuilder && !useThis) ? ts.factory.createIdentifier($$) : ts.factory.createThis(), + ts.factory.createStringLiteral('__' + name) + ), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createElementAccessExpression( + (isBuilder && !useThis) ? ts.factory.createIdentifier($$) : ts.factory.createThis(), + ts.factory.createStringLiteral('__' + name) + ), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + ts.factory.createElementAccessExpression( + (isBuilder && !useThis) ? ts.factory.createIdentifier($$) : ts.factory.createThis(), + ts.factory.createStringLiteral(name) + ) + )) + ) + )); +} + +function addInnerBuilderParameter(node: ts.ExpressionStatement, + isGlobalBuilder: boolean = false): ts.ExpressionStatement { + if (node.expression && ts.isCallExpression(node.expression) && node.expression.arguments) { + node.expression.arguments.push(isGlobalBuilder ? parentConditionalExpression() : ts.factory.createThis()); + return ts.factory.createExpressionStatement(ts.factory.updateCallExpression(node.expression, + node.expression.expression, node.expression.typeArguments, node.expression.arguments)); + } else { + return node; + } +} + +function processExpressionStatementChange(node: ts.ExpressionStatement, nextNode: ts.Block, + log: LogInfo[]): ts.ExpressionStatement { + let name: string; + // @ts-ignore + if (node.expression.expression && ts.isIdentifier(node.expression.expression)) { + name = node.expression.expression.escapedText.toString(); + } else if (node.expression.expression && ts.isPropertyAccessExpression(node.expression.expression)) { + name = node.expression.expression.getText(); + } + if (builderParamObjectCollection.get(name) && + builderParamObjectCollection.get(name).size === 1) { + return processBlockToExpression(node, nextNode, log, name, false); + } else if (projectConfig.compileMode === 'esmodule' && process.env.compileTool === 'rollup' && + storedFileInfo.overallBuilderParamCollection.get(name) && + storedFileInfo.overallBuilderParamCollection.get(name).size === 1 + ) { + return processBlockToExpression(node, nextNode, log, name, true); + } else { + log.push({ + type: LogType.ERROR, + message: `In the trailing lambda case, '${name}' must have one and only one property decorated with ` + + '@BuilderParam, and its @BuilderParam expects no parameter.', + pos: node.getStart(), + code: '10905102' + }); + return null; + } +} + +function processBlockToExpression(node: ts.ExpressionStatement, nextNode: ts.Block, + log: LogInfo[], name: string, isPropertyAccessExpressionNode: boolean): ts.ExpressionStatement { + const childParam: string = isPropertyAccessExpressionNode ? [...storedFileInfo.overallBuilderParamCollection.get(name)].slice(-1)[0] : + [...builderParamObjectCollection.get(name)].slice(-1)[0]; + const newBlock: ts.Block = processComponentBlock(nextNode, false, log); + const arrowNode: ts.ArrowFunction = ts.factory.createArrowFunction(undefined, undefined, + [], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), newBlock); + const newPropertyAssignment:ts.PropertyAssignment = ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(childParam), arrowNode); + // @ts-ignore + let argumentsArray: ts.ObjectLiteralExpression[] = node.expression.arguments; + if (argumentsArray && !argumentsArray.length) { + argumentsArray = [ts.factory.createObjectLiteralExpression([newPropertyAssignment], true)]; + } else if (ts.isObjectLiteralExpression(argumentsArray[0]) && argumentsArray.length === 1) { + argumentsArray = [ts.factory.createObjectLiteralExpression( + // @ts-ignore + node.expression.arguments[0].properties.concat([newPropertyAssignment]), true)]; + } else if (ts.isObjectLiteralExpression(argumentsArray[0]) && argumentsArray.length === 2) { + argumentsArray = [ts.factory.createObjectLiteralExpression( + // @ts-ignore + node.expression.arguments[0].properties.concat([newPropertyAssignment]), true), argumentsArray[1]]; + } + const callNode: ts.CallExpression = ts.factory.updateCallExpression( + // @ts-ignore + node.expression, node.expression.expression, node.expression.expression.typeArguments, + argumentsArray); + // @ts-ignore + node.expression.expression.parent = callNode; + // @ts-ignore + callNode.parent = node.expression.parent; + node = ts.factory.updateExpressionStatement(node, callNode); + return node; +} + +type EtsComponentResult = { + etsComponentNode: ts.EtsComponentExpression; + hasAttr: boolean; +}; +function parseEtsComponentExpression(node: ts.ExpressionStatement): EtsComponentResult { + let etsComponentNode: ts.EtsComponentExpression; + let hasAttr: boolean = false; + let temp: any = node.expression; + while (temp) { + if (ts.isCallExpression(temp) && temp.expression && + ts.isPropertyAccessExpression(temp.expression)) { + hasAttr = true; + } + if (ts.isEtsComponentExpression(temp)) { + etsComponentNode = temp; + break; + } + temp = temp.expression; + } + return { etsComponentNode: etsComponentNode, hasAttr: hasAttr }; +} + +export function createCollectElmtIdNode(): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(MY_IDS), + ts.factory.createIdentifier(PUSH) + ), + undefined, + [ts.factory.createIdentifier(ELMTID)] + )); +} + +function processInnerComponent(node: ts.ExpressionStatement, innerCompStatements: ts.Statement[], + log: LogInfo[], parent: string = undefined, isBuilder: boolean = false, isGlobalBuilder: boolean = false, + isTransition: boolean = false, idName: ts.Expression = undefined, savedParent: string = undefined, + builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): void { + const newStatements: ts.Statement[] = []; + const res: CreateResult = createComponent(node, COMPONENT_CREATE_FUNCTION); + newStatements.push(res.newNode); + const nameResult: NameResult = { name: null, arguments: [] }; + validateEtsComponentNode(node.expression as ts.EtsComponentExpression, nameResult); + if (savedParent && nameResult.name) { + checkNonspecificParents(node, nameResult.name, savedParent, log); + } + if (partialUpdateConfig.partialUpdateMode && ItemComponents.includes(nameResult.name)) { + processItemComponent(node, nameResult, innerCompStatements, log, parent, isGlobalBuilder, idName, builderParamsResult, isInRepeatTemplate); + } else if (partialUpdateConfig.partialUpdateMode && TabContentAndNavDestination.has(nameResult.name)) { + processTabAndNav(node, innerCompStatements, nameResult, log, parent, isGlobalBuilder, idName, builderParamsResult, isInRepeatTemplate); + } else { + processNormalComponent(node, nameResult, innerCompStatements, log, parent, isBuilder, isGlobalBuilder, + isTransition, idName, builderParamsResult, isInRepeatTemplate); + } +} + +function processNormalComponent(node: ts.ExpressionStatement, nameResult: NameResult, + innerCompStatements: ts.Statement[], log: LogInfo[], parent: string = undefined, isBuilder: boolean = false, + isGlobalBuilder: boolean = false, isTransition: boolean = false, idName: ts.Expression = undefined, + builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): void { + const newStatements: ts.Statement[] = []; + if (addElmtIdNode()) { + newStatements.push(createCollectElmtIdNode()); + } + const immutableStatements: ts.Statement[] = []; + const res: CreateResult = createComponent(node, COMPONENT_CREATE_FUNCTION); + newStatements.push(res.newNode); + processDebug(node, nameResult, newStatements); + const etsComponentResult: EtsComponentResult = parseEtsComponentExpression(node); + const componentName: string = res.identifierNode.getText(); + let judgeIdStart: number; + if (partialUpdateConfig.partialUpdateMode && idName) { + judgeIdStart = innerCompStatements.length; + } + if (etsComponentResult.etsComponentNode.body && ts.isBlock(etsComponentResult.etsComponentNode.body)) { + if (res.isButton) { + checkButtonParamHasLabel(etsComponentResult.etsComponentNode, log); + if (projectConfig.isPreview || projectConfig.enableDebugLine) { + newStatements.splice(-2, 1, createComponent(node, COMPONENT_CREATE_CHILD_FUNCTION).newNode); + } else { + newStatements.splice(-1, 1, createComponent(node, COMPONENT_CREATE_CHILD_FUNCTION).newNode); + } + } + if (etsComponentResult.hasAttr) { + bindComponentAttr(node, res.identifierNode, newStatements, log, true, false, immutableStatements); + } + processInnerCompStatements(innerCompStatements, newStatements, node, isGlobalBuilder, + isTransition, undefined, immutableStatements, componentName, builderParamsResult); + storedFileInfo.lazyForEachInfo.isDependItem = false; + processComponentChild(etsComponentResult.etsComponentNode.body, innerCompStatements, log, + {isAcceleratePreview: false, line: 0, column: 0, fileName: ''}, isBuilder, parent, undefined, + isGlobalBuilder, false, builderParamsResult, isInRepeatTemplate); + } else { + bindComponentAttr(node, res.identifierNode, newStatements, log, true, false, immutableStatements); + processInnerCompStatements(innerCompStatements, newStatements, node, isGlobalBuilder, + isTransition, undefined, immutableStatements, componentName, builderParamsResult); + } + if (res.isContainerComponent || res.needPop) { + innerCompStatements.push(createComponent(node, COMPONENT_POP_FUNCTION).newNode); + } + if (partialUpdateConfig.partialUpdateMode && idName) { + innerCompStatements.splice(judgeIdStart, innerCompStatements.length - judgeIdStart, + ifRetakeId(innerCompStatements.slice(judgeIdStart), idName)); + } +} + +export function ifRetakeId(blockContent: ts.Statement[], idName: ts.Expression): ts.IfStatement { + return ts.factory.createIfStatement( + ts.factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(COMPONENT_IF), + ts.factory.createIdentifier(CAN_RETAKE) + ), + undefined, + [idName] + ) + ), + ts.factory.createBlock( + blockContent, + true + ), + undefined + ); +} + +function processRepeatComponent(node: ts.ExpressionStatement, innerCompStatements: ts.Statement[], + log: LogInfo[], isBuilder: boolean = false, isGlobalBuilder: boolean = false, + isTransition: boolean = false, builderParamsResult: BuilderParamsResult = null, + isInRepeatTemplate: boolean = false): void { + const chainCallTransform: ts.CallExpression = + recurseRepeatExpression(node.expression as ts.CallExpression, log, isBuilder, isGlobalBuilder, + isTransition, isInRepeatTemplate) as ts.CallExpression; + innerCompStatements.push(createComponentCreationStatement(node, + [ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + chainCallTransform, + ts.factory.createIdentifier(COMPONENT_RENDER_FUNCTION) + ), + undefined, + [ts.factory.createIdentifier(ISINITIALRENDER)] + ))], COMPONENT_REPEAT, isGlobalBuilder, isTransition, undefined, null, builderParamsResult)); +} + +function recurseRepeatExpression(node: ts.CallExpression | ts.PropertyAccessExpression, + log: LogInfo[], isBuilder: boolean = false, isGlobalBuilder: boolean = false, isTransition: boolean = false, isInRepeatTemplate: boolean = false): + ts.PropertyAccessExpression | ts.CallExpression { + if (ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && + node.expression.getText() === COMPONENT_REPEAT) { + return ts.factory.createCallExpression(node.expression, node.typeArguments, [...node.arguments, ts.factory.createThis()]); + } else if (ts.isPropertyAccessExpression(node)) { + return ts.factory.updatePropertyAccessExpression(node, + recurseRepeatExpression(node.expression, log, isBuilder, isGlobalBuilder, isTransition, isInRepeatTemplate), node.name); + } else { + let repeatPropArgs: ts.ArrowFunction[] = processRepeatAttributeArrowNode(node.arguments); + storedFileInfo.processRepeat = true; + repeatPropArgs = processRepeatPropWithChild(node, repeatPropArgs, log, isBuilder, isGlobalBuilder, isTransition, isInRepeatTemplate); + storedFileInfo.processRepeat = false; + return ts.factory.updateCallExpression(node, + recurseRepeatExpression(node.expression as ts.PropertyAccessExpression, log, isBuilder, + isGlobalBuilder, isTransition, isInRepeatTemplate) as ts.PropertyAccessExpression, undefined, repeatPropArgs); + } +} + +function processRepeatPropWithChild(node: ts.CallExpression, repeatPropArgs: ts.ArrowFunction[], + log: LogInfo[], isBuilder: boolean = false, isGlobalBuilder: boolean = false, isTransition: boolean = false, + isInRepeatTemplate: boolean = false): ts.ArrowFunction[] { + if (ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.name) && + node.expression.name.getText() === REPEAT_EACH && repeatPropArgs.length > 0 && repeatPropArgs[0].body) { + // transfer args for each property + return [ + ts.factory.updateArrowFunction(repeatPropArgs[0], repeatPropArgs[0].modifiers, repeatPropArgs[0].typeParameters, + repeatPropArgs[0].parameters, repeatPropArgs[0].type, repeatPropArgs[0].equalsGreaterThanToken, + processComponentBlock(processRepeatCallBackBlock(repeatPropArgs[0]), false, log, isTransition, + isBuilder, undefined, undefined, isGlobalBuilder, null, false, isInRepeatTemplate)), + ...repeatPropArgs.slice(1) + ]; + } else if (ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.name) && + node.expression.name.getText() === REPEAT_TEMPLATE && repeatPropArgs.length > 1 && repeatPropArgs[1].body) { + // transfer args for template property + return [ + repeatPropArgs[0], ts.factory.updateArrowFunction(repeatPropArgs[1], repeatPropArgs[1].modifiers, repeatPropArgs[1].typeParameters, + repeatPropArgs[1].parameters, repeatPropArgs[1].type, repeatPropArgs[1].equalsGreaterThanToken, + processComponentBlock(processRepeatCallBackBlock(repeatPropArgs[1]), false, log, isTransition, isBuilder, undefined, undefined, + isGlobalBuilder, null, false, true)), + ...repeatPropArgs.slice(2) + ]; + } + return repeatPropArgs; +} + +function processRepeatCallBackBlock(repeatPropArg: ts.ArrowFunction): ts.Block { + if (ts.isBlock(repeatPropArg.body)) { + return repeatPropArg.body; + } else { + return ts.factory.updateArrowFunction(repeatPropArg, ts.getModifiers(repeatPropArg), repeatPropArg.typeParameters, + repeatPropArg.parameters, repeatPropArg.type, repeatPropArg.equalsGreaterThanToken, + ts.factory.createBlock([ts.factory.createExpressionStatement(repeatPropArg.body)], true)).body as ts.Block; + } +} + +function processRepeatAttributeArrowNode(argumentsNode: ts.ArrowFunction[]): ts.ArrowFunction[] { + for (let i = 0; i < argumentsNode.length; i++) { + while (ts.isParenthesizedExpression(argumentsNode[i])) { + if (ts.isArrowFunction(argumentsNode[i].expression)) { + argumentsNode.splice(i, 1, argumentsNode[i].expression); + break; + } else { + if (argumentsNode[i].expression) { + argumentsNode[i] = argumentsNode[i].expression; + } else { + break; + } + } + } + } + return argumentsNode; +} + +function processDebug(node: ts.Statement, nameResult: NameResult, newStatements: ts.Statement[], + getNode: boolean = false): ts.ExpressionStatement { + if ((projectConfig.isPreview || projectConfig.enableDebugLine) && nameResult.name && + !NO_DEBUG_LINE_COMPONENT.has(nameResult.name)) { + let posOfNode: ts.LineAndCharacter; + let curFileName: string; + let line: number = 1; + let col: number = 1; + if (sourceNode && newsupplement.isAcceleratePreview) { + posOfNode = sourceNode.getLineAndCharacterOfPosition(getRealNodePos(node) - 22); + curFileName = newsupplement.fileName; + if (posOfNode.line === 0) { + col = newsupplement.column - 1; + } + line = newsupplement.line; + } else { + posOfNode = transformLog.sourceFile.getLineAndCharacterOfPosition(getRealNodePos(node)); + curFileName = transformLog.sourceFile.fileName.replace(/\.ts$/, ''); + } + let debugInfo: string; + if (projectConfig.isPreview) { + if (projectConfig.minAPIVersion >= 11) { + debugInfo = `${path.relative(projectConfig.projectRootPath, curFileName).replace(/\\+/g, '/')}` + + `(${posOfNode.line + line}:${posOfNode.character + col})`; + } else { + debugInfo = `${path.relative(projectConfig.projectPath, curFileName).replace(/\\+/g, '/')}` + + `(${posOfNode.line + line}:${posOfNode.character + col})`; + } + } else if (projectConfig.enableDebugLine) { + debugInfo = `${path.relative(projectConfig.projectRootPath, curFileName)}` + + `(${posOfNode.line + line}:${posOfNode.character + col})`; + } + const debugNode: ts.ExpressionStatement = ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(nameResult.name), + ts.factory.createIdentifier(COMPONENT_DEBUGLINE_FUNCTION), + createDebugLineArgs(debugInfo))); + if (getNode) { + return debugNode; + } + newStatements.push(debugNode); + } + return undefined; +} + +function createDebugLineArgs(debugInfo: string): ts.NodeArray { + const argsArr: ts.Node[] = [ts.factory.createStringLiteral(debugInfo)]; + const pkgName: string = storedFileInfo.getCurrentArkTsFile().pkgName; + if (pkgName) { + argsArr.push(ts.factory.createStringLiteral(pkgName)); + } + return ts.factory.createNodeArray(argsArr); +} + +function processInnerCompStatements(innerCompStatements: ts.Statement[], + newStatements: ts.Statement[], node: ts.Statement, isGlobalBuilder: boolean, isTransition: boolean, + nameResult: NameResult, immutableStatements: ts.Statement[], componentName: string, + builderParamsResult: BuilderParamsResult): void { + if (!partialUpdateConfig.partialUpdateMode) { + innerCompStatements.push(...newStatements); + } else { + innerCompStatements.push(createComponentCreationStatement(node, newStatements, componentName, + isGlobalBuilder, isTransition, nameResult, immutableStatements, builderParamsResult)); + } +} + +function createComponentCreationArrowParams(isGlobalBuilder: boolean, + builderParamsResult: BuilderParamsResult, isRecycleComponent: boolean = false): ts.ParameterDeclaration[] { + const arrowNodes: ts.ParameterDeclaration[] = [ + ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(ELMTID), undefined, undefined, undefined), + ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(ISINITIALRENDER), undefined, undefined, undefined) + ]; + if (!isRecycleComponent && partialUpdateConfig.optimizeComponent && isGlobalBuilder && + builderParamsResult && builderParamsResult.firstParam) { + const paramName: ts.Identifier = builderParamsResult.firstParam.name as ts.Identifier; + arrowNodes.push(ts.factory.createParameterDeclaration(undefined, undefined, + paramName, undefined, undefined, ts.factory.createIdentifier(`__${paramName.escapedText.toString()}__`) + )); + } + return arrowNodes; +} + +export function createComponentCreationStatement(node: ts.Statement, innerStatements: ts.Statement[], + componentName: string, isGlobalBuilder: boolean = false, isTransition: boolean = false, + nameResult: NameResult = undefined, immutableStatements: ts.Statement[] = null, + builderParamsResult: BuilderParamsResult = null, isRecycleComponent: boolean = false): ts.Statement { + const blockArr: ts.Statement[] = [...innerStatements]; + if (nameResult) { + blockArr.push(processDebug(node, nameResult, innerStatements, true)); + } + if (!isTransition) { + createInitRenderStatement(node, immutableStatements, blockArr); + } + if (!partialUpdateConfig.optimizeComponent) { + blockArr.unshift(createViewStackProcessorStatement(STARTGETACCESSRECORDINGFOR, ELMTID)); + blockArr.push(createViewStackProcessorStatement(STOPGETACCESSRECORDING)); + } + const creationArgs: ts.Expression[] = [ + ts.factory.createArrowFunction(undefined, undefined, + createComponentCreationArrowParams(isGlobalBuilder, builderParamsResult, isRecycleComponent), + undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock(blockArr, true) + ) + ]; + if (partialUpdateConfig.optimizeComponent) { + creationArgs.push(isTransition ? ts.factory.createNull() : + ts.factory.createIdentifier(componentName)); + } + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(createConditionParent(isGlobalBuilder), + ts.factory.createIdentifier(partialUpdateConfig.optimizeComponent ? + OBSERVECOMPONENTCREATION2 : OBSERVECOMPONENTCREATION) + ), undefined, creationArgs) + ); +} + +export function createViewStackProcessorStatement(propertyAccessName: string, elmtId?: string): ts.Statement { + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(VIEWSTACKPROCESSOR), + ts.factory.createIdentifier(propertyAccessName) + ), + undefined, + elmtId ? [ts.factory.createIdentifier(ELMTID)] : [] + ) + ); +} + +function createInitRenderStatement(node: ts.Statement, + immutableStatements: ts.Statement[], blockArr: ts.Statement[]): void { + if (partialUpdateConfig.optimizeComponent) { + if (immutableStatements && immutableStatements.length) { + blockArr.push(ts.factory.createIfStatement( + ts.factory.createIdentifier(ISINITIALRENDER), + ts.factory.createBlock(immutableStatements, true) + )); + } + } else { + blockArr.push(ts.factory.createIfStatement( + ts.factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + ts.factory.createIdentifier(ISINITIALRENDER) + ), + ts.factory.createBlock( + [ + ts.isExpressionStatement(node) ? + createComponent(node, COMPONENT_POP_FUNCTION).newNode : createIfPop() + ], + true + ), + immutableStatements && immutableStatements.length ? + ts.factory.createBlock(immutableStatements, true) : undefined + )); + } +} + +function processItemComponent(node: ts.ExpressionStatement, nameResult: NameResult, innerCompStatements: ts.Statement[], + log: LogInfo[], parent: string = undefined, isGlobalBuilder: boolean = false, idName: ts.Expression = undefined, + builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): void { + const itemRenderInnerStatements: ts.Statement[] = []; + const immutableStatements: ts.Statement[] = []; + const deepItemRenderInnerStatements: ts.Statement[] = []; + if (addElmtIdNode()) { + itemRenderInnerStatements.push(createCollectElmtIdNode()); + } + const res: CreateResult = createComponent(node, COMPONENT_CREATE_FUNCTION); + const isLazyCreate: boolean = checkLazyCreate(node, nameResult); + const itemCreateStatement: ts.Statement = createItemCreate(nameResult, isLazyCreate); + itemRenderInnerStatements.push(itemCreateStatement); + const etsComponentResult: EtsComponentResult = parseEtsComponentExpression(node); + if (etsComponentResult.etsComponentNode.body && ts.isBlock(etsComponentResult.etsComponentNode.body)) { + if (etsComponentResult.hasAttr) { + bindComponentAttr(node, res.identifierNode, itemRenderInnerStatements, log, true, false, immutableStatements); + } + storedFileInfo.lazyForEachInfo.isDependItem = false; + processComponentChild(etsComponentResult.etsComponentNode.body, deepItemRenderInnerStatements, log, + {isAcceleratePreview: false, line: 0, column: 0, fileName: ''}, false, parent, undefined, isGlobalBuilder, false, + builderParamsResult, isInRepeatTemplate); + } else { + bindComponentAttr(node, res.identifierNode, itemRenderInnerStatements, log, true, false, immutableStatements); + } + let generateItem: ts.IfStatement | ts.Block; + if (idName) { + generateItem = ifRetakeId([createItemBlock( + node, itemRenderInnerStatements, deepItemRenderInnerStatements, nameResult, isLazyCreate, + immutableStatements, isGlobalBuilder, builderParamsResult, itemCreateStatement)], idName); + } else { + generateItem = createItemBlock( + node, itemRenderInnerStatements, deepItemRenderInnerStatements, nameResult, isLazyCreate, + immutableStatements, isGlobalBuilder, builderParamsResult, itemCreateStatement); + } + innerCompStatements.push(generateItem); +} + +function createItemCreate(nameResult: NameResult, isLazyCreate: boolean): ts.Statement { + const itemCreateArgs: ts.Expression[] = []; + if (isLazyCreate) { + itemCreateArgs.push(ts.factory.createIdentifier(DEEPRENDERFUNCTION), ts.factory.createTrue()); + } else { + itemCreateArgs.push( + ts.factory.createArrowFunction(undefined, undefined, [], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock([], false)), + ts.factory.createFalse() + ); + } + itemCreateArgs.push(...nameResult.arguments); + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(nameResult.name), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION)), undefined, itemCreateArgs)); +} + +type ItemCreation = { + creationArgs: ts.Expression[], + creationName: string, +}; + +function getItemCreation(nameResult: NameResult): ItemCreation { + const creationArgs: ts.Expression[] = []; + let creationName: string = OBSERVECOMPONENTCREATION; + if (partialUpdateConfig.optimizeComponent) { + creationArgs.push( + ts.factory.createIdentifier(ITEMCREATION2), + ts.factory.createIdentifier(nameResult.name)); + creationName = OBSERVECOMPONENTCREATION2; + } else { + creationArgs.push(ts.factory.createIdentifier(ITEMCREATION)); + } + return { creationArgs, creationName }; +} + +function createItemBlock( + node: ts.ExpressionStatement, + itemRenderInnerStatements: ts.Statement[], + deepItemRenderInnerStatements: ts.Statement[], + nameResult: NameResult, isLazyCreate: boolean, + immutableStatements: ts.Statement[], + isGlobalBuilder: boolean, + builderParamsResult: BuilderParamsResult, + itemCreateStatement: ts.Statement +): ts.Block { + const blockNode: ts.Statement[] = [ + createItemCreation2(node, itemRenderInnerStatements, nameResult, immutableStatements, + isGlobalBuilder, builderParamsResult) + ]; + const itemCreation: ItemCreation = getItemCreation(nameResult); + if (isLazyCreate) { + blockNode.unshift(createItemCreation(node, isGlobalBuilder, builderParamsResult, itemCreateStatement)); + blockNode.push( + createDeepRenderFunction(node, deepItemRenderInnerStatements, isGlobalBuilder, builderParamsResult), + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(itemCreation.creationName) + ), undefined, itemCreation.creationArgs + )), + createComponent(node, COMPONENT_POP_FUNCTION).newNode + ); + } else { + if (!partialUpdateConfig.optimizeComponent) { + blockNode.unshift(createItemCreation(node, isGlobalBuilder, builderParamsResult)); + } + blockNode.push( + createObservedDeepRender(node, deepItemRenderInnerStatements, itemCreation), + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createIdentifier(OBSERVEDDEEPRENDER), undefined, [])) + ); + } + return ts.factory.createBlock(blockNode, true); +} + +function checkLazyCreate(node: ts.ExpressionStatement, nameResult: NameResult): boolean { + if (nameResult.name === LIST_ITEM) { + if (nameResult.arguments.length && ts.isStringLiteral(nameResult.arguments[0]) && + nameResult.arguments[0].text === 'false') { + return false; + } + if (storedFileInfo.processRepeat) { + return false; + } + if (isLazyForEachChild(node)) { + return false; + } + return true; + } + return false; +} + +function createItemCreation(node: ts.ExpressionStatement, isGlobalBuilder: boolean, + builderParamsResult: BuilderParamsResult, itemCreateStatement: ts.Statement = undefined): ts.VariableStatement { + if (!partialUpdateConfig.optimizeComponent) { + itemCreateStatement = ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createIdentifier(ITEMCREATION2), + undefined, createItemCreationArgs(isGlobalBuilder, builderParamsResult))); + } + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(ITEMCREATION), undefined, undefined, + ts.factory.createArrowFunction(undefined, undefined, + createComponentCreationArrowParams(isGlobalBuilder, builderParamsResult), undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [ + createViewStackProcessorStatement(STARTGETACCESSRECORDINGFOR, ELMTID), + itemCreateStatement, + ts.factory.createIfStatement( + ts.factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + ts.factory.createIdentifier(ISINITIALRENDER) + ), + ts.factory.createBlock( + [createComponent(node, COMPONENT_POP_FUNCTION).newNode], + true + ) + ), + createViewStackProcessorStatement(STOPGETACCESSRECORDING) + ], + true + ) + ) + )], + ts.NodeFlags.Const + ) + ); +} + +function createItemCreation2( + node: ts.ExpressionStatement, + itemRenderInnerStatements: ts.Statement[], + nameResult: NameResult, + immutableStatements: ts.Statement[], + isGlobalBuilder: boolean, + builderParamsResult: BuilderParamsResult +): ts.VariableStatement { + const itemBlock: ts.Statement[] = [ + ...itemRenderInnerStatements, + processDebug(node, nameResult, itemRenderInnerStatements, true) + ]; + if (immutableStatements && immutableStatements.length) { + itemBlock.push(ts.factory.createIfStatement( + ts.factory.createIdentifier(ISINITIALRENDER), + ts.factory.createBlock(immutableStatements, true) + )); + } + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(ITEMCREATION2), undefined, undefined, + ts.factory.createArrowFunction(undefined, undefined, + createComponentCreationArrowParams(isGlobalBuilder, builderParamsResult), undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock(itemBlock, true) + ) + )], + ts.NodeFlags.Const + ) + ); +} + +function createItemCreationArgs(isGlobalBuilder: boolean, + builderParamsResult: BuilderParamsResult): ts.Expression[] { + const itemCreationArgs: ts.Expression[] = [ + ts.factory.createIdentifier(ELMTID), ts.factory.createIdentifier(ISINITIALRENDER)]; + if (partialUpdateConfig.optimizeComponent && isGlobalBuilder && builderParamsResult && + builderParamsResult.firstParam) { + itemCreationArgs.push(builderParamsResult.firstParam.name as ts.Identifier); + } + return itemCreationArgs; +} + +function createDeepRenderFunction( + node: ts.ExpressionStatement, + deepItemRenderInnerStatements: ts.Statement[], + isGlobalBuilder: boolean, + builderParamsResult: BuilderParamsResult +): ts.VariableStatement { + const blockNode: ts.Statement[] = [ + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createIdentifier(ITEMCREATION), undefined, + createItemCreationArgs(isGlobalBuilder, builderParamsResult) + )), + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(UPDATE_FUNC_BY_ELMT_ID) + ), + ts.factory.createIdentifier(CREATE_SET_METHOD) + ), undefined, + [ts.factory.createIdentifier(ELMTID), ts.factory.createIdentifier(ITEMCREATION)] + )), + ...deepItemRenderInnerStatements, + createComponent(node, COMPONENT_POP_FUNCTION).newNode + ]; + if (partialUpdateConfig.optimizeComponent) { + blockNode.splice(1, 1); + } + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(DEEPRENDERFUNCTION), undefined, undefined, + ts.factory.createArrowFunction(undefined, undefined, + createComponentCreationArrowParams(isGlobalBuilder, builderParamsResult), undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock(blockNode, true) + ) + )], + ts.NodeFlags.Const + ) + ); +} + +function createObservedDeepRender( + node: ts.ExpressionStatement, + deepItemRenderInnerStatements: ts.Statement[], + itemCreation: ItemCreation +): ts.VariableStatement { + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(OBSERVEDDEEPRENDER), + undefined, + undefined, + ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [ + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(itemCreation.creationName) + ), undefined, itemCreation.creationArgs + )), + ...deepItemRenderInnerStatements, + createComponent(node, COMPONENT_POP_FUNCTION).newNode + ], + true + ) + ) + )], + ts.NodeFlags.Const + ) + ); +} + +function processTabAndNav(node: ts.ExpressionStatement, innerCompStatements: ts.Statement[], + nameResult: NameResult, log: LogInfo[], parent: string = undefined, isGlobalBuilder: boolean = false, + idName: ts.Expression = undefined, builderParamsResult: BuilderParamsResult = null, + isInRepeatTemplate: boolean = false): void { + const name: string = nameResult.name; + const tabContentComp: ts.EtsComponentExpression = getEtsComponentExpression(node); + const tabContentBody: ts.Block = tabContentComp.body; + let tabContentCreation: ts.Statement; + const tabContentPop: ts.Statement = ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name), + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION)), undefined, [])); + const tabAttrs: ts.Statement[] = []; + if (addElmtIdNode()) { + tabAttrs.push(createCollectElmtIdNode()); + } + const immutableStatements: ts.Statement[] = []; + let judgeIdStart: number; + if (idName) { + judgeIdStart = innerCompStatements.length; + } + if (tabContentBody && tabContentBody.statements.length) { + const newTabContentChildren: ts.Statement[] = []; + processComponentChild(tabContentBody, newTabContentChildren, log, {isAcceleratePreview: false, line: 0, column: 0, fileName: ''}, + false, parent, undefined, isGlobalBuilder, false, builderParamsResult, isInRepeatTemplate); + const navDestinationCallback: (ts.ArrowFunction | ts.NewExpression | ts.ObjectLiteralExpression)[] = + [ts.factory.createArrowFunction(undefined, undefined, [], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock([...newTabContentChildren], true))]; + if (name === NAV_DESTINATION) { + navDestinationCallback.push(...navigationCreateParam(NAV_DESTINATION, COMPONENT_CREATE_FUNCTION, undefined, true)); + } + tabContentCreation = ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(name), ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION)), + undefined, navDestinationCallback)); + bindComponentAttr(node, ts.factory.createIdentifier(name), tabAttrs, log, true, false, immutableStatements); + processInnerCompStatements( + innerCompStatements, [tabContentCreation, ...tabAttrs], node, isGlobalBuilder, false, + nameResult, immutableStatements, name, builderParamsResult); + storedFileInfo.lazyForEachInfo.isDependItem = false; + } else { + tabContentCreation = ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION)), undefined, + name === NAV_DESTINATION ? navigationCreateParam(NAV_DESTINATION, COMPONENT_CREATE_FUNCTION) : [])); + bindComponentAttr(node, ts.factory.createIdentifier(name), tabAttrs, log, true, false, immutableStatements); + processInnerCompStatements( + innerCompStatements, [tabContentCreation, ...tabAttrs], node, isGlobalBuilder, false, + nameResult, immutableStatements, name, builderParamsResult); + } + innerCompStatements.push(tabContentPop); + if (idName) { + innerCompStatements.splice(judgeIdStart, innerCompStatements.length - judgeIdStart, + ifRetakeId(innerCompStatements.slice(judgeIdStart), idName)); + } +} + +export function getRealNodePos(node: ts.Node): number { + // @ts-ignore + if (node.pos === -1 && node.expression) { + // @ts-ignore + return getRealNodePos(node.expression); + } else { + return node.getStart(); + } +} + +function processForEachComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[], + log: LogInfo[], isBuilder: boolean = false, isGlobalBuilder: boolean = false): void { + const popNode: ts.ExpressionStatement = ts.factory.createExpressionStatement(createFunction( + // @ts-ignore + node.expression.expression as ts.Identifier, + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null)); + if (ts.isCallExpression(node.expression)) { + const propertyNode: ts.PropertyAccessExpression = ts.factory.createPropertyAccessExpression( + node.expression.expression as ts.Identifier, + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION) + ); + const argumentsArray: ts.Expression[] = Array.from(node.expression.arguments); + let arrayObserveredObject: ts.CallExpression; + if (argumentsArray.length) { + arrayObserveredObject = ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(FOREACH_OBSERVED_OBJECT), + ts.factory.createIdentifier(FOREACH_GET_RAW_OBJECT)), undefined, [argumentsArray[0]]); + } + argumentsArray.splice(0, 1, arrayObserveredObject); + const newForEachArrowFunc: ts.ArrowFunction = processForEachFunctionBlock(node.expression); + const newArrowNode: ts.ArrowFunction = + processForEachBlock(node.expression, log, newForEachArrowFunc, isBuilder) as ts.ArrowFunction; + if (newArrowNode) { + argumentsArray.splice(1, 1, newArrowNode); + } + node = addForEachId(ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression( + node.expression, propertyNode, node.expression.typeArguments, argumentsArray)), isGlobalBuilder); + } + newStatements.push(node, popNode); +} + +function collectForEachAttribute(node: ts.ExpressionStatement, + attributeList: ts.ExpressionStatement[], name: string): ts.ExpressionStatement { + let tempNode = node.expression; + while (tempNode && ts.isCallExpression(tempNode)) { + if (tempNode.expression && ts.isPropertyAccessExpression(tempNode.expression)) { + attributeList.unshift(generateForEachAttribute(tempNode, name)); + } else if (tempNode.expression && ts.isIdentifier(tempNode.expression)) { + return ts.factory.updateExpressionStatement(node, tempNode); + } + tempNode = tempNode.expression?.expression; + } + return node; +} + +function generateForEachAttribute(tempNode: ts.CallExpression, name: string): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(name), + tempNode.expression.name + ), + undefined, + tempNode.arguments + )); +} + +function processForEachComponentNew(node: ts.ExpressionStatement, newStatements: ts.Statement[], + log: LogInfo[], name: string, isGlobalBuilder: boolean = false, builderParamsResult: BuilderParamsResult = null, + isInRepeatTemplate: boolean = false): void { + const attributeList: ts.ExpressionStatement[] = []; + const newNode = collectForEachAttribute(node, attributeList, name); + const newForEachStatements: ts.Statement[] = []; + const popNode: ts.ExpressionStatement = ts.factory.createExpressionStatement(createFunction( + (newNode.expression as ts.CallExpression).expression as ts.Identifier, + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null)); + if (ts.isCallExpression(newNode.expression)) { + if (checkForEachComponent(newNode)) { + storedFileInfo.processForEach += 1; + } else { + storedFileInfo.processLazyForEach += 1; + } + const argumentsArray: ts.Expression[] = Array.from(newNode.expression.arguments); + const propertyNode: ts.ExpressionStatement = ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + newNode.expression.expression as ts.Identifier, + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION)), undefined, [])); + const newForEachArrowFunc: ts.ArrowFunction = processForEachFunctionBlock(newNode.expression); + const newArrowNode: ts.NodeArray = + processForEachBlock(newNode.expression, log, newForEachArrowFunc, false, isGlobalBuilder, + builderParamsResult, isInRepeatTemplate) as ts.NodeArray; + const itemGenFunctionStatement: ts.VariableStatement = createItemGenFunctionStatement(newNode.expression, newArrowNode, newForEachArrowFunc); + const itemIdFuncStatement: ts.VariableStatement = createItemIdFuncStatement(newNode.expression, argumentsArray); + const updateFunctionStatement: ts.ExpressionStatement = createUpdateFunctionStatement(argumentsArray, newForEachArrowFunc, isGlobalBuilder); + const lazyForEachStatement: ts.ExpressionStatement = createLazyForEachStatement(argumentsArray); + if (newNode.expression.expression.getText() === COMPONENT_FOREACH) { + newForEachStatements.push(propertyNode, ...attributeList, itemGenFunctionStatement, updateFunctionStatement); + newStatements.push(createComponentCreationStatement(newNode, newForEachStatements, COMPONENT_FOREACH, + isGlobalBuilder, false, undefined, null, builderParamsResult), popNode); + } else { + if (argumentsArray[2]) { + newStatements.push(ts.factory.createBlock([itemGenFunctionStatement, itemIdFuncStatement, lazyForEachStatement, + ...attributeList, popNode], true)); + } else { + newStatements.push(ts.factory.createBlock([itemGenFunctionStatement, lazyForEachStatement, popNode, ...attributeList], true)); + } + } + if (checkForEachComponent(newNode)) { + storedFileInfo.processForEach -= 1; + } else { + storedFileInfo.processLazyForEach -= 1; + } + } +} + +function checkForEachComponent(node: ts.ExpressionStatement): boolean { + return node.expression.expression && ts.isIdentifier(node.expression.expression) && + node.expression.expression.getText() === COMPONENT_FOREACH; +} + +function createItemGenFunctionStatement( + node: ts.CallExpression, + newArrowNode: ts.NodeArray, + newForEachArrowFunc: ts.ArrowFunction +): ts.VariableStatement { + if (newForEachArrowFunc && ts.isArrowFunction(newForEachArrowFunc)) { + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(node.expression.getText() === COMPONENT_FOREACH ? + FOREACHITEMGENFUNCTION : __LAZYFOREACHITEMGENFUNCTION), + undefined, undefined, + ts.factory.createArrowFunction( + undefined, undefined, + newForEachArrowFunc.parameters && newForEachArrowFunc.parameters.length >= 1 ? + getParameters(newForEachArrowFunc) : [], + undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + newForEachArrowFunc.parameters && newForEachArrowFunc.parameters.length >= 1 ? + isForEachItemGeneratorParam(newForEachArrowFunc, newArrowNode) : + newArrowNode ? [...newArrowNode] : undefined, + true + ) + ) + ) + ], + ts.NodeFlags.Const + ) + ); + } + return undefined; +} + +function isForEachItemGeneratorParam(argumentsArray: ts.Expression, newArrowNode: ts.NodeArray): ts.Statement[] { + const createVariableStatementNode: ts.Statement[] = []; + createVariableStatementNode.push(ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier( + argumentsArray.parameters[0] && argumentsArray.parameters[0].name.getText()), + undefined, + undefined, + ts.factory.createIdentifier(_ITEM) + )], + ts.NodeFlags.Const + ) + )); + if (newArrowNode) { + createVariableStatementNode.push(...newArrowNode); + } + return createVariableStatementNode; +} + +function getParameters(node: ts.ArrowFunction): ts.ParameterDeclaration[] { + const parameterArr: ts.ParameterDeclaration[] = [ + ts.factory.createParameterDeclaration( + undefined, undefined, ts.factory.createIdentifier(_ITEM)) + ]; + if (node.parameters && node.parameters.length > 1) { + parameterArr.push(node.parameters[1]); + } + if (projectConfig.optLazyForEach && storedFileInfo.processLazyForEach) { + if (node.parameters.length === 1) { + parameterArr.push(ts.factory.createParameterDeclaration( + undefined, undefined, ts.factory.createIdentifier(INDEX))); + } + parameterArr.push( + ts.factory.createParameterDeclaration( + undefined, undefined, ts.factory.createIdentifier(IS_INITIAL_ITEM)), + ts.factory.createParameterDeclaration( + undefined, undefined, ts.factory.createIdentifier(IDS)) + ); + } + return parameterArr; +} + +function createItemIdFuncStatement( + node: ts.CallExpression, + argumentsArray: ts.Expression[] +): ts.VariableStatement { + if (argumentsArray[2]) { + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(node.expression.getText() === COMPONENT_FOREACH ? + FOREACHITEMIDFUNC : __LAZYFOREACHITEMIDFUNC), undefined, undefined, + argumentsArray[2] + )], + ts.NodeFlags.Const + ) + ); + } + return undefined; +} + +function createUpdateFunctionStatement(argumentsArray: ts.Expression[], + newForEachArrowFunc: ts.ArrowFunction, isGlobalBuilder: boolean = false): ts.ExpressionStatement { + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + isGlobalBuilder ? parentConditionalExpression() : ts.factory.createThis(), + ts.factory.createIdentifier(FOREACHUPDATEFUNCTION) + ), + undefined, + addForEachIdFuncParameter(argumentsArray, newForEachArrowFunc) + ) + ); +} + +function addForEachIdFuncParameter(argumentsArray: ts.Expression[], newForEachArrowFunc: ts.ArrowFunction): ts.Expression[] { + const addForEachIdFuncParameterArr: ts.Expression[] = []; + addForEachIdFuncParameterArr.push( + ts.factory.createIdentifier(ELMTID), + argumentsArray[0], + ts.factory.createIdentifier(FOREACHITEMGENFUNCTION) + ); + // @ts-ignore + if (newForEachArrowFunc && newForEachArrowFunc.parameters && newForEachArrowFunc.parameters[1]) { + if (!argumentsArray[2]) { + addForEachIdFuncParameterArr.push(...addForEachParameter(ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED), TRUE, FALSE)); + } else { + // @ts-ignore + argumentsArray[2].parameters && argumentsArray[2].parameters[1] ? + addForEachIdFuncParameterArr.push(...addForEachParameter(argumentsArray[2], TRUE, TRUE)) : + addForEachIdFuncParameterArr.push(...addForEachParameter(argumentsArray[2], TRUE, FALSE)); + } + } + // @ts-ignore + if (newForEachArrowFunc && newForEachArrowFunc.parameters && newForEachArrowFunc.parameters.length < 2 && newForEachArrowFunc.parameters[0] && + argumentsArray && argumentsArray.length > 2 && argumentsArray[2]) { + // @ts-ignore + argumentsArray[2].parameters && argumentsArray[2].parameters[1] ? + addForEachIdFuncParameterArr.push(...addForEachParameter(argumentsArray[2], FALSE, TRUE)) : + addForEachIdFuncParameterArr.push(...addForEachParameter(argumentsArray[2], FALSE, FALSE)); + } + return addForEachIdFuncParameterArr; +} + +function addForEachParameter(forEachItemIdContent: ts.Expression, forEachItemGen: string, forEachItemId: string): ts.Expression[] { + return [forEachItemIdContent, ts.factory.createIdentifier(forEachItemGen), + ts.factory.createIdentifier(forEachItemId)]; +} + +function createLazyForEachStatement(argumentsArray: ts.Expression[]): ts.ExpressionStatement { + const parameterList: ts.Expression[] = [ + ts.factory.createStringLiteral(componentInfo.id.toString()), + ts.factory.createThis(), + argumentsArray[0], + ts.factory.createIdentifier(__LAZYFOREACHITEMGENFUNCTION) + ]; + if (argumentsArray.length >= 3 && argumentsArray[2]) { + parameterList.push(ts.factory.createIdentifier(__LAZYFOREACHITEMIDFUNC)); + } + if (projectConfig.optLazyForEach) { + parameterList.push(ts.factory.createTrue()); + } + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(COMPONENT_LAZYFOREACH), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION) + ), + undefined, + parameterList + ) + ); +} + +function addForEachId(node: ts.ExpressionStatement, isGlobalBuilder: boolean = false): ts.ExpressionStatement { + const forEachComponent: ts.CallExpression = node.expression as ts.CallExpression; + return ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression( + forEachComponent, forEachComponent.expression, forEachComponent.typeArguments, + [ts.factory.createStringLiteral((++componentInfo.id).toString()), + isGlobalBuilder ? parentConditionalExpression() : ts.factory.createThis(), + ...forEachComponent.arguments])); +} + +export function parentConditionalExpression(): ts.ConditionalExpression { + return ts.factory.createConditionalExpression( + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + ts.factory.createThis()); +} +function processForEachFunctionBlock(node: ts.CallExpression): ts.ArrowFunction { + if (ts.isArrowFunction(node.arguments[1])) { + return node.arguments[1]; + } else if (ts.isParenthesizedExpression(node.arguments[1]) && ts.isArrowFunction(node.arguments[1].expression)) { + return node.arguments[1].expression; + } else { + return null; + } +} +function processForEachBlock(node: ts.CallExpression, log: LogInfo[], + arrowNode: ts.ArrowFunction, isBuilder: boolean = false, isGlobalBuilder: boolean = false, + builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): ts.NodeArray | ts.ArrowFunction { + if (node.arguments.length > 1 && ts.isArrowFunction(arrowNode)) { + const isLazy: boolean = node.expression.getText() === COMPONENT_LAZYFOREACH; + const body: ts.ConciseBody = arrowNode.body; + if (!ts.isBlock(body)) { + const statement: ts.Statement = ts.factory.createExpressionStatement(body); + const blockNode: ts.Block = ts.factory.createBlock([statement], true); + // @ts-ignore + statement.parent = blockNode; + if (!partialUpdateConfig.partialUpdateMode) { + return ts.factory.updateArrowFunction( + arrowNode, ts.getModifiers(arrowNode), arrowNode.typeParameters, arrowNode.parameters, + arrowNode.type, arrowNode.equalsGreaterThanToken, + processComponentBlock(blockNode, isLazy, log, false, false, undefined, + arrowNode.parameters, isGlobalBuilder)); + } else { + return processComponentBlock(blockNode, isLazy, log, false, false, undefined, + arrowNode.parameters, isGlobalBuilder, builderParamsResult, false, isInRepeatTemplate).statements; + } + } else { + if (!partialUpdateConfig.partialUpdateMode) { + return ts.factory.updateArrowFunction( + arrowNode, ts.getModifiers(arrowNode), arrowNode.typeParameters, arrowNode.parameters, + arrowNode.type, arrowNode.equalsGreaterThanToken, + processComponentBlock(body, isLazy, log, false, isBuilder, undefined, arrowNode.parameters)); + } else { + return processComponentBlock(body, isLazy, log, false, false, undefined, arrowNode.parameters, + isGlobalBuilder, builderParamsResult, false, isInRepeatTemplate).statements; + } + } + } + return null; +} + +function createRenderingInProgress(isTrue: boolean): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(IS_RENDERING_IN_PROGRESS) + ), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + isTrue ? ts.factory.createTrue() : ts.factory.createFalse() + )); +} + +function addElmtIdNode(): boolean { + return partialUpdateConfig.partialUpdateMode && projectConfig.optLazyForEach && + ((storedFileInfo.processLazyForEach && storedFileInfo.lazyForEachInfo.isDependItem) || storedFileInfo.processBuilder); +} + +function processIfStatement(node: ts.IfStatement, newStatements: ts.Statement[], + log: LogInfo[], isBuilder: boolean = false, isGlobalBuilder: boolean = false, + builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): void { + const ifStatements: ts.Statement[] = []; + if (addElmtIdNode()) { + ifStatements.push(createCollectElmtIdNode()); + storedFileInfo.lazyForEachInfo.isDependItem = false; + } + const ifCreate: ts.ExpressionStatement = createIfCreate(); + const newIfNode: ts.IfStatement = processInnerIfStatement(node, 0, log, isBuilder, isGlobalBuilder, builderParamsResult, isInRepeatTemplate); + ifStatements.push(ifCreate, newIfNode); + const ifPop: ts.ExpressionStatement = createIfPop(); + if (!partialUpdateConfig.partialUpdateMode) { + newStatements.push(ifCreate, newIfNode, ifPop); + } else { + newStatements.push(createComponentCreationStatement(node, ifStatements, COMPONENT_IF, + isGlobalBuilder, false, undefined, null, builderParamsResult), ifPop); + } +} + +function processInnerIfStatement(node: ts.IfStatement, id: number, log: LogInfo[], + isBuilder: boolean = false, isGlobalBuilder: boolean = false, + builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): ts.IfStatement { + if (ts.isIdentifier(node.expression) && node.expression.originalKeywordKind === undefined && + !node.expression.escapedText) { + log.push({ + type: LogType.ERROR, + message: 'Condition expression cannot be null in if statement.', + pos: node.expression.getStart(), + code: '10905208' + }); + node = ts.factory.updateIfStatement(node, ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED), + node.thenStatement, node.elseStatement); + } + const newThenStatement: ts.Statement = processThenStatement( + node.thenStatement, id, log, isBuilder, isGlobalBuilder, builderParamsResult, isInRepeatTemplate); + const newElseStatement: ts.Statement = processElseStatement( + node.elseStatement, id, log, isBuilder, isGlobalBuilder, builderParamsResult, isInRepeatTemplate); + const newIfNode: ts.IfStatement = ts.factory.updateIfStatement( + node, node.expression, newThenStatement, newElseStatement); + return newIfNode; +} + +function processThenStatement(thenStatement: ts.Statement, id: number, + log: LogInfo[], isBuilder: boolean = false, isGlobalBuilder: boolean = false, + builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): ts.Statement { + if (ts.isExpressionStatement(thenStatement) && ts.isIdentifier(thenStatement.expression) && + thenStatement.expression.originalKeywordKind === undefined && + !thenStatement.expression.escapedText) { + log.push({ + type: LogType.ERROR, + message: 'Then statement cannot be null in if statement.', + pos: thenStatement.expression.getStart(), + code: '10905207' + }); + } + if (thenStatement) { + if (ts.isBlock(thenStatement)) { + thenStatement = processIfBlock(thenStatement, id, log, isBuilder, isGlobalBuilder, builderParamsResult, isInRepeatTemplate); + } else if (ts.isIfStatement(thenStatement)) { + thenStatement = processInnerIfStatement(thenStatement, 0, log, isBuilder, isGlobalBuilder, builderParamsResult, isInRepeatTemplate); + thenStatement = ts.factory.createBlock( + partialUpdateConfig.partialUpdateMode ? + [createIfCreate(), createIfBranchFunc(id, [thenStatement], isGlobalBuilder), createIfPop()] : + [createIfCreate(), createIfBranchId(id), thenStatement, createIfPop()], + true + ); + } else { + thenStatement = ts.factory.createBlock([thenStatement], true); + thenStatement = processIfBlock(thenStatement as ts.Block, id, log, isBuilder, isGlobalBuilder, builderParamsResult, isInRepeatTemplate); + } + } + return thenStatement; +} + +function processElseStatement(elseStatement: ts.Statement, id: number, + log: LogInfo[], isBuilder: boolean = false, isGlobalBuilder: boolean = false, + builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): ts.Statement { + if (elseStatement) { + if (ts.isBlock(elseStatement)) { + elseStatement = processIfBlock(elseStatement, id + 1, log, isBuilder, isGlobalBuilder, builderParamsResult, isInRepeatTemplate); + } else if (ts.isIfStatement(elseStatement)) { + elseStatement = processInnerIfStatement(elseStatement, id + 1, log, isBuilder, isGlobalBuilder, builderParamsResult, isInRepeatTemplate); + } else { + elseStatement = ts.factory.createBlock([elseStatement], true); + elseStatement = processIfBlock(elseStatement as ts.Block, id + 1, log, isBuilder, isGlobalBuilder, builderParamsResult, isInRepeatTemplate); + } + } else if (partialUpdateConfig.partialUpdateMode) { + elseStatement = ts.factory.createBlock([ + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(IFELSEBRANCHUPDATEFUNCTION) + ), + undefined, + [ + ts.factory.createNumericLiteral(++id), + ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [], + true + ) + ) + ] + )) + ], true); + } + return elseStatement; +} + +function processIfBlock(block: ts.Block, id: number, log: LogInfo[], isBuilder: boolean = false, + isGlobalBuilder: boolean = false, builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): ts.Block { + return addIfBranchId(id, isGlobalBuilder, + processComponentBlock(block, false, log, false, isBuilder, COMPONENT_IF, undefined, isGlobalBuilder, builderParamsResult, false, isInRepeatTemplate)); +} + +function addIfBranchId(id: number, isGlobalBuilder: boolean = false, container: ts.Block): ts.Block { + let containerStatements: ts.Statement[]; + if (partialUpdateConfig.partialUpdateMode) { + containerStatements = [createIfBranchFunc(id, [...container.statements], isGlobalBuilder)]; + } else { + containerStatements = [createIfBranchId(id), ...container.statements]; + } + return ts.factory.updateBlock(container, containerStatements); +} + +function createIf(): ts.Identifier { + return ts.factory.createIdentifier(COMPONENT_IF); +} + +function createIfCreate(): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(createFunction(createIf(), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), ts.factory.createNodeArray([]))); +} + +function createIfPop(): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(createFunction(createIf(), + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null)); +} + +function createIfBranchId(id: number): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(createFunction(createIf(), + ts.factory.createIdentifier(COMPONENT_IF_BRANCH_ID_FUNCTION), + ts.factory.createNodeArray([ts.factory.createNumericLiteral(id)]))); +} + +function createIfBranchFunc(id: number, innerStatements: ts.Statement[], + isGlobalBuilder: boolean = false): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + isGlobalBuilder ? parentConditionalExpression() : ts.factory.createThis(), + ts.factory.createIdentifier(IFELSEBRANCHUPDATEFUNCTION)), undefined, + [ts.factory.createNumericLiteral(id), ts.factory.createArrowFunction(undefined, undefined, [], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createBlock(innerStatements, true))])); +} + +interface CreateResult { + newNode: ts.ExpressionStatement; + identifierNode: ts.Identifier; + isContainerComponent: boolean; + isButton: boolean; + needPop: boolean; +} + +function createComponent(node: ts.ExpressionStatement, type: string): CreateResult { + const res: CreateResult = { + newNode: node, + identifierNode: null, + isContainerComponent: false, + isButton: false, + needPop: false + }; + let identifierNode: ts.Identifier = ts.factory.createIdentifier(type); + let temp: any = node.expression; + while (temp && !ts.isIdentifier(temp) && temp.expression) { + temp = temp.expression; + } + if (temp && temp.parent && (ts.isCallExpression(temp.parent) || + ts.isEtsComponentExpression(temp.parent)) && ts.isIdentifier(temp)) { + if (temp.getText() === COMPONENT_BUTTON && type !== COMPONENT_POP_FUNCTION) { + res.isButton = true; + identifierNode = type === COMPONENT_CREATE_CHILD_FUNCTION ? + ts.factory.createIdentifier(COMPONENT_CREATE_CHILD_FUNCTION) : + ts.factory.createIdentifier(COMPONENT_CREATE_LABEL_FUNCTION); + } + if (NEEDPOP_COMPONENT.has(temp.getText())) { + res.needPop = true; + } + if (checkContainer(temp.getText(), temp.parent)) { + res.isContainerComponent = true; + } + res.newNode = type === COMPONENT_POP_FUNCTION ? + ts.factory.createExpressionStatement(createFunction(temp, identifierNode, null)) : + ts.factory.createExpressionStatement(createFunction(temp, identifierNode, checkArguments(temp, type))); + res.identifierNode = temp; + } + return res; +} + +function checkArguments(temp: ts.Identifier, type: string): ts.Expression[] { + const newArguments: ts.Expression[] = []; + if (CUSTOM_BUILDER_CONSTRUCTORS.has(temp.escapedText.toString())) { + temp.parent.arguments.forEach(argument => { + if (ts.isConditionalExpression(argument)) { + newArguments.push(processConditionalBuilder(argument, temp, type)); + } else if (isBuilderChangeNode(argument, temp, type)) { + newArguments.push(parseBuilderNode(argument, type)); + } else { + newArguments.push(argument); + } + }); + return newArguments; + } + return temp.getText() === 'XComponent' && type === COMPONENT_CREATE_FUNCTION && + projectConfig.moduleName && projectConfig.bundleName ? + // @ts-ignore + temp.parent.arguments.concat([ + ts.factory.createStringLiteral(`${projectConfig.bundleName}/${projectConfig.moduleName}`) + ]) : temp.parent.arguments; +} + +function checkContainer(name: string, node: ts.Node): boolean { + return BUILDIN_CONTAINER_COMPONENT.has(name) && (name !== 'XComponent' || + (node && node.arguments && node.arguments.length && + ts.isObjectLiteralExpression(node.arguments[0]) && node.arguments[0].properties && + checkComponentType(node.arguments[0].properties))); +} + +function checkComponentType(properties: ts.PropertyAssignment[]): boolean { + let flag: boolean = false; + properties.forEach(item => { + if (isXComponentContainer(item)) { + flag = true; + } + }); + return flag; +} + +function isXComponentContainer(item: ts.PropertyAssignment): boolean { + return item.name && ts.isIdentifier(item.name) && item.name.getText() === RESOURCE_NAME_TYPE && + item.initializer && ((ts.isStringLiteral(item.initializer) && + // value = 'component' + (item.initializer.getText() === XCOMPONENT_SINGLE_QUOTATION || + item.initializer.getText() === XCOMPONENT_DOUBLE_QUOTATION)) || + // value = 1 + (ts.isNumericLiteral(item.initializer) && item.initializer.getText() === '1') || + // value = XComponentType.COMPONENT + (ts.isPropertyAccessExpression(item.initializer) && item.initializer.expression && + ts.isIdentifier(item.initializer.expression) && item.initializer.name && + ts.isIdentifier(item.initializer.name) && item.initializer.expression.getText() === XCOMPONENTTYPE) && + item.initializer.name.getText() === XCOMPONENTTYPE_CONTAINER); +} + +interface AnimationInfo { + statement: ts.Statement, + kind: boolean, + hasAnimationAttr: boolean, +} + +export interface ComponentAttrInfo { + reuseId: ts.Node, + hasIdAttr: boolean, + attrCount: number, + reuse: string, +} + +export function bindComponentAttr(node: ts.ExpressionStatement, identifierNode: ts.Identifier, + newStatements: ts.Statement[], log: LogInfo[], reverse: boolean = true, + isStylesAttr: boolean = false, newImmutableStatements: ts.Statement[] = null, + isStyleFunction: boolean = false, componentAttrInfo: ComponentAttrInfo = null, + isReusableV2NodeAttr: boolean = false): void { + const isStylesUIComponent: boolean = validateStylesUIComponent(node, isStylesAttr); + let temp = node.expression; + const statements: ts.Statement[] = []; + const immutableStatements: ts.Statement[] = []; + const updateStatements: ts.Statement[] = []; + const lastStatement: AnimationInfo = { + statement: null, + kind: false, + hasAnimationAttr: false + }; + const isRecycleComponent: boolean = isRecycle(componentCollection.currentClassName); + const isReuseComponentInV2: boolean = isReuseInV2(componentCollection.currentClassName); + if (ts.isPropertyAccessExpression(temp) || isStylesUIComponent) { + log.push({ + type: isStylesUIComponent ? LogType.WARN :LogType.ERROR, + message: `'${node.getText()}' does not meet UI component syntax.`, + pos: node.getStart(), + code: '10905206' + }); + } + while (temp && ts.isCallExpression(temp) && temp.expression) { + let flag: boolean = false; + if (temp.expression && (validatePropertyAccessExpressionWithCustomBuilder(temp.expression) || + validateIdentifierWithCustomBuilder(temp.expression))) { + let propertyName: string = ''; + if (ts.isIdentifier(temp.expression)) { + propertyName = temp.expression.escapedText.toString(); + } else if (ts.isPropertyAccessExpression(temp.expression)) { + propertyName = temp.expression.name.escapedText.toString(); + } + switch (true) { + case BIND_POPUP_SET.has(propertyName): + temp = processBindPopupBuilder(temp); + break; + case BIND_DRAG_SET.has(propertyName): + temp = processDragStartBuilder(temp, propertyName); + break; + default: + temp = processCustomBuilderProperty(temp, identifierNode, propertyName); + } + } + if (ts.isPropertyAccessExpression(temp.expression) && + temp.expression.name && ts.isIdentifier(temp.expression.name) && + (!componentCollection.customComponents.has(temp.expression.name.getText()) || STYLES_ATTRIBUTE.has(temp.expression.name.getText()))) { + parseRecycleId(temp, temp.expression.name, componentAttrInfo, log, isReusableV2NodeAttr); + addComponentAttr(temp, temp.expression.name, lastStatement, statements, identifierNode, log, + isStylesAttr, immutableStatements, updateStatements, newImmutableStatements, + isRecycleComponent, isReuseComponentInV2, isStyleFunction); + temp = temp.expression.expression; + flag = true; + } else if (ts.isIdentifier(temp.expression)) { + if (!INNER_COMPONENT_NAMES.has(temp.expression.getText()) && + !GESTURE_TYPE_NAMES.has(temp.expression.getText()) && + !componentCollection.customComponents.has(temp.expression.getText())) { + parseRecycleId(temp, temp.expression.name, componentAttrInfo, log, isReusableV2NodeAttr); + addComponentAttr(temp, temp.expression, lastStatement, statements, identifierNode, log, + isStylesAttr, immutableStatements, updateStatements, newImmutableStatements, + isRecycleComponent, isReuseComponentInV2, isStyleFunction); + } + break; + } + if (!flag) { + temp = temp.expression; + } + } + if (lastStatement.statement && lastStatement.kind) { + statements.push(lastStatement.statement); + } + if ((!isRecycleComponent && !isReuseComponentInV2) || lastStatement.hasAnimationAttr) { + if (statements.length) { + reverse ? newStatements.push(...statements.reverse()) : newStatements.push(...statements); + } + } else { + if (updateStatements.length) { + reverse ? newStatements.push(...updateStatements.reverse()) : newStatements.push(...updateStatements); + } + if (newImmutableStatements && immutableStatements.length) { + reverse ? newImmutableStatements.push(...immutableStatements.reverse()) : newImmutableStatements.push(...immutableStatements); + } + } +} + +function validateStylesUIComponent(node: ts.ExpressionStatement, isStylesAttr: boolean): boolean { + return (ts.isIfStatement(node) || ts.isSwitchStatement(node)) && isStylesAttr; +} + +function parseRecycleId(node: ts.CallExpression, attr: ts.Identifier, componentAttrInfo: ComponentAttrInfo, log: LogInfo[], + isReusableV2NodeAttr: boolean = false): void { + if (componentAttrInfo) { + const attrName: string = attr.escapedText.toString(); + if (attrName === RECYCLE_REUSE_ID) { + logMessageCollection.checkUsageOfReuseIdAttribute(node, isReusableV2NodeAttr, log); + componentAttrInfo.reuseId = node.arguments[0]; + } else if (attrName === ATTRIBUTE_ID) { + componentAttrInfo.hasIdAttr = true; + } else if (attrName === REUSE_ATTRIBUTE) { + logMessageCollection.checkUsageOfReuseAttribute(node, isReusableV2NodeAttr, log); + if (ts.isObjectLiteralExpression(node.arguments[0]) && !!getReuseIdInReuse(node.arguments[0])) { + componentAttrInfo.reuse = getReuseIdInReuse(node.arguments[0]); + } else { + componentAttrInfo.reuse = ''; + } + } + componentAttrInfo.attrCount++; + } +} + +function getReuseIdInReuse(node: ts.ObjectLiteralExpression): string { + let reuse: string = ''; + if (node.properties && node.properties.length) { + node.properties.forEach((item: ts.ObjectLiteralElementLike) => { + if (ts.isPropertyAssignment(item) && item.name && ts.isIdentifier(item.name) && + item.name.getText() === RECYCLE_REUSE_ID && item.initializer && + ts.isArrowFunction(item.initializer) && item.initializer.body && + ts.isStringLiteral(item.initializer.body)) { + reuse = item.initializer.body.text; + } + }); + } + return reuse; +} + +function processCustomBuilderProperty(node: ts.CallExpression, identifierNode: ts.Identifier, + propertyName: string): ts.CallExpression { + const newArguments: ts.Expression[] = []; + node.arguments.forEach((argument: ts.Expression | ts.Identifier, index: number) => { + if (ts.isConditionalExpression(argument)) { + newArguments.push(processConditionalBuilder(argument, identifierNode, propertyName)); + } else if (isBuilderChangeNode(argument, identifierNode, propertyName)) { + newArguments.push(parseBuilderNode(argument, propertyName)); + } else { + newArguments.push(argument); + } + }); + node = ts.factory.updateCallExpression(node, node.expression, node.typeArguments, newArguments); + return node; +} + +function isBuilderChangeNode(argument: ts.Node, identifierNode: ts.Identifier, propertyName: string): boolean { + return ts.isPropertyAccessExpression(argument) && argument.name && ts.isIdentifier(argument.name) && + storedFileInfo.builderLikeCollection.has(argument.name.getText()) || + ts.isCallExpression(argument) && argument.expression && argument.expression.name && + ts.isIdentifier(argument.expression.name) && + storedFileInfo.builderLikeCollection.has(argument.expression.name.getText()) || ts.isIdentifier(argument) && + argument.escapedText && storedFileInfo.builderLikeCollection.has(argument.escapedText.toString()) || + ts.isObjectLiteralExpression(argument) && (BIND_OBJECT_PROPERTY.get(identifierNode.escapedText.toString()) && + BIND_OBJECT_PROPERTY.get(identifierNode.escapedText.toString()).has(propertyName) || + BIND_OBJECT_PROPERTY.get(ALL_COMPONENTS).has(propertyName)) || + ts.isCallExpression(argument) && argument.expression && ts.isIdentifier(argument.expression) && + storedFileInfo.builderLikeCollection.has(argument.expression.escapedText.toString()) || + isWrappedBuilder(argument as ts.PropertyAccessExpression) || isWrappedBuilderCallExpression(argument as ts.CallExpression); +} + +export function isWrappedBuilder(node: ts.PropertyAccessExpression): boolean { + if (projectConfig.minAPIVersion >= 11 && ts.isPropertyAccessExpression(node) && + node.name && ts.isIdentifier(node.name) && node.name.escapedText.toString() === WRAPBUILDER_BUILDERPROP && + globalProgram.checker.getTypeAtLocation(node.expression) && + globalProgram.checker.getTypeAtLocation(node.expression).symbol && + globalProgram.checker.getTypeAtLocation(node.expression).symbol.escapedName === WRAPPEDBUILDER_CLASS) { + return true; + } + return false; +} + +function isWrappedBuilderCallExpression(node: ts.CallExpression): boolean { + if (projectConfig.minAPIVersion >= 11 && ts.isCallExpression(node) && node.expression && + isWrappedBuilder(node.expression as ts.PropertyAccessExpression)) { + return true; + } + return false; +} + +function parseBuilderNode(node: ts.Node, propertyName: string): + ts.ObjectLiteralExpression | ts.CallExpression | ts.ArrowFunction { + if (isWrappedBuilder(node as ts.PropertyAccessExpression) || isPropertyAccessExpressionNode(node)) { + if (CUSTOM_BUILDER_PROPERTIES_WITHOUTKEY.has(propertyName)) { + return processPropertyBuilderWithoutKey(node as ts.PropertyAccessExpression); + } else { + return processPropertyBuilder(node as ts.PropertyAccessExpression); + } + } else if (ts.isIdentifier(node) && storedFileInfo.builderLikeCollection.has(node.escapedText.toString())) { + if (CUSTOM_BUILDER_PROPERTIES_WITHOUTKEY.has(propertyName)) { + return processIdentifierBuilderWithoutKey(node); + } else { + return processIdentifierBuilder(node); + } + } else if (isWrappedBuilderCallExpression(node as ts.CallExpression) || ts.isCallExpression(node)) { + if (CUSTOM_BUILDER_PROPERTIES_WITHOUTKEY.has(propertyName)) { + return getParsedBuilderAttrArgumentWithParamsWithoutKey(node); + } else { + return getParsedBuilderAttrArgumentWithParams(node); + } + } else if (ts.isObjectLiteralExpression(node)) { + return processObjectPropertyBuilder(node); + } + return undefined; +} + +export function processObjectPropertyBuilder(node: ts.ObjectLiteralExpression): ts.ObjectLiteralExpression { + const newProperties: ts.PropertyAssignment[] = []; + node.properties.forEach((property: ts.PropertyAssignment) => { + if (property.name && ts.isIdentifier(property.name) && + [CUSTOM_DIALOG_CONTROLLER_BUILDER, HEADER, INDICATORBUILDER, FOOTER, START, END, PREVIEW, TITLE].includes( + property.name.escapedText.toString()) && property.initializer) { + if (isPropertyAccessExpressionNode(property.initializer) || ts.isIdentifier(property.initializer) && + storedFileInfo.builderLikeCollection.has(property.initializer.escapedText.toString())) { + newProperties.push(ts.factory.updatePropertyAssignment(property, property.name, + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + property.initializer, + ts.factory.createIdentifier(BUILDER_ATTR_BIND) + ), + undefined, + [ts.factory.createThis()] + ))); + } else if (isGlobalBuilderCallExpressionNode(property.initializer) || + isInnerBuilderCallExpressionNode(property.initializer)) { + newProperties.push(transformBuilderCallExpression(property)); + } else if (ts.isObjectLiteralExpression(property.initializer)) { + newProperties.push(ts.factory.updatePropertyAssignment(property, property.name, + processObjectPropertyBuilder(property.initializer))); + } else { + newProperties.push(property); + } + } else { + newProperties.push(property); + } + }); + return ts.factory.updateObjectLiteralExpression(node, newProperties); +} + +function transDoubleDollarInCustomBuilder(node: ts.CallExpression): ts.Expression[] { + let name: string = ''; + if (node.expression && ts.isIdentifier(node.expression)) { + name = node.expression.escapedText.toString(); + } else if (node.expression && ts.isPropertyAccessExpression(node.expression) && + node.expression.name && ts.isIdentifier(node.expression.name)) { + name = node.expression.name.escapedText.toString(); + } + if (node.arguments.length === 1 && ts.isObjectLiteralExpression(node.arguments[0])) { + return [ts.factory.createCallExpression( + ts.factory.createIdentifier(BUILDER_PARAM_PROXY), + undefined, + [ + ts.factory.createStringLiteral(name), + traverseBuilderParams(node.arguments[0], storedFileInfo.processBuilder) + ] + )]; + } else { + return node.arguments; + } +} + +function transformBuilderCallExpression(property: ts.PropertyAssignment): ts.PropertyAssignment { + const newArguments: ts.Expression[] = transDoubleDollarInCustomBuilder(property.initializer as ts.CallExpression); + return ts.factory.updatePropertyAssignment(property, property.name, + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + property.initializer.expression, + ts.factory.createIdentifier(BUILDER_ATTR_BIND) + ), + undefined, + [ts.factory.createThis(), ...(newArguments || [])] + )); +} + +function isInnerBuilderCallExpressionNode(node: ts.Node): boolean { + return ts.isCallExpression(node) && node.expression && isPropertyAccessExpressionNode(node.expression); +} + +function isGlobalBuilderCallExpressionNode(node: ts.Node): boolean { + return ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && + CUSTOM_BUILDER_METHOD.has(node.expression.escapedText.toString()); +} + +function isPropertyAccessExpressionNode(node: ts.Node): boolean { + return ts.isPropertyAccessExpression(node) && node.expression && + node.expression.kind === ts.SyntaxKind.ThisKeyword && node.name && ts.isIdentifier(node.name) && + storedFileInfo.builderLikeCollection.has(node.name.escapedText.toString()); +} + +function processBindPopupBuilder(node: ts.CallExpression): ts.CallExpression { + const newArguments: ts.Expression[] = []; + node.arguments.forEach((argument: ts.ObjectLiteralExpression, index: number) => { + if (index === 1 && ts.isObjectLiteralExpression(argument)) { + // @ts-ignore + newArguments.push(processBindPopupBuilderProperty(argument)); + } else { + newArguments.push(argument); + } + }); + node = ts.factory.updateCallExpression(node, node.expression, node.typeArguments, newArguments); + return node; +} + +function processDragStartBuilder(node: ts.CallExpression, propertyName: string): ts.CallExpression { + const newStatements: ts.Statement[] = []; + if (isNodeFunction(node)) { + // @ts-ignore + for (let i = 0; i < node.arguments[0].body.statements.length; i++) { + // @ts-ignore + const statement: ts.Statement = node.arguments[0].body.statements[i]; + newStatements.push(checkStatement(statement, propertyName)); + } + node = ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [ts.factory.updateArrowFunction( + // @ts-ignore + node.arguments[0], undefined, undefined, node.arguments[0].parameters, node.arguments[0].type, + // @ts-ignore + node.arguments[0].equalsGreaterThanToken, ts.factory.updateBlock(node.arguments[0].body, newStatements))]); + } + return node; +} + +function isNodeFunction(node: ts.CallExpression): boolean { + return node.arguments && node.arguments.length && ts.isArrowFunction(node.arguments[0]) && node.arguments[0].body && + ts.isBlock(node.arguments[0].body); +} + +function checkStatement(statement: ts.Statement, propertyName: string): ts.Statement { + if (ts.isReturnStatement(statement)) { + if (ts.isObjectLiteralExpression(statement.expression)) { + const newProperties: ts.ObjectLiteralElementLike[] = []; + for (let j = 0; j < statement.expression.properties.length; j++) { + let property: ts.ObjectLiteralElementLike = statement.expression.properties[j]; + property = checkProperty(property, propertyName); + newProperties.push(property); + } + return ts.factory.createReturnStatement(ts.factory.createObjectLiteralExpression(newProperties)); + } else { + let initializer: ts.Expression = statement.expression; + initializer = processInitializer(initializer, propertyName); + return ts.factory.updateReturnStatement(statement, initializer); + } + } else { + return statement; + } +} + +function checkProperty(property: ts.ObjectLiteralElementLike, propertyName: string): ts.ObjectLiteralElementLike { + if (isPropertyFunction(property)) { + let initializer: ts.Expression = property.initializer; + initializer = processInitializer(initializer, propertyName); + property = ts.factory.createPropertyAssignment(property.name, initializer); + } + return property; +} + +function processInitializer(initializer: ts.Expression, propertyName: string): ts.Expression { + if (initializer && ts.isConditionalExpression(initializer)) { + return processConditionalBuilder(initializer, ts.factory.createIdentifier(CUSTOM_COMPONENT_DEFAULT), + propertyName); + } else if (isBuilderChangeNode(initializer, ts.factory.createIdentifier(CUSTOM_COMPONENT_DEFAULT), + propertyName)) { + return parseBuilderNode(initializer, propertyName); + } + return initializer; +} + +function isPropertyFunction(property: ts.ObjectLiteralElementLike): boolean { + return ts.isPropertyAssignment(property) && property.name && ts.isIdentifier(property.name) && + property.name.escapedText.toString() === BUILDER_ATTR_NAME; +} + +function processBindPopupBuilderProperty(node: ts.ObjectLiteralExpression): ts.ObjectLiteralExpression { + const newProperties: ts.PropertyAssignment[] = []; + node.properties.forEach((property: ts.PropertyAssignment, index: number) => { + if (property.name && ts.isIdentifier(property.name) && property.initializer && + property.name.escapedText.toString() === CUSTOM_DIALOG_CONTROLLER_BUILDER) { + let initializer: ts.Expression = property.initializer; + initializer = processInitializer(initializer, BIND_POPUP); + newProperties.push(ts.factory.updatePropertyAssignment(property, property.name, initializer)); + } else { + newProperties.push(property); + } + }); + return ts.factory.updateObjectLiteralExpression(node, newProperties); +} + +function processConditionalBuilder(initializer: ts.ConditionalExpression, identifierNode: ts.Identifier, + propertyName: string): ts.ConditionalExpression { + let whenTrue: ts.Expression = initializer.whenTrue; + let whenFalse: ts.Expression = initializer.whenFalse; + if (isBuilderChangeNode(initializer.whenTrue, identifierNode, propertyName)) { + whenTrue = parseBuilderNode(initializer.whenTrue, propertyName); + } + if (isBuilderChangeNode(initializer.whenFalse, identifierNode, propertyName)) { + whenFalse = parseBuilderNode(initializer.whenFalse, propertyName); + } + return ts.factory.createConditionalExpression( + initializer.condition, + initializer.questionToken, + whenTrue, + initializer.colonToken, + whenFalse + ); +} + +function processPropertyBuilder(node: ts.PropertyAccessExpression): ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(BUILDER_ATTR_NAME), + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + node, + ts.factory.createIdentifier(BUILDER_ATTR_BIND) + ), + undefined, + [ts.factory.createThis()] + ) + ) + ]); +} + +function processPropertyBuilderWithoutKey(node: ts.PropertyAccessExpression): ts.CallExpression { + return ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + node, + ts.factory.createIdentifier(BUILDER_ATTR_BIND) + ), + undefined, + [ts.factory.createThis()] + ); +} + +function processIdentifierBuilder(node: ts.Identifier): ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(BUILDER_ATTR_NAME), + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(node, ts.factory.createIdentifier(BUILDER_ATTR_BIND)), + undefined, [ts.factory.createThis()] + ) + ) + ]); +} + +function processIdentifierBuilderWithoutKey(node: ts.Identifier): ts.CallExpression { + return ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(node, ts.factory.createIdentifier(BUILDER_ATTR_BIND)), + undefined, [ts.factory.createThis()] + ); +} + +function getParsedBuilderAttrArgumentWithParams(node: ts.CallExpression): + ts.ObjectLiteralExpression { + const newArguments: ts.Expression[] = transDoubleDollarInCustomBuilder(node); + return ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(BUILDER_ATTR_NAME), + ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(node.expression, ts.factory.createIdentifier(CALL) + ), undefined, [ts.factory.createThis(), ...newArguments]))], + true + ) + ) + ) + ]); +} + +function getParsedBuilderAttrArgumentWithParamsWithoutKey(node: ts.CallExpression): + ts.ArrowFunction { + const newArguments: ts.Expression[] = transDoubleDollarInCustomBuilder(node); + return ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(node.expression, ts.factory.createIdentifier(CALL) + ), undefined, [ts.factory.createThis(), ...newArguments]))], + true + ) + ); +} + +function validatePropertyAccessExpressionWithCustomBuilder(node: ts.Node): boolean { + return ts.isPropertyAccessExpression(node) && node.name && + ts.isIdentifier(node.name) && CUSTOM_BUILDER_PROPERTIES.has(node.name.escapedText.toString()); +} + +function validateIdentifierWithCustomBuilder(node: ts.Node): boolean { + return ts.isIdentifier(node) && CUSTOM_BUILDER_PROPERTIES.has(node.escapedText.toString()); +} + +function createArrowFunctionForDollar($$varExp: ts.Expression): ts.ArrowFunction { + return ts.factory.createArrowFunction( + undefined, undefined, + [ts.factory.createParameterDeclaration( + undefined, undefined, + ts.factory.createIdentifier($$_NEW_VALUE), + undefined, undefined, undefined + )], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + $$varExp, + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + ts.factory.createIdentifier($$_NEW_VALUE) + ))], + false + ) + ); +} + +function updateArgumentForExclamation(argument): ts.Expression { + if (ts.isNonNullExpression(argument) && ts.isNonNullExpression(argument.expression)) { + return argument.expression.expression; + } + return argument; +} + +function updateArgumentForDollar(argument): ts.Expression { + if (ts.isElementAccessExpression(argument)) { + return ts.factory.updateElementAccessExpression( + argument, updateArgumentForDollar(argument.expression), argument.argumentExpression); + } else if (ts.isIdentifier(argument)) { + if (argument.getText() === $$_THIS) { + return ts.factory.createThis(); + } else if (argument.getText().match(/^\$\$(.|\n)+/)) { + return ts.factory.createIdentifier(argument.getText().replace(/\$\$/, '')); + } + } else if (ts.isPropertyAccessExpression(argument)) { + return ts.factory.updatePropertyAccessExpression( + argument, updateArgumentForDollar(argument.expression), argument.name); + } + return argument; +} + +function verifyComponentId(temp: any, node: ts.Identifier, propName: string, + log: LogInfo[]): void { + if (!newsupplement.isAcceleratePreview && propName === ATTRIBUTE_ID && + ts.isStringLiteral(temp.arguments[0])) { + const id: string = temp.arguments[0].text; + const posOfNode: ts.LineAndCharacter = transformLog.sourceFile + .getLineAndCharacterOfPosition(getRealNodePos(node)); + const curFileName: string = transformLog.sourceFile.fileName.replace(/\.ts$/, ''); + const rPath: string = path.resolve(projectConfig.projectPath, curFileName) + .replace(/\\+/g, '/'); + const rLine: number = posOfNode.line + 1; + const rCol: number = posOfNode.character + 1; + if (ID_ATTRS.has(id)) { + const idInfo: Map = ID_ATTRS.get(id); + if (!(idInfo.get('path') === rPath && + idInfo.get('line') === rLine && + idInfo.get('col') === rCol)) { + log.push({ + type: LogType.WARN, + message: `The current component id "${id}" is duplicate with ` + + `${idInfo.get('path')}:${idInfo.get('line')}:${idInfo.get('col')}.`, + pos: node.pos + }); + } + } else { + ID_ATTRS.set(id, new Map().set('path', rPath) + .set('line', rLine) + .set('col', rCol)); + } + } +} + +class StyleResult { + doubleDollar: boolean = false; + doubleExclamation: boolean = false; +} + +function isDoubleBind(styleResult: StyleResult, isStylesAttr: boolean, identifierNode: ts.Identifier, + propName: string, temp: any): boolean { + if (isDoubleDollarToChange(isStylesAttr, identifierNode, propName, temp)) { + styleResult.doubleDollar = true; + return true; + } else if (isDoubleExclamationToChange(isStylesAttr, identifierNode, propName, temp)) { + styleResult.doubleExclamation = true; + return true; + } + return false; +} + +function addComponentAttr(temp, node: ts.Identifier, lastStatement, + statements: ts.Statement[], identifierNode: ts.Identifier, log: LogInfo[], + isStylesAttr: boolean, immutableStatements: ts.Statement[], updateStatements: ts.Statement[], + newImmutableStatements: ts.Statement[] = null, isRecycleComponent: boolean = false, + isReuseComponentInV2: boolean = false, isStyleFunction: boolean = false): void { + const styleResult: StyleResult = new StyleResult(); + const propName: string = node.getText(); + verifyComponentId(temp, node, propName, log); + const extendType: ExtendType = {type: ''}; + if (propName === ATTRIBUTE_ANIMATION) { + const animationNullNode: ts.ExpressionStatement = ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(GLOBAL_CONTEXT), node, + // @ts-ignore + [ts.factory.createNull()])); + if (!lastStatement.statement) { + if (!(temp.arguments.length === 1 && + temp.arguments[0].kind === ts.SyntaxKind.NullKeyword)) { + statements.push(animationNullNode); + } + } else { + statements.push(lastStatement.statement, animationNullNode); + } + lastStatement.statement = ts.factory.createExpressionStatement(createFunction( + ts.factory.createIdentifier(GLOBAL_CONTEXT), node, temp.arguments)); + lastStatement.kind = false; + lastStatement.hasAnimationAttr = true; + } else if (GESTURE_ATTRS.has(propName)) { + parseGesture(temp, propName, statements, log, updateStatements); + lastStatement.kind = true; + } else if (isExtendFunctionNode(identifierNode, propName, extendType)) { + if (newsupplement.isAcceleratePreview) { + log.push({ + type: LogType.ERROR, + message: `Doesn't support Extend function now`, + pos: temp.getStart(), + code: '10906205' + }); + } + let functionName: string = ''; + if (extendType.type === CHECK_COMPONENT_EXTEND_DECORATOR) { + functionName = `__${identifierNode.escapedText.toString()}__${propName}`; + } else { + functionName = propName; + } + const extendNode: ts.Statement = ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createIdentifier(functionName), undefined, + extendType.type === CHECK_COMPONENT_EXTEND_DECORATOR ? + temp.arguments : + [ + ...temp.arguments, ts.factory.createIdentifier(ELMTID), + ts.factory.createIdentifier(ISINITIALRENDER), + ts.factory.createThis() + ] + )); + statements.push(extendNode); + updateStatements.push(extendNode); + lastStatement.kind = true; + } else if (propName === ATTRIBUTE_STATESTYLES) { + if (temp.arguments.length === 1 && ts.isObjectLiteralExpression(temp.arguments[0])) { + statements.push(createViewStackProcessor(temp, true)); + if (isRecycleComponent) { + updateStatements.push(createViewStackProcessor(temp, true)); + } + traverseStateStylesAttr(temp, statements, identifierNode, log, updateStatements, + newImmutableStatements, isRecycleComponent); + lastStatement.kind = true; + } else { + validateStateStyleSyntax(temp, log); + } + } else if (GLOBAL_STYLE_FUNCTION.has(propName) || INNER_STYLE_FUNCTION.has(propName)) { + const styleBlock: ts.Block = + INNER_STYLE_FUNCTION.get(propName) || GLOBAL_STYLE_FUNCTION.get(propName); + if (styleBlock.statements.length > 0) { + bindComponentAttr(styleBlock.statements[0] as ts.ExpressionStatement, identifierNode, + statements, log, false, true, newImmutableStatements); + if (isRecycleComponent) { + bindComponentAttr(styleBlock.statements[0] as ts.ExpressionStatement, identifierNode, + updateStatements, log, false, true, newImmutableStatements, true); + } + } + lastStatement.kind = true; + } else if (isDoubleBind(styleResult, isStylesAttr, identifierNode, propName, temp)) { + const argumentsArr: ts.Expression[] = []; + styleResult.doubleDollar ? classifyArgumentsNum(temp.arguments, argumentsArr, propName, identifierNode) : + classifyArgumentsNumV2(temp.arguments, argumentsArr, propName, identifierNode); + const doubleDollarNode: ts.Statement = ts.factory.createExpressionStatement( + createFunction(identifierNode, node, argumentsArr)); + statements.push(doubleDollarNode); + updateStatements.push(doubleDollarNode); + lastStatement.kind = true; + } else { + temp = loopEtscomponent(temp, isStylesAttr); + if (propName !== RECYCLE_REUSE_ID && propName !== REUSE_ATTRIBUTE) { + let isAttributeModifier: boolean = false; + if ([ATTRIBUTE_ATTRIBUTE_MODIFIER, ATTRIBUTE_CONTENT_MODIFIER, + ATTRIBUTE_MENUITEM_CONTENT_MODIFIER].includes(propName)) { + isAttributeModifier = true; + } + const attrStatement: ts.Statement = ts.factory.createExpressionStatement( + createFunction(identifierNode, node, temp.arguments, isAttributeModifier)); + statements.push(attrStatement); + if ((isRecycleComponent || isReuseComponentInV2) && (!isStylesAttr || isStyleFunction) && + !isGestureType(identifierNode) && filterRegularAttrNode(temp.arguments)) { + immutableStatements.push(attrStatement); + } else { + updateStatements.push(attrStatement); + } + } + lastStatement.kind = true; + } +} + +function isGestureType(node: ts.Identifier): boolean { + return GESTURE_TYPE_NAMES.has(node.escapedText.toString()); +} + +function filterRegularAttrNode(argumentsNode: ts.NodeArray): boolean { + return argumentsNode.every((argument: ts.Expression) => { + return isRegularAttrNode(argument); + }); +} + +type AttrResult = { isRegularNode: boolean }; +function isRegularAttrNode(node: ts.Expression): boolean { + if (ts.isObjectLiteralExpression(node)) { + return node.properties.every((propNode: ts.PropertyAssignment) => { + if (propNode.initializer) { + return isRegularAttrNode(propNode.initializer); + } + return false; + }); + } + if (ts.isArrayLiteralExpression(node)) { + return node.elements.every((child: ts.Expression) => { + return isRegularAttrNode(child); + }); + } + // literal e.g. 'hello', 1, true, false, () => {} + if (isLiteralNode(node)) { + return true; + } + // enum e.g. Color.Red + if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && + ts.isIdentifier(node.name)) { + if (enumCollection.has(node.expression.escapedText.toString())) { + return true; + } + if (globalProgram.checker) { + const type: ts.Type = globalProgram.checker.getTypeAtLocation(node); + /* Enum */ + if (type.flags & (32 | 1024)) { + return true; + } + } + return false; + } + // regular variable, e.g. this.regularValue + const result: AttrResult = { isRegularNode: false }; + if (ts.isPropertyAccessExpression(node)) { + traversePropNode(node, result); + } + return result.isRegularNode || false; +} + +function isLiteralNode(node: ts.Expression): boolean { + return ts.isStringLiteral(node) || ts.isNumericLiteral(node) || ts.isArrowFunction(node) || + [ts.SyntaxKind.TrueKeyword, ts.SyntaxKind.FalseKeyword].includes(node.kind); +} + +function isSimpleType(node: ts.PropertyAccessExpression): boolean { + const symbol: ts.Symbol = getSymbolIfAliased(node); + const simpleTypeCollection: ts.SyntaxKind[] = [ts.SyntaxKind.StringKeyword, ts.SyntaxKind.NumberKeyword, + ts.SyntaxKind.BooleanKeyword]; + if (symbol && symbol.declarations && symbol.declarations.length === 1 && ts.isPropertyDeclaration(symbol.declarations[0]) && + symbol.declarations[0].type && symbol.declarations[0].type.kind && simpleTypeCollection.includes(symbol.declarations[0].type.kind)) { + return true; + } + return false; +} + +function traversePropNode(node: ts.PropertyAccessExpression, result: AttrResult): void { + const structInfo: StructInfo = processStructComponentV2.getOrCreateStructInfo(componentCollection.currentClassName); + if (structInfo.isComponentV2 && node.expression.kind === ts.SyntaxKind.ThisKeyword && ts.isIdentifier(node.name) && + structInfo.regularSet.has(node.name.escapedText.toString()) && isSimpleType(node)) { + result.isRegularNode = true; + return; + } + if (!structInfo.isComponentV2 && node.expression.kind === ts.SyntaxKind.ThisKeyword && ts.isIdentifier(node.name) && + regularCollection.get(componentCollection.currentClassName).has(node.name.escapedText.toString())) { + result.isRegularNode = true; + return; + } + if (ts.isPropertyAccessExpression(node.expression)) { + traversePropNode(node.expression, result); + } +} + +function isDoubleDollarToChange(isStylesAttr: boolean, identifierNode: ts.Identifier, + propName: string, temp): boolean { + return !isStylesAttr && + PROPERTIES_ADD_DOUBLE_DOLLAR.has(identifierNode.escapedText.toString()) && + PROPERTIES_ADD_DOUBLE_DOLLAR.get(identifierNode.escapedText.toString()).has(propName) || + STYLE_ADD_DOUBLE_DOLLAR.has(propName) && temp.arguments.length && temp.arguments[0] ? + temp.arguments[0].getText().match(/^(?!\$\$\.)\$\$(.|\n)+/) !== null : + false; +} + +function isDoubleExclamationToChange(isStylesAttr: boolean, identifierNode: ts.Identifier, propName: string, temp: ts.CallExpression): boolean { + return !isStylesAttr && + (STYLE_ADD_DOUBLE_EXCLAMATION.has(propName) && temp.arguments.length && temp.arguments[0] || + PROPERTIES_ADD_DOUBLE_EXCLAMATION.has(identifierNode.escapedText.toString()) && + PROPERTIES_ADD_DOUBLE_EXCLAMATION.get(identifierNode.escapedText.toString()).has(propName)) && + ts.isNonNullExpression(temp.arguments[0]) && ts.isNonNullExpression(temp.arguments[0].expression) && + !ts.isNonNullExpression(temp.arguments[0].expression.expression); +} + +function isHaveDoubleDollar(param: ts.PropertyAssignment, name: string): boolean { + return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) && + PROPERTIES_ADD_DOUBLE_DOLLAR.get(name).has(param.name.getText()) && param.initializer && + param.initializer.getText().match(/^(?!\$\$\.)\$\$(.|\n)+/) !== null; +} + +function isHaveDoubleExclamation(param: ts.PropertyAssignment, name: string): boolean { + return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) && + PROPERTIES_ADD_DOUBLE_EXCLAMATION.has(name) && PROPERTIES_ADD_DOUBLE_EXCLAMATION.get(name).has(param.name.getText()) && + param.initializer && ts.isNonNullExpression(param.initializer) && ts.isNonNullExpression(param.initializer.expression) && + !ts.isNonNullExpression(param.initializer.expression.expression); +} + +function loopEtscomponent(node: any, isStylesAttr: boolean): ts.Node { + node.arguments.forEach((item: ts.Node, index: number) => { + if (ts.isEtsComponentExpression(item)) { + node.arguments[index] = ts.factory.createCallExpression( + item.expression, undefined, item.arguments); + } else if ((ts.isCallExpression(item) || ts.isNewExpression(item)) && + !newsupplement.isAcceleratePreview) { + node.arguments[index] = ts.visitEachChild(item, + changeEtsComponentKind, contextGlobal); + } + }); + return node; +} + +function changeEtsComponentKind(node: ts.Node): ts.Node { + if (ts.isEtsComponentExpression(node)) { + node.kind = 204; + return node; + } + return ts.visitEachChild(node, changeEtsComponentKind, contextGlobal); +} + +function classifyArgumentsNum(args, argumentsArr: ts.Expression[], propName: string, + identifierNode: ts.Identifier): void { + if (STYLE_ADD_DOUBLE_DOLLAR.has(propName) && args.length >= 2) { + const varExp: ts.Expression = updateArgumentForDollar(args[0]); + argumentsArr.push(generateObjectForDollar(varExp), ...args.slice(1)); + } else if (STYLE_ADD_DOUBLE_EXCLAMATION.has(propName) && args.length >= 2) { + const varExp: ts.Expression = updateArgumentForExclamation(args[0]); + argumentsArr.push(generateObjectForExclamation(varExp), ...args.slice(1)); + } else if (PROPERTIES_ADD_DOUBLE_DOLLAR.has(identifierNode.escapedText.toString()) && args.length === 1 && + PROPERTIES_ADD_DOUBLE_DOLLAR.get(identifierNode.escapedText.toString()).has(propName) || + STYLE_ADD_DOUBLE_DOLLAR.has(propName) && args.length === 1) { + const varExp: ts.Expression = updateArgumentForDollar(args[0]); + argumentsArr.push(varExp, createArrowFunctionForDollar(varExp)); + } +} + +function classifyArgumentsNumV2(args: any, argumentsArr: ts.Expression[], propName: string, + identifierNode: ts.Identifier): void { + const componentName: string = identifierNode.escapedText.toString(); + if (STYLE_ADD_DOUBLE_EXCLAMATION.has(propName) && args.length || + PROPERTIES_ADD_DOUBLE_EXCLAMATION.has(componentName) && args.length === 1 && + PROPERTIES_ADD_DOUBLE_EXCLAMATION.get(componentName).has(propName)) { + const varExp: ts.Expression = updateArgumentForExclamation(args[0]); + argumentsArr.push(generateObjectForExclamation(varExp), ...args.slice(1)); + } +} + +function generateObjectForExclamation(varExp: ts.Expression): ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier($$_VALUE), + varExp), + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier($_VALUE), + ts.factory.createArrowFunction( + undefined, undefined, + [ts.factory.createParameterDeclaration( + undefined, undefined, + ts.factory.createIdentifier($$_NEW_VALUE), + undefined, undefined, undefined + )], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + varExp, + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + ts.factory.createIdentifier($$_NEW_VALUE) + ))], false) + ))], false); +} + +function generateObjectForDollar(varExp: ts.Expression): ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression( + [ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier($$_VALUE), + varExp + ), + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier($$_CHANGE_EVENT), + createArrowFunctionForDollar(varExp) + ) + ], + false + ); +} + +function generateFunctionPropertyAssignmentForExclamation(name: string, varExp: ts.Expression): ts.PropertyAssignment { + return ts.factory.createPropertyAssignment( + ts.factory.createIdentifier('$' + name), + createArrowFunctionForDollar(varExp) + ); +} + +function createViewStackProcessor(item, endViewStack: boolean): ts.ExpressionStatement { + const argument: ts.StringLiteral[] = []; + if (!endViewStack && item.name) { + argument.push(ts.factory.createStringLiteral(item.name.getText())); + } + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(VIEW_STACK_PROCESSOR), + ts.factory.createIdentifier(VISUAL_STATE) + ), + undefined, + argument + )); +} + +function traverseStateStylesAttr(temp, statements: ts.Statement[], + identifierNode: ts.Identifier, log: LogInfo[], updateStatements: ts.Statement[], + newImmutableStatements: ts.Statement[] = null, isRecycleComponent: boolean = false): void { + temp.arguments[0].properties.reverse().forEach((item: ts.PropertyAssignment) => { + if (ts.isPropertyAccessExpression(item.initializer) && + item.initializer.expression.getText() === THIS && + INNER_STYLE_FUNCTION.get(item.initializer.name.getText())) { + const name: string = item.initializer.name.getText(); + bindComponentAttr(INNER_STYLE_FUNCTION.get(name).statements[0] as ts.ExpressionStatement, + identifierNode, statements, log, false, true, newImmutableStatements); + if (isRecycleComponent) { + bindComponentAttr(INNER_STYLE_FUNCTION.get(name).statements[0] as ts.ExpressionStatement, + identifierNode, updateStatements, log, false, true, newImmutableStatements); + } + } else if (ts.isIdentifier(item.initializer) && + GLOBAL_STYLE_FUNCTION.get(item.initializer.getText())) { + const name: string = item.initializer.getText(); + bindComponentAttr(GLOBAL_STYLE_FUNCTION.get(name).statements[0] as ts.ExpressionStatement, + identifierNode, statements, log, false, true, newImmutableStatements); + if (isRecycleComponent) { + bindComponentAttr(GLOBAL_STYLE_FUNCTION.get(name).statements[0] as ts.ExpressionStatement, + identifierNode, updateStatements, log, false, true, newImmutableStatements); + } + } else if (ts.isObjectLiteralExpression(item.initializer) && + item.initializer.properties.length === 1 && + ts.isPropertyAssignment(item.initializer.properties[0])) { + bindComponentAttr(ts.factory.createExpressionStatement( + item.initializer.properties[0].initializer), identifierNode, statements, log, false, true, + newImmutableStatements); + if (isRecycleComponent) { + bindComponentAttr(ts.factory.createExpressionStatement( + item.initializer.properties[0].initializer), identifierNode, updateStatements, log, false, true, + newImmutableStatements); + } + } else { + if (!(ts.isObjectLiteralExpression(item.initializer) && item.initializer.properties.length === 0)) { + validateStateStyleSyntax(temp, log); + } + } + if (item.name) { + const viewNode: ts.Statement = createViewStackProcessor(item, false); + statements.push(viewNode); + if (isRecycleComponent) { + updateStatements.push(viewNode); + } + } + }); +} + +interface ExtendType { + type: string +} + +function isExtendFunctionNode(identifierNode: ts.Identifier, propName: string, + extendType: ExtendType): boolean { + const componentName: string = identifierNode.escapedText.toString(); + if (EXTEND_ATTRIBUTE.has(componentName) && [...EXTEND_ATTRIBUTE.get(componentName)].includes(propName)) { + extendType.type = CHECK_COMPONENT_EXTEND_DECORATOR; + return true; + } + const animatableExtendAttribute: Map> = + storedFileInfo.getCurrentArkTsFile().animatableExtendAttribute; + if (animatableExtendAttribute.has(componentName) && + [...animatableExtendAttribute.get(componentName)].includes(propName)) { + extendType.type = CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR; + return true; + } + return false; +} + +const gestureMap: Map = new Map([ + [PRIORITY_GESTURE_ATTRIBUTE, GESTURE_ENUM_VALUE_HIGH], + [PARALLEL_GESTURE_ATTRIBUTE, GESTURE_ENUM_VALUE_PARALLEL], + [GESTURE_ATTRIBUTE, GESTURE_ENUM_VALUE_LOW] +]); + +function parseGesture(node: ts.CallExpression, propName: string, statements: ts.Statement[], + log: LogInfo[], updateStatements: ts.Statement[]): void { + const popNode: ts.Statement = ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_GESTURE), + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null)); + statements.push(popNode); + updateStatements.push(popNode); + parseGestureInterface(node, statements, log, updateStatements); + const argumentArr: ts.NodeArray = ts.factory.createNodeArray( + [ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(GESTURE_ENUM_KEY), + ts.factory.createIdentifier(gestureMap.get(propName))) + ] + ); + if (node.arguments && node.arguments.length > 1 && + ts.isPropertyAccessExpression(node.arguments[1])) { + // @ts-ignore + argumentArr.push(node.arguments[1]); + } + const createNode: ts.Statement = ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_GESTURE), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), argumentArr)); + statements.push(createNode); + updateStatements.push(createNode); +} + +function processGestureType(node: ts.CallExpression, statements: ts.Statement[], log: LogInfo[], + updateStatements: ts.Statement[], reverse: boolean = false): void { + const newStatements: ts.Statement[] = []; + const newNode: ts.ExpressionStatement = ts.factory.createExpressionStatement(node); + let temp = node.expression; + while (temp && !ts.isIdentifier(temp) && temp.expression) { + temp = temp.expression; + } + if (temp && temp.parent && ts.isCallExpression(temp.parent) && ts.isIdentifier(temp) && + GESTURE_TYPE_NAMES.has(temp.escapedText.toString())) { + newStatements.push(ts.factory.createExpressionStatement( + createFunction(temp, ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); + if (temp.escapedText.toString() === COMPONENT_GESTURE_GROUP) { + const gestureStatements: ts.Statement[] = []; + parseGestureInterface(temp.parent, gestureStatements, log, [], true); + newStatements.push(...gestureStatements.reverse()); + bindComponentAttr(newNode, temp, newStatements, log, false); + let argumentArr: ts.NodeArray = null; + if (temp.parent.arguments && temp.parent.arguments.length) { + // @ts-ignore + argumentArr = ts.factory.createNodeArray([temp.parent.arguments[0]]); + } + newStatements.push(ts.factory.createExpressionStatement( + createFunction(temp, ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), argumentArr))); + } else { + bindComponentAttr(newNode, temp, newStatements, log, false); + newStatements.push(ts.factory.createExpressionStatement( + createFunction(temp, ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), temp.parent.arguments))); + } + } + if (newStatements.length) { + reverse ? statements.push(...newStatements.reverse()) : statements.push(...newStatements); + reverse ? updateStatements.push(...newStatements.reverse()) : updateStatements.push(...newStatements); + } +} + +function parseGestureInterface(node: ts.CallExpression, statements: ts.Statement[], log: LogInfo[], + updateStatements: ts.Statement[], reverse: boolean = false): void { + if (node.arguments && node.arguments.length) { + node.arguments.forEach((item: ts.Node) => { + if (ts.isCallExpression(item)) { + processGestureType(item, statements, log, updateStatements, reverse); + } + }); + } +} + +export function getName(node: ts.ExpressionStatement | ts.Expression): string { + // @ts-ignore + let temp = node.expression; + let name: string; + while (temp) { + if (ts.isIdentifier(temp) && temp.parent && (ts.isCallExpression(temp.parent) || + ts.isEtsComponentExpression(temp.parent))) { + name = temp.escapedText.toString(); + break; + } else if (ts.isPropertyAccessExpression(temp) && temp.name && ts.isIdentifier(temp.name) && + isCustomAttributes(temp)) { + name = temp.name.escapedText.toString(); + break; + } + temp = temp.expression; + } + return name; +} + +function isCustomAttributes(temp: ts.PropertyAccessExpression): boolean { + if (temp.expression && temp.expression.getText() === THIS) { + return true; + } else if (temp.expression && ts.isIdentifier(temp.expression) && temp.expression.getText() === $$ && + builderTypeParameter.params.includes(temp.expression.getText())) { + return true; + } else { + return !BUILDIN_STYLE_NAMES.has(temp.name.escapedText.toString()); + } +} + +export function isAttributeNode(node: ts.ExpressionStatement): boolean { + let temp: any = node.expression; + let name: string; + while (temp) { + if (ts.isCallExpression(temp) && temp.expression && ts.isIdentifier(temp.expression)) { + name = temp.expression.escapedText.toString(); + break; + } + temp = temp.expression; + } + return BUILDIN_STYLE_NAMES.has(name); +} + +enum ComponentType { + innerComponent, + customComponent, + forEachComponent, + customBuilderMethod, + builderParamMethod, + function, + builderTypeFunction, + repeatComponent +} + +function isEtsComponent(node: ts.ExpressionStatement): boolean { + let isEtsComponent: boolean = false; + let temp: any = node.expression; + while (temp) { + if (ts.isEtsComponentExpression(temp)) { + isEtsComponent = true; + } + temp = temp.expression; + } + return isEtsComponent; +} + +function isSomeName(forEachParameters: ts.NodeArray, name: string): boolean { + return Array.isArray(forEachParameters) && + forEachParameters.some((item) => { + return ts.isIdentifier(item.name) ? item.name.escapedText.toString() === name : false; + }); +} + +function isParamFunction(node: ts.ExpressionStatement): boolean { + return node.expression && ts.isCallExpression(node.expression) && + node.expression.expression && ts.isIdentifier(node.expression.expression); +} + +function getComponentType(node: ts.ExpressionStatement, log: LogInfo[], name: string, + parent: string, forEachParameters: ts.NodeArray = undefined): ComponentType { + let isBuilderName: boolean = true; + let isLocalBuilderName: boolean = true; + if (forEachParameters && isSomeName(forEachParameters, name) && isParamFunction(node)) { + isBuilderName = false; + isLocalBuilderName = false; + } + if (isEtsComponent(node)) { + if (componentCollection.customComponents.has(name)) { + isCustomComponentAttributes(node, log); + return ComponentType.customComponent; + } else { + return ComponentType.innerComponent; + } + } else if (!isPartMethod(node) && componentCollection.customComponents.has(name)) { + isCustomComponentAttributes(node, log); + return ComponentType.customComponent; + } else if (name === COMPONENT_FOREACH || name === COMPONENT_LAZYFOREACH) { + return ComponentType.forEachComponent; + } else if (name === COMPONENT_REPEAT) { + return ComponentType.repeatComponent; + } else if (isLocalBuilderOrBuilderMethod(CUSTOM_BUILDER_METHOD, isBuilderName, name) || isWrappedBuilderExpression(node) || + isLocalBuilderOrBuilderMethod(INNER_CUSTOM_LOCALBUILDER_METHOD, isLocalBuilderName, name)) { + return ComponentType.customBuilderMethod; + } else if (builderParamObjectCollection.get(componentCollection.currentClassName) && + builderParamObjectCollection.get(componentCollection.currentClassName).has(name)) { + return ComponentType.builderParamMethod; + } else if (!partialUpdateConfig.builderCheck && builderTypeParameter.params.includes(name) && + judgeBuilderType(node)) { + return ComponentType.builderTypeFunction; + } else if ((['XComponent'].includes(parent) || CUSTOM_BUILDER_METHOD.has(parent)) && + ts.isCallExpression(node.expression) && ts.isIdentifier(node.expression.expression)) { + return ComponentType.function; + } else if (!isAttributeNode(node)) { + log.push({ + type: LogType.ERROR, + message: `'${node.getText()}' does not meet UI component syntax.`, + pos: node.getStart(), + code: '10905204' + }); + } + return null; +} + +function isCustomComponentAttributes(node: ts.ExpressionStatement, log: LogInfo[]): void { + if (node.expression && ts.isCallExpression(node.expression) && ts.isPropertyAccessExpression(node.expression.expression) && + ts.isIdentifier(node.expression.expression.name) && !COMMON_ATTRS.has(node.expression.expression.name.escapedText.toString())) { + log.push({ + type: LogType.ERROR, + message: `'${node.getText()}' does not meet UI component syntax.`, + pos: node.getStart() + }); + } +} + +function isLocalBuilderOrBuilderMethod(LocalBuilderOrBuilderSet: Set, + isLocalBuilderOrBuilderName: boolean, name: string): boolean { + return LocalBuilderOrBuilderSet.has(name) && isLocalBuilderOrBuilderName; +} + +function isPartMethod(node: ts.ExpressionStatement): boolean { + if (ts.isCallExpression(node.expression) && ts.isPropertyAccessExpression(node.expression.expression) && + node.expression.expression.expression && node.expression.expression.expression.kind && + node.expression.expression.expression.kind === ts.SyntaxKind.ThisKeyword) { + return true; + } else { + return false; + } +} + +function isWrappedBuilderExpression(node: ts.ExpressionStatement): boolean { + if (projectConfig.minAPIVersion >= 11 && node.expression && + isWrappedBuilderCallExpression(node.expression as ts.CallExpression)) { + return true; + } + return false; +} + +function judgeBuilderType(node: ts.ExpressionStatement): boolean { + let checker: ts.TypeChecker; + if (globalProgram.program) { + checker = globalProgram.program.getTypeChecker(); + } else if (globalProgram.watchProgram) { + checker = globalProgram.watchProgram.getCurrentProgram().getProgram().getTypeChecker(); + } + if (node.expression && node.expression.expression && checker) { + const type: ts.Type = checker.getTypeAtLocation(node.expression.expression); + if (type && type.aliasSymbol && type.aliasSymbol.escapedName === BUILDER_TYPE) { + return true; + } + } + return false; +} + +export function validateStateStyleSyntax(temp, log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `.stateStyles doesn't conform standard.`, + pos: temp.getStart(), + code: '10905203' + }); +} + +function getEtsComponentExpression(node:ts.ExpressionStatement): ts.EtsComponentExpression { + let current = node.expression; + while (current) { + if (ts.isEtsComponentExpression(current)) { + return current; + } + current = current.expression; + } + return null; +} + +function checkEtsAndIdInIf(node:ts.ExpressionStatement, parent: string): [ts.EtsComponentExpression, ts.Expression] { + let current = node.expression; + let idName: ts.Expression; + while (current) { + if (ts.isEtsComponentExpression(current)) { + break; + } + if (!idName && parent === COMPONENT_IF && ts.isPropertyAccessExpression(current) && current.name && + ts.isIdentifier(current.name) && current.name.escapedText.toString() === ATTRIBUTE_ID && + current.parent && current.parent.arguments && current.parent.arguments.length) { + idName = current.parent.arguments[0]; + } + current = current.expression; + } + return [current, idName]; +} + +function checkIdInIf(node:ts.ExpressionStatement, parent: string): ts.Expression { + let current: any = node.expression; + let idName: ts.Expression; + while (current) { + if (parent === COMPONENT_IF && ts.isPropertyAccessExpression(current) && current.name && + ts.isIdentifier(current.name) && current.name.escapedText.toString() === ATTRIBUTE_ID && + current.parent && current.parent.arguments && current.parent.arguments.length) { + idName = current.parent.arguments[0]; + break; + } + current = current.expression; + } + return idName; +} + +function checkEtsComponent(node: ts.ExpressionStatement, log: LogInfo[]): void { + const etsComponentExpression: ts.EtsComponentExpression = getEtsComponentExpression(node); + if (etsComponentExpression) { + checkAllNode( + etsComponentExpression, + new Set([...INNER_COMPONENT_NAMES, ...componentCollection.customComponents]), + transformLog.sourceFile, + log + ); + } +} + +function checkButtonParamHasLabel(node: ts.EtsComponentExpression, log: LogInfo[]): void { + if (node.arguments && node.arguments.length !== 0) { + for (let i = 0; i < node.arguments.length; i++) { + const argument: ts.Expression = node.arguments[i]; + if (ts.isStringLiteral(argument) || (ts.isCallExpression(argument) && ts.isIdentifier(argument.expression) && + (argument.expression.escapedText.toString() === RESOURCE))) { + log.push({ + type: LogType.ERROR, + message: 'The Button component with a label parameter can not have any child.', + pos: node.getStart(), + code: '10905202' + }); + return; + } + } + } +} + +function isLazyForEachChild(node: ts.ExpressionStatement): boolean { + let temp = node.parent; + while (temp && !ts.isEtsComponentExpression(temp) && !ts.isCallExpression(temp)) { + temp = temp.parent; + } + if (temp && temp.expression && (temp.expression as ts.Identifier).escapedText?.toString() === COMPONENT_LAZYFOREACH) { + return true; + } + return false; +} + +function processDollarEtsComponent(argumentsArr: ts.NodeArray, name: string): ts.Expression[] { + const arr: ts.Expression[] = []; + argumentsArr.forEach((item: ts.Expression, index: number) => { + if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) { + const properties: ts.PropertyAssignment[] = []; + item.properties.forEach((param: ts.PropertyAssignment, paramIndex: number) => { + if (isHaveDoubleDollar(param, name)) { + const varExp: ts.Expression = updateArgumentForDollar(param.initializer); + properties.push(ts.factory.updatePropertyAssignment(param, param.name, generateObjectForDollar(varExp))); + } else { + properties.push(param); + } + }); + arr.push(ts.factory.updateObjectLiteralExpression(item, properties)); + } else { + arr.push(item); + } + }); + return arr; +} + +function processExclamationEtsComponent(argumentsArr: ts.NodeArray, name: string): ts.Expression[] { + const arr: ts.Expression[] = []; + argumentsArr.forEach((item: ts.Expression, index: number) => { + if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) { + const properties: ts.PropertyAssignment[] = []; + item.properties.forEach((param: ts.PropertyAssignment, paramIndex: number) => { + if (isHaveDoubleExclamation(param, name) && param.initializer && param.name) { + const varExp: ts.Expression = updateArgumentForExclamation(param.initializer); + properties.push(ts.factory.updatePropertyAssignment(param, param.name, varExp)); + properties.push(generateFunctionPropertyAssignmentForExclamation(param.name.getText(), varExp)); + } else { + properties.push(param); + } + }); + arr.push(ts.factory.updateObjectLiteralExpression(item, properties)); + } else { + arr.push(item); + } + }); + return arr; +} + +export function createFunction(node: ts.Identifier, attrNode: ts.Identifier, + argumentsArr: ts.NodeArray, isAttributeModifier: boolean = false): ts.CallExpression { + const compName: string = node.escapedText.toString(); + const type: string = attrNode.escapedText.toString(); + if (argumentsArr && argumentsArr.length) { + if (type === COMPONENT_CREATE_FUNCTION && PROPERTIES_ADD_DOUBLE_DOLLAR.has(compName)) { + // @ts-ignore + argumentsArr = processDollarEtsComponent(argumentsArr, compName); + } + if (type === COMPONENT_CREATE_FUNCTION && PROPERTIES_ADD_DOUBLE_EXCLAMATION.has(compName)) { + argumentsArr = processExclamationEtsComponent(argumentsArr, compName) as unknown as ts.NodeArray; + } + if (checkCreateArgumentBuilder(node, attrNode)) { + argumentsArr = transformBuilder(argumentsArr); + } + if (compName === NAVIGATION && type === COMPONENT_CREATE_FUNCTION && partialUpdateConfig.partialUpdateMode) { + // @ts-ignore + argumentsArr = navigationCreateParam(compName, type, argumentsArr); + } + } else { + // @ts-ignore + argumentsArr = navigationCreateParam(compName, type); + } + return ts.factory.createCallExpression( + isAttributeModifier ? ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + node, + attrNode + ), + ts.factory.createIdentifier(BUILDER_ATTR_BIND) + ), + undefined, + [ts.factory.createThis()] + ) : + ts.factory.createPropertyAccessExpression( + node, + attrNode + ), + undefined, + argumentsArr + ); +} + +function navigationCreateParam(compName: string, type: string, + argumentsArr: ts.NodeArray = undefined, isNavDestinationCallback: boolean = false): + (ts.ObjectLiteralExpression | ts.NewExpression | ts.ArrowFunction)[] | [] { + const navigationOrNavDestination: (ts.ObjectLiteralExpression | ts.NewExpression | ts.ArrowFunction)[] = []; + const isCreate: boolean = type === COMPONENT_CREATE_FUNCTION; + const partialUpdateMode: boolean = partialUpdateConfig.partialUpdateMode; + let isHaveParam: boolean = true; + if (argumentsArr && argumentsArr.length) { + // @ts-ignore + navigationOrNavDestination.push(...argumentsArr); + } else if (partialUpdateMode && isCreate) { + if (compName === NAVIGATION) { + isHaveParam = false; + navigationOrNavDestination.push(ts.factory.createNewExpression( + ts.factory.createIdentifier(NAV_PATH_STACK), undefined, [] + )); + } else if (compName === NAV_DESTINATION && !isNavDestinationCallback) { + navigationOrNavDestination.push(ts.factory.createArrowFunction( + undefined, undefined, [], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [], + false + ) + )); + } + } + if (CREATE_ROUTER_COMPONENT_COLLECT.has(compName) && isCreate && partialUpdateMode) { + navigationOrNavDestination.push(ts.factory.createObjectLiteralExpression( + navigationOrNavDestinationCreateContent(compName, isHaveParam), + false + )); + } + return navigationOrNavDestination; +} + +function navigationOrNavDestinationCreateContent(compName: string, isHaveParam: boolean): ts.PropertyAssignment[] { + const navigationOrNavDestinationContent: ts.PropertyAssignment[] = []; + navigationOrNavDestinationContent.push(ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(RESOURCE_NAME_MODULE), + ts.factory.createStringLiteral(projectConfig.moduleName || '') + ), + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(PAGE_PATH), + ts.factory.createStringLiteral( + projectConfig.compileHar ? '' : + path.relative(projectConfig.projectRootPath || '', resourceFileName).replace(/\\/g, '/').replace(/\.ets$/, '') + ) + )); + if (compName === NAVIGATION) { + navigationOrNavDestinationContent.push(ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(IS_USER_CREATE_STACK), + isHaveParam ? ts.factory.createTrue() : ts.factory.createFalse() + )); + } + return navigationOrNavDestinationContent; +} + +function checkCreateArgumentBuilder(node: ts.Identifier, attrNode: ts.Identifier): boolean { + if (attrNode.escapedText.toString() === COMPONENT_CREATE_FUNCTION && + CREATE_BIND_COMPONENT.has(node.escapedText.toString())) { + return true; + } + return false; +} + +function transformBuilder(argumentsArr: ts.NodeArray): ts.NodeArray { + const newArguments: ts.Expression[] = []; + argumentsArr.forEach((argument: ts.Expression) => { + newArguments.push(parseCreateParameterBuilder(argument)); + }); + // @ts-ignore + return newArguments; +} + +function parseCreateParameterBuilder(argument: ts.Expression):ts.Expression { + if (ts.isObjectLiteralExpression(argument)) { + return processObjectPropertyBuilder(argument); + } else { + return argument; + } +} + +function checkNonspecificParents(node: ts.ExpressionStatement, name: string, savedParent: string, log: LogInfo[]): void { + if (SPECIFIC_PARENT_COMPONENT.has(name)) { + const specificParemtsSet: Set = SPECIFIC_PARENT_COMPONENT.get(name); + if (!specificParemtsSet.has(savedParent) && INNER_COMPONENT_NAMES.has(savedParent)) { + const specificParentArray: string = + Array.from(SPECIFIC_PARENT_COMPONENT.get(name)).join(','); + log.push({ + type: LogType.ERROR, + message: `The '${name}' component can only be nested in the '${specificParentArray}' parent component.`, + pos: node.expression.getStart(), + code: '10905201' + }); + } + } +} diff --git a/compiler/src/interop/src/process_component_class.ts b/compiler/src/interop/src/process_component_class.ts new file mode 100644 index 0000000000000000000000000000000000000000..a61f581e32efef1da637e47d5497d28ac6f3264e --- /dev/null +++ b/compiler/src/interop/src/process_component_class.ts @@ -0,0 +1,1271 @@ +/* + * Copyright (c) 2021 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 ts from 'typescript'; + +import { + COMPONENT_STATE_DECORATOR, + COMPONENT_PROVIDE_DECORATOR, + COMPONENT_LINK_DECORATOR, + COMPONENT_PROP_DECORATOR, + COMPONENT_STORAGE_LINK_DECORATOR, + COMPONENT_STORAGE_PROP_DECORATOR, + COMPONENT_OBJECT_LINK_DECORATOR, + COMPONENT_CONSUME_DECORATOR, + SYNCHED_PROPERTY_NESED_OBJECT, + SYNCHED_PROPERTY_SIMPLE_TWO_WAY, + SYNCHED_PROPERTY_SIMPLE_ONE_WAY, + OBSERVED_PROPERTY_OBJECT, + OBSERVED_PROPERTY_SIMPLE, + COMPONENT_BUILD_FUNCTION, + BASE_COMPONENT_NAME, + CREATE_CONSTRUCTOR_PARAMS, + COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, + COMPONENT_CONSTRUCTOR_INITIAL_PARAMS, + COMPONENT_CONSTRUCTOR_PURGE_VARIABLE_DEP, + COMPONENT_CONSTRUCTOR_DELETE_PARAMS, + COMPONENT_DECORATOR_PREVIEW, + CREATE_CONSTRUCTOR_SUBSCRIBER_MANAGER, + ABOUT_TO_BE_DELETE_FUNCTION_ID, + ABOUT_TO_BE_DELETE_FUNCTION_ID__, + CREATE_CONSTRUCTOR_GET_FUNCTION, + CREATE_CONSTRUCTOR_DELETE_FUNCTION, + FOREACH_OBSERVED_OBJECT, + FOREACH_GET_RAW_OBJECT, + COMPONENT_BUILDER_DECORATOR, + COMPONENT_TRANSITION_FUNCTION, + COMPONENT_CREATE_FUNCTION, + GEOMETRY_VIEW, + COMPONENT_STYLES_DECORATOR, + STYLES, + INTERFACE_NAME_SUFFIX, + OBSERVED_PROPERTY_ABSTRACT, + COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, + COMPONENT_LOCAL_STORAGE_PROP_DECORATOR, + COMPONENT_CONSTRUCTOR_LOCALSTORAGE, + COMPONENT_SET_AND_LINK, + COMPONENT_SET_AND_PROP, + COMPONENT_CONSTRUCTOR_UNDEFINED, + CUSTOM_COMPONENT, + COMPONENT_CONSTRUCTOR_PARENT, + NULL, + INNER_COMPONENT_MEMBER_DECORATORS, + COMPONENT_RERENDER_FUNCTION, + RMELMTID, + ABOUTTOBEDELETEDINTERNAL, + UPDATEDIRTYELEMENTS, + BASE_COMPONENT_NAME_PU, + OBSERVED_PROPERTY_SIMPLE_PU, + OBSERVED_PROPERTY_OBJECT_PU, + SYNCHED_PROPERTY_SIMPLE_TWO_WAY_PU, + SYNCHED_PROPERTY_SIMPLE_ONE_WAY_PU, + SYNCHED_PROPERTY_NESED_OBJECT_PU, + OBSERVED_PROPERTY_ABSTRACT_PU, + CREATE_LOCAL_STORAGE_LINK, + CREATE_LOCAL_STORAGE_PROP, + COMPONENT_UPDATE_STATE_VARS, + COMPONENT_WATCH_DECORATOR, + $$, + COMPONENT_UPDATE_ELMT_ID, + OLD_ELMT_ID, + NEW_ELMT_ID, + UPDATE_RECYCLE_ELMT_ID, + GET_ENTRYNAME, + COMPONENT_PARAMS_FUNCTION, + FUNCTION, + COMPONENT_PARAMS_LAMBDA_FUNCTION, + DECORATOR_COMPONENT_FREEZEWHENINACTIVE, + INIT_ALLOW_COMPONENT_FREEZE, + FINALIZE_CONSTRUCTION, + PROTOTYPE, + REFLECT, + CREATE_SET_METHOD, + COMPONENT_REQUIRE_DECORATOR, + CONTEXT_STACK, + COMPONENT_IF_UNDEFINED, + COMPONENT_POP_FUNCTION, + PUSH, + PUV2_VIEW_BASE, + COMPONENT_LOCAL_BUILDER_DECORATOR, + DECORATOR_REUSEABLE +} from './pre_define'; +import { + BUILDIN_STYLE_NAMES, + CUSTOM_BUILDER_METHOD, + INNER_STYLE_FUNCTION, + INTERFACE_NODE_SET, + STYLES_ATTRIBUTE, + INNER_CUSTOM_BUILDER_METHOD +} from './component_map'; +import { + componentCollection, + linkCollection, + localStorageLinkCollection, + localStoragePropCollection, + builderParamObjectCollection, + methodDecoratorCollect +} from './validate_ui_syntax'; +import { + addConstructor, + getInitConstructor, + updateConstructor +} from './process_component_constructor'; +import { + ControllerType, + processMemberVariableDecorators, + UpdateResult, + stateObjectCollection, + PropMapManager, + decoratorParamSet, + isSimpleType, + isSingleKey, + findDecoratorIndex +} from './process_component_member'; +import { + processComponentBuild, + processComponentBlock +} from './process_component_build'; +import { isRecycle } from './process_custom_component'; +import { + LogType, + LogInfo, + hasDecorator, + getPossibleBuilderTypeParameter, + storedFileInfo, + removeDecorator +} from './utils'; +import { + partialUpdateConfig, + projectConfig +} from '../main'; +import { + builderTypeParameter, + initializeMYIDS, + globalBuilderParamAssignment +} from './process_ui_syntax'; +import constantDefine from './constant_define'; +import processStructComponentV2, { StructInfo } from './process_struct_componentV2'; + +export function processComponentClass(node: ts.StructDeclaration, context: ts.TransformationContext, + log: LogInfo[], program: ts.Program): ts.ClassDeclaration { + const decoratorNode: readonly ts.Decorator[] = ts.getAllDecorators(node); + const memberNode: ts.ClassElement[] = + processMembers(node.members, node.name, context, decoratorNode, log, program, checkPreview(node)); + return ts.factory.createClassDeclaration( + ts.getModifiers(node), + node.name, + node.typeParameters, + updateHeritageClauses(node, log), + memberNode + ); +} + +function checkPreview(node: ts.StructDeclaration): boolean { + let hasPreview: boolean = false; + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (node && decorators) { + for (let i = 0; i < decorators.length; i++) { + const name: string = decorators[i].getText().replace(/\([^\(\)]*\)/, '').trim(); + if (name === COMPONENT_DECORATOR_PREVIEW) { + hasPreview = true; + break; + } + } + } + return hasPreview; +} + +export type BuildCount = { + count: number; +}; +export type FreezeParamType = { + componentFreezeParam: ts.Expression; +}; +function processMembers(members: ts.NodeArray, parentComponentName: ts.Identifier, + context: ts.TransformationContext, decoratorNode: readonly ts.Decorator[], log: LogInfo[], + program: ts.Program, hasPreview: boolean): ts.ClassElement[] { + const buildCount: BuildCount = { count: 0 }; + let ctorNode: any = getInitConstructor(members, parentComponentName); + const newMembers: ts.ClassElement[] = []; + const watchMap: Map = new Map(); + const updateParamsStatements: ts.Statement[] = []; + const stateVarsStatements: ts.Statement[] = []; + const purgeVariableDepStatements: ts.Statement[] = []; + const rerenderStatements: ts.Statement[] = []; + const deleteParamsStatements: ts.PropertyDeclaration[] = []; + const checkController: ControllerType = { hasController: !componentCollection.customDialogs.has(parentComponentName.getText()), + unassignedControllerSet: new Set() }; + const interfaceNode = ts.factory.createInterfaceDeclaration(undefined, + parentComponentName.getText() + INTERFACE_NAME_SUFFIX, undefined, undefined, []); + members.forEach((item: ts.MethodDeclaration) => { + if (hasDecorator(item, COMPONENT_STYLES_DECORATOR)) { + methodDecoratorCollect(item); + } + }) + members.forEach((item: ts.ClassElement) => { + let updateItem: ts.ClassElement; + if (ts.isPropertyDeclaration(item)) { + if (isStaticProperty(item)) { + newMembers.push(item); + validateDecorators(item, log); + } else { + addPropertyMember(item, newMembers, program, parentComponentName.getText(), log); + const result: UpdateResult = processMemberVariableDecorators(parentComponentName, item, + ctorNode, watchMap, checkController, log, program, context, hasPreview, interfaceNode); + if (result.isItemUpdate()) { + updateItem = result.getProperity(); + } else { + updateItem = item; + } + if (result.getVariableGet()) { + newMembers.push(result.getVariableGet()); + } + if (result.getVariableSet()) { + newMembers.push(result.getVariableSet()); + } + if (result.isCtorUpdate()) { + ctorNode = result.getCtor(); + } + if (result.getUpdateParams()) { + updateParamsStatements.push(result.getUpdateParams()); + } + if (result.getStateVarsParams()) { + stateVarsStatements.push(result.getStateVarsParams()); + } + if (result.isDeleteParams()) { + deleteParamsStatements.push(item); + } + if (result.getControllerSet()) { + newMembers.push(result.getControllerSet()); + } + processPropertyUnchanged(result, purgeVariableDepStatements); + } + } + if (ts.isMethodDeclaration(item) && item.name) { + updateItem = + processComponentMethod(item, context, log, buildCount); + } + if (updateItem) { + newMembers.push(updateItem); + } + }); + INTERFACE_NODE_SET.add(interfaceNode); + validateBuildMethodCount(buildCount, parentComponentName, log); + validateHasControllerAndControllerCount(parentComponentName, checkController, log); + if (storedFileInfo.getCurrentArkTsFile().recycleComponents.has(parentComponentName.getText())) { + newMembers.unshift(addDeleteParamsFunc(deleteParamsStatements, true)); + } + newMembers.unshift(addDeleteParamsFunc(deleteParamsStatements)); + addIntoNewMembers(newMembers, parentComponentName, updateParamsStatements, + purgeVariableDepStatements, rerenderStatements, stateVarsStatements); + if (partialUpdateConfig.partialUpdateMode) { + const creezeParam: FreezeParamType = { + componentFreezeParam: undefined + }; + const isFreezeComponent: boolean = decoratorAssignParams(decoratorNode, context, creezeParam); + ctorNode = updateConstructor(ctorNode, [], assignParams(parentComponentName.getText()), + isFreezeComponent ? decoratorComponentParam(creezeParam) : [], true); + } + newMembers.unshift(addConstructor(ctorNode, watchMap, parentComponentName)); + if (componentCollection.entryComponent === parentComponentName.escapedText.toString() && + partialUpdateConfig.partialUpdateMode && projectConfig.minAPIVersion > 10) { + newMembers.push(getEntryNameFunction(componentCollection.entryComponent)); + } + log.push(...Array.from(PropMapManager.logInfoMap.values()).flat()); + PropMapManager.reset(); + return newMembers; +} + +export function decoratorAssignParams(decoratorNode: readonly ts.Decorator[], context: ts.TransformationContext, + creezeParam: FreezeParamType): boolean { + if (decoratorNode && Array.isArray(decoratorNode) && decoratorNode.length) { + return decoratorNode.some((item: ts.Decorator) => { + if (isFreezeComponents(item, context, creezeParam)) { + return true; + } else { + return false; + } + }); + } else { + return false; + } +} + +function isFreezeComponents(decorator: ts.Decorator, context: ts.TransformationContext, + creezeParam: FreezeParamType): boolean { + let isComponentAssignParent: boolean = false; + ts.visitNode(decorator, visitComponentParament); + function visitComponentParament(decorator: ts.Node): ts.Node { + if (ts.isPropertyAssignment(decorator) && decorator.name && decorator.name.text && + decorator.name.text.toString() === DECORATOR_COMPONENT_FREEZEWHENINACTIVE) { + isComponentAssignParent = true; + creezeParam.componentFreezeParam = decorator.initializer; + return decorator; + } + return ts.visitEachChild(decorator, visitComponentParament, context); + } + return isComponentAssignParent; +} + +export function getEntryNameFunction(entryName: string): ts.MethodDeclaration { + return ts.factory.createMethodDeclaration( + [ts.factory.createToken(ts.SyntaxKind.StaticKeyword)], + undefined, + ts.factory.createIdentifier(GET_ENTRYNAME), + undefined, + undefined, + [], + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ts.factory.createBlock( + [ts.factory.createReturnStatement(ts.factory.createStringLiteral(entryName))], + true + ) + ); +} + +function assignParams(parentComponentName: string): ts.Statement[] { + return [ts.factory.createIfStatement( + ts.factory.createBinaryExpression( + ts.factory.createTypeOfExpression(ts.factory.createIdentifier(COMPONENT_PARAMS_LAMBDA_FUNCTION)), + ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + ts.factory.createStringLiteral(FUNCTION) + ), + ts.factory.createBlock( + [ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(COMPONENT_PARAMS_FUNCTION) + ), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + ts.factory.createIdentifier(COMPONENT_PARAMS_LAMBDA_FUNCTION) + ))], + true + ) + )]; +} + +function decoratorComponentParam(freezeParam: FreezeParamType): ts.IfStatement[] { + return [ts.factory.createIfStatement( + ts.factory.createBinaryExpression( + ts.factory.createElementAccessExpression( + ts.factory.createSuper(), + ts.factory.createStringLiteral(INIT_ALLOW_COMPONENT_FREEZE) + ), + ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + ts.factory.createBinaryExpression( + ts.factory.createTypeOfExpression(ts.factory.createElementAccessExpression( + ts.factory.createSuper(), + ts.factory.createStringLiteral(INIT_ALLOW_COMPONENT_FREEZE) + )), + ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + ts.factory.createStringLiteral(FUNCTION) + ) + ), + ts.factory.createBlock( + [ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createElementAccessExpression( + ts.factory.createSuper(), + ts.factory.createStringLiteral(INIT_ALLOW_COMPONENT_FREEZE) + ), + undefined, + [freezeParam.componentFreezeParam] + ))], + true + ), + undefined + )]; +} + +function isStaticProperty(property: ts.PropertyDeclaration): boolean { + const modifiers: readonly ts.Modifier[] = + ts.canHaveModifiers(property) ? ts.getModifiers(property) : undefined; + return modifiers && modifiers.length && modifiers.some(modifier => { + return modifier.kind === ts.SyntaxKind.StaticKeyword; + }); +} + +function validateDecorators(item: ts.ClassElement, log: LogInfo[]): void { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item); + if (decorators && decorators.length) { + decorators.map((decorator: ts.Decorator) => { + const decoratorName: string = decorator.getText(); + if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { + log.push({ + type: LogType.ERROR, + message: `The static variable of struct cannot be used together with built-in decorators.`, + pos: item.getStart(), + code: '10905313' + }); + } + }); + } +} + +function processPropertyUnchanged( + result: UpdateResult, + purgeVariableDepStatements: ts.Statement[] +): void { + if (partialUpdateConfig.partialUpdateMode) { + if (result.getPurgeVariableDepStatement()) { + purgeVariableDepStatements.push(result.getPurgeVariableDepStatement()); + } + } +} + +function addIntoNewMembers( + newMembers: ts.ClassElement[], + parentComponentName: ts.Identifier, + updateParamsStatements: ts.Statement[], + purgeVariableDepStatements: ts.Statement[], + rerenderStatements: ts.Statement[], + stateVarsStatements: ts.Statement[] +): void { + if (partialUpdateConfig.partialUpdateMode) { + newMembers.unshift( + addInitialParamsFunc(updateParamsStatements, parentComponentName), + addUpdateStateVarsFunc(stateVarsStatements, parentComponentName), + addPurgeVariableDepFunc(purgeVariableDepStatements) + ); + newMembers.push(addRerenderFunc(rerenderStatements)); + } else { + newMembers.unshift(addUpdateParamsFunc(updateParamsStatements, parentComponentName)); + } +} + +export function isRegularProperty(decorators: readonly ts.Decorator[]): boolean { + if (decorators && decorators.length) { + if (decorators.length === 1 && decorators[0].getText() === COMPONENT_REQUIRE_DECORATOR) { + return true; + } + return false; + } + return true; +} + +function addPropertyMember(item: ts.ClassElement, newMembers: ts.ClassElement[], + program: ts.Program, parentComponentName: string, log: LogInfo[]): void { + const propertyItem: ts.PropertyDeclaration = item as ts.PropertyDeclaration; + let decoratorName: string; + let updatePropertyItem: ts.PropertyDeclaration; + const type: ts.TypeNode = propertyItem.type; + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(propertyItem); + if (isRegularProperty(decorators)) { + updatePropertyItem = createPropertyDeclaration(propertyItem, type, true); + newMembers.push(updatePropertyItem); + } else { + for (let i = 0; i < decorators.length; i++) { + let newType: ts.TypeNode; + decoratorName = decorators[i].getText().replace(/\(.*\)$/, '').trim(); + let isLocalStorage: boolean = false; + if (!partialUpdateConfig.partialUpdateMode) { + newType = createTypeReference(decoratorName, type, log, program); + } else { + newType = createTypeReferencePU(decoratorName, type, log, program); + } + if ( + decoratorName === COMPONENT_LOCAL_STORAGE_LINK_DECORATOR || + decoratorName === COMPONENT_LOCAL_STORAGE_PROP_DECORATOR + ) { + isLocalStorage = true; + } + const newUpdatePropertyItem = createPropertyDeclaration( + propertyItem, newType, false, isLocalStorage, parentComponentName); + if (!updatePropertyItem) { + updatePropertyItem = newUpdatePropertyItem; + } else if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName) && + ![COMPONENT_WATCH_DECORATOR, COMPONENT_REQUIRE_DECORATOR].includes(decoratorName)) { + updatePropertyItem = newUpdatePropertyItem; + } + } + if (updatePropertyItem) { + newMembers.push(updatePropertyItem); + } + } +} + +function createPropertyDeclaration(propertyItem: ts.PropertyDeclaration, newType: ts.TypeNode | undefined, + normalVar: boolean, isLocalStorage: boolean = false, parentComponentName: string = null): ts.PropertyDeclaration { + if (typeof newType === undefined) { + return undefined; + } + let prefix: string = ''; + if (!normalVar) { + prefix = '__'; + } + const privateM: ts.ModifierToken = + ts.factory.createModifier(ts.SyntaxKind.PrivateKeyword); + const modifiers: readonly ts.Modifier[] = + ts.canHaveModifiers(propertyItem) ? ts.getModifiers(propertyItem) : undefined; + return ts.factory.updatePropertyDeclaration(propertyItem, + ts.concatenateDecoratorsAndModifiers(undefined, modifiers || [privateM]), prefix + propertyItem.name.getText(), + propertyItem.questionToken, newType, isLocalStorage ? + createLocalStroageCallExpression(propertyItem, propertyItem.name.getText(), + parentComponentName) : undefined); +} + +function createLocalStroageCallExpression(node: ts.PropertyDeclaration, name: string, + parentComponentName: string): ts.CallExpression { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (isSingleKey(node)) { + const localStorageLink: Set = localStorageLinkCollection.get(parentComponentName).get(name); + const localStorageProp: Set = localStoragePropCollection.get(parentComponentName).get(name); + let localFuncName: string; + const index: number = findDecoratorIndex(decorators, + [COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, COMPONENT_LOCAL_STORAGE_PROP_DECORATOR]); + const localValue: ts.Expression[] = [ + decorators[index].expression.arguments[0], + node.initializer ? node.initializer : ts.factory.createNumericLiteral(COMPONENT_CONSTRUCTOR_UNDEFINED), + ts.factory.createThis(), + ts.factory.createStringLiteral(name || COMPONENT_CONSTRUCTOR_UNDEFINED) + ]; + if (!partialUpdateConfig.partialUpdateMode) { + localFuncName = localStorageLink && !localStorageProp ? COMPONENT_SET_AND_LINK : + COMPONENT_SET_AND_PROP; + } else { + localFuncName = localStorageLink && !localStorageProp ? CREATE_LOCAL_STORAGE_LINK : + CREATE_LOCAL_STORAGE_PROP; + localValue.splice(-2, 1); + } + return ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + !partialUpdateConfig.partialUpdateMode ? + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(`${COMPONENT_CONSTRUCTOR_LOCALSTORAGE}_`) + ) : ts.factory.createThis(), + ts.factory.createIdentifier(localFuncName) + ), + [node.type], + localValue + ); + } + return undefined; +} +export interface builderConditionType { + isBuilder: boolean, + isLocalBuilder: boolean +} +export function processComponentMethod(node: ts.MethodDeclaration, context: ts.TransformationContext, + log: LogInfo[], buildCount: BuildCount): ts.MethodDeclaration { + let updateItem: ts.MethodDeclaration = node; + const name: string = node.name.getText(); + const customBuilder: ts.Decorator[] = []; + const builderCondition: builderConditionType = { + isBuilder: false, + isLocalBuilder: false + }; + if (builderParamObjectCollection.get(componentCollection.currentClassName)) { + storedFileInfo.builderLikeCollection = + new Set([...builderParamObjectCollection.get(componentCollection.currentClassName), ...CUSTOM_BUILDER_METHOD]); + } else { + storedFileInfo.builderLikeCollection = CUSTOM_BUILDER_METHOD; + } + if (name === COMPONENT_BUILD_FUNCTION) { + storedFileInfo.processBuilder = false; + storedFileInfo.processGlobalBuilder = false; + buildCount.count = buildCount.count + 1; + if (node.parameters.length) { + log.push({ + type: LogType.ERROR, + message: `The 'build' method can not have arguments.`, + pos: node.getStart(), + code: '10905106' + }); + } + const buildNode: ts.MethodDeclaration = processComponentBuild(node, log); + updateItem = processBuildMember(buildNode, context, log); + } else if (node.body && ts.isBlock(node.body)) { + if (name === COMPONENT_TRANSITION_FUNCTION) { + updateItem = ts.factory.updateMethodDeclaration(node, ts.getModifiers(node), + node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, + node.type, processComponentBlock(node.body, false, log, true)); + } else if (isBuilderOrLocalBuilder(node, builderCondition, customBuilder)) { + storedFileInfo.processBuilder = true; + storedFileInfo.processGlobalBuilder = false; + if (builderCondition.isLocalBuilder) { + storedFileInfo.processLocalBuilder = true; + } + CUSTOM_BUILDER_METHOD.add(name); + INNER_CUSTOM_BUILDER_METHOD.add(name); + builderTypeParameter.params = getPossibleBuilderTypeParameter(node.parameters); + const parameters: ts.NodeArray = ts.factory.createNodeArray(Array.from(node.parameters)); + if (!builderCondition.isLocalBuilder) { + parameters.push(createParentParameter()); + } + if (projectConfig.optLazyForEach) { + parameters.push(initializeMYIDS()); + } + const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + const componentBlock: ts.Block = processComponentBlock(node.body, false, log, false, true); + if (partialUpdateConfig.partialUpdateMode && builderCondition.isLocalBuilder && + node.body.statements.length) { + componentBlock.statements.unshift(globalBuilderParamAssignment()); + } + let builderNode: ts.MethodDeclaration | ts.PropertyDeclaration = ts.factory.updateMethodDeclaration(node, + ts.concatenateDecoratorsAndModifiers(removeDecorator(customBuilder, 'Builder'), modifiers), + node.asteriskToken, node.name, node.questionToken, node.typeParameters, + parameters, node.type, componentBlock); + builderTypeParameter.params = []; + updateItem = processBuildMember(builderNode, context, log, true); + if (builderCondition.isLocalBuilder) { + checkDecoratorMethod(node, modifiers, log); + updateItem = localBuilderNode(node, updateItem.body); + } + storedFileInfo.processBuilder = false; + storedFileInfo.processLocalBuilder = false; + } else if (hasDecorator(node, COMPONENT_STYLES_DECORATOR)) { + if (node.parameters && node.parameters.length === 0) { + if (ts.isBlock(node.body) && node.body.statements && node.body.statements.length) { + INNER_STYLE_FUNCTION.set(name, node.body); + STYLES_ATTRIBUTE.add(name); + BUILDIN_STYLE_NAMES.add(name); + decoratorParamSet.add(STYLES); + } + } else { + log.push({ + type: LogType.ERROR, + message: `@Styles can't have parameters.`, + pos: node.getStart(), + code: '10905105' + }); + } + return undefined; + } + } + return updateItem; +} + +function checkDecoratorMethod(node: ts.MethodDeclaration, modifiers: readonly ts.Modifier[], log: LogInfo[]): void { + if (modifiers && modifiers.length) { + for (let i = 0; i < modifiers.length; i++) { + if (modifiers[i].kind && modifiers[i].kind === ts.SyntaxKind.StaticKeyword) { + log.push({ + type: LogType.ERROR, + message: `Static methods in custom components cannot be decorated by @LocalBuilder.`, + pos: node.getStart(), + code: '10905104' + }); + return; + } + } + } +} + +export function isBuilderOrLocalBuilder(node: ts.MethodDeclaration | ts.FunctionDeclaration, builderCondition: builderConditionType, + customBuilder: ts.Decorator[] = undefined): boolean { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (decorators && decorators.length) { + for (let i = 0; i < decorators.length; i++) { + const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim(); + if ([COMPONENT_LOCAL_BUILDER_DECORATOR, COMPONENT_BUILDER_DECORATOR].includes(originalDecortor)) { + if (originalDecortor === COMPONENT_BUILDER_DECORATOR) { + builderCondition.isBuilder = true; + if (customBuilder) { + customBuilder.push(...decorators.slice(i + 1), ...decorators.slice(0, i)); + } + } else { + builderCondition.isLocalBuilder = true; + } + return true; + } + } + } + return false; +} + +function localBuilderNode(node: ts.MethodDeclaration, componentBlock: ts.Block): ts.PropertyDeclaration { + return ts.factory.createPropertyDeclaration( + undefined, node.name, undefined, undefined, + ts.factory.createArrowFunction( + undefined, undefined, node.parameters ? node.parameters : [], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + componentBlock + ) + ); +} + +export function createParentParameter(): ts.ParameterDeclaration { + return ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT), undefined, undefined, + ts.factory.createIdentifier(NULL)); +} + +export function processBuildMember(node: ts.MethodDeclaration | ts.FunctionDeclaration, context: ts.TransformationContext, + log: LogInfo[], isBuilder = false): ts.MethodDeclaration | ts.FunctionDeclaration { + return ts.visitNode(node, visitBuild); + function visitBuild(node: ts.Node): ts.Node { + if (isGeometryView(node)) { + node = processGeometryView(node as ts.ExpressionStatement, log); + } + if (isProperty(node)) { + node = createReference(node as ts.PropertyAssignment, log, isBuilder); + } + if (isNeedGetRawObject(node)) { + return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(FOREACH_OBSERVED_OBJECT), + ts.factory.createIdentifier(FOREACH_GET_RAW_OBJECT)), undefined, [node]); + } + return ts.visitEachChild(node, visitBuild, context); + } +} + +function checkStateName(node: ts.PropertyAccessExpression): string { + if (node.expression && !node.expression.expression && node.name && ts.isIdentifier(node.name)) { + return node.name.escapedText.toString(); + } + return null; +} + +// EnableV2Compatibility MakeV1Observed Do not generate ObservedObject GetRawObject. +function isNeedGetRawObject(node: ts.Node): boolean { + return !isInComponentV2Context() && !isEnableV2CompatibilityOrMakeV1Observed(node) && ts.isPropertyAccessExpression(node) && + ts.isIdentifier(node.name) && stateObjectCollection.has(checkStateName(node)) && node.parent && ts.isCallExpression(node.parent) && + ts.isPropertyAccessExpression(node.parent.expression) && node !== node.parent.expression && + node.parent.expression.name.escapedText.toString() !== FOREACH_GET_RAW_OBJECT; +} + +function isEnableV2CompatibilityOrMakeV1Observed(node: ts.Node): boolean { + if (node.parent && ts.isCallExpression(node.parent) && ts.isPropertyAccessExpression(node.parent.expression) && + ts.isIdentifier(node.parent.expression.name) && + ['enableV2Compatibility', 'makeV1Observed'].includes(node.parent.expression.name.escapedText.toString())) { + return true; + } else { + return false; + } +} + +function isInComponentV2Context(): boolean { + if (componentCollection.currentClassName) { + const parentStructInfo: StructInfo = + processStructComponentV2.getOrCreateStructInfo(componentCollection.currentClassName); + return parentStructInfo.isComponentV2; + } + return false; +} + +function isGeometryView(node: ts.Node): boolean { + if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) { + const call: ts.CallExpression = node.expression; + const exp: ts.Expression = call.expression; + const args: ts.NodeArray = call.arguments; + if (ts.isPropertyAccessExpression(exp) && ts.isIdentifier(exp.expression) && + exp.expression.escapedText.toString() === GEOMETRY_VIEW && ts.isIdentifier(exp.name) && + exp.name.escapedText.toString() === COMPONENT_CREATE_FUNCTION && args && args.length === 1 && + (ts.isArrowFunction(args[0]) || ts.isFunctionExpression(args[0]))) { + return true; + } + } + return false; +} + +function processGeometryView(node: ts.ExpressionStatement, + log: LogInfo[]): ts.ExpressionStatement { + const exp: ts.CallExpression = node.expression as ts.CallExpression; + const arg: ts.ArrowFunction | ts.FunctionExpression = + exp.arguments[0] as ts.ArrowFunction | ts.FunctionExpression; + return ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression(exp, + exp.expression, undefined, [ts.factory.createArrowFunction(undefined, undefined, arg.parameters, + undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + getGeometryReaderFunctionBlock(arg, log))])); +} + +function getGeometryReaderFunctionBlock(node: ts.ArrowFunction | ts.FunctionExpression, + log: LogInfo[]): ts.Block { + let blockNode: ts.Block; + if (ts.isBlock(node.body)) { + blockNode = node.body; + } else if (ts.isArrowFunction(node) && ts.isCallExpression(node.body)) { + blockNode = ts.factory.createBlock([ts.factory.createExpressionStatement(node.body)]); + } + return processComponentBlock(blockNode, false, log); +} + +export function updateHeritageClauses(node: ts.StructDeclaration, log: LogInfo[], + isComponentV2: boolean = false): ts.NodeArray { + if (node.heritageClauses && !checkHeritageClauses(node)) { + log.push({ + type: LogType.ERROR, + message: 'The struct component is not allowed to extends other class or implements other interface.', + pos: node.heritageClauses.pos, + code: '10905212' + }); + } + const result: ts.HeritageClause[] = []; + const heritageClause: ts.HeritageClause = createHeritageClause(isComponentV2); + result.push(heritageClause); + return ts.factory.createNodeArray(result); +} + +function checkHeritageClauses(node: ts.StructDeclaration): boolean { + if (node.heritageClauses.length === 1 && node.heritageClauses[0].types && + node.heritageClauses[0].types.length === 1) { + const expressionNode: ts.ExpressionWithTypeArguments = node.heritageClauses[0].types[0]; + if (expressionNode.expression && ts.isIdentifier(expressionNode.expression) && + expressionNode.expression.escapedText.toString() === CUSTOM_COMPONENT) { + return true; + } + } + return false; +} + +export function isProperty(node: ts.Node): Boolean { + if (judgmentParentType(node)) { + if (node.parent.parent.expression && ts.isIdentifier(node.parent.parent.expression) && + !BUILDIN_STYLE_NAMES.has(node.parent.parent.expression.escapedText.toString()) && + componentCollection.customComponents.has( + node.parent.parent.expression.escapedText.toString())) { + return true; + } else if (ts.isPropertyAccessExpression(node.parent.parent.expression) && + ts.isIdentifier(node.parent.parent.expression.expression) && + componentCollection.customComponents.has( + node.parent.parent.expression.name.escapedText.toString())) { + return true; + } + } + return false; +} + +function judgmentParentType(node: ts.Node): boolean { + return ts.isPropertyAssignment(node) && node.name && ts.isIdentifier(node.name) && + node.parent && ts.isObjectLiteralExpression(node.parent) && node.parent.parent && + (ts.isCallExpression(node.parent.parent) || ts.isEtsComponentExpression(node.parent.parent)); +} + +export function createReference(node: ts.PropertyAssignment, log: LogInfo[], isBuilder = false, + isParamsLambda: boolean = false, isRecycleComponent: boolean = false): ts.PropertyAssignment { + const linkParentComponent: string[] = getParentNode(node, linkCollection).slice(1); + const propertyName: ts.Identifier = node.name as ts.Identifier; + let initText: string; + const LINK_REG: RegExp = /^\$/g; + if (isRecycleComponent && ts.isShorthandPropertyAssignment(node)) { + return node; + } + const initExpression: ts.Expression = node.initializer; + let is$$: boolean = false; + if (ts.isIdentifier(initExpression) && + initExpression.escapedText.toString().match(LINK_REG)) { + initText = initExpression.escapedText.toString().replace(LINK_REG, ''); + } else if (ts.isPropertyAccessExpression(initExpression) && initExpression.expression && + initExpression.expression.kind === ts.SyntaxKind.ThisKeyword && + ts.isIdentifier(initExpression.name) && initExpression.name.escapedText.toString().match(LINK_REG)) { + initText = initExpression.name.escapedText.toString().replace(LINK_REG, ''); + } else if (isBuilder && ts.isPropertyAccessExpression(initExpression) && initExpression.expression && + ts.isIdentifier(initExpression.expression) && initExpression.expression.escapedText.toString() === $$ && + ts.isIdentifier(initExpression.name) && linkParentComponent.includes(propertyName.escapedText.toString())) { + is$$ = true; + initText = initExpression.name.escapedText.toString(); + } else if (isMatchInitExpression(initExpression) && + linkParentComponent.includes(propertyName.escapedText.toString())) { + initText = initExpression.name.escapedText.toString().replace(LINK_REG, ''); + } + if (initText) { + node = addDoubleUnderline(node, propertyName, initText, is$$, isParamsLambda, isRecycleComponent); + } + return node; +} + +function isMatchInitExpression(initExpression: ts.Expression): boolean { + return ts.isPropertyAccessExpression(initExpression) && + initExpression.expression && + initExpression.expression.kind === ts.SyntaxKind.ThisKeyword && + ts.isIdentifier(initExpression.name); +} + +function addDoubleUnderline(node: ts.PropertyAssignment, propertyName: ts.Identifier, + initText: string, is$$ = false, isParamsLambda: boolean, isRecycleComponent: boolean): ts.PropertyAssignment { + return ts.factory.updatePropertyAssignment(node, propertyName, + ts.factory.createPropertyAccessExpression( + is$$ && partialUpdateConfig.partialUpdateMode ? ts.factory.createIdentifier($$) : ts.factory.createThis(), + isParamsLambda || isRecycleComponent ? ts.factory.createIdentifier(initText) : ts.factory.createIdentifier(`__${initText}`))); +} + +function getParentNode(node: ts.PropertyAssignment, collection: Map>): string[] { + const grandparentNode: ts.NewExpression = node.parent.parent as ts.NewExpression; + const grandparentExpression: ts.Identifier | ts.PropertyAccessExpression = + grandparentNode.expression as ts.Identifier | ts.PropertyAccessExpression; + let parentComponent: Set = new Set(); + let grandparentName: string; + if (ts.isIdentifier(grandparentExpression)) { + grandparentName = grandparentExpression.escapedText.toString(); + parentComponent = collection.get(grandparentName); + } else if (ts.isPropertyAccessExpression(grandparentExpression)) { + if (storedFileInfo.isAsPageImport) { + grandparentName = grandparentExpression.getText(); + } else { + grandparentName = grandparentExpression.name.escapedText.toString(); + } + parentComponent = collection.get(grandparentName); + } else { + // ignore + } + if (!parentComponent) { + parentComponent = new Set(); + } + return [grandparentName, ...parentComponent]; +} + +function addUpdateParamsFunc(statements: ts.Statement[], parentComponentName: ts.Identifier): + ts.MethodDeclaration { + return createParamsInitBlock(COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, statements, parentComponentName); +} + +function addInitialParamsFunc(statements: ts.Statement[], parentComponentName: ts.Identifier): ts.MethodDeclaration { + return createParamsInitBlock(COMPONENT_CONSTRUCTOR_INITIAL_PARAMS, statements, parentComponentName); +} + +function addUpdateStateVarsFunc(statements: ts.Statement[], parentComponentName: ts.Identifier): ts.MethodDeclaration { + return createParamsInitBlock(COMPONENT_UPDATE_STATE_VARS, statements, parentComponentName); +} + +function addPurgeVariableDepFunc(statements: ts.Statement[]): ts.MethodDeclaration { + return ts.factory.createMethodDeclaration( + undefined, undefined, + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PURGE_VARIABLE_DEP), + undefined, undefined, [ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(RMELMTID), undefined, undefined, undefined)], undefined, + ts.factory.createBlock(statements, true)); +} + +function addDeleteParamsFunc(statements: ts.PropertyDeclaration[], + updateRecyle: boolean = false): ts.MethodDeclaration { + const deleteStatements: ts.ExpressionStatement[] = []; + const updateStatements: ts.ExpressionStatement[] = []; + statements.forEach((statement: ts.PropertyDeclaration) => { + const name: ts.Identifier = statement.name as ts.Identifier; + let paramsStatement: ts.ExpressionStatement; + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(statement); + const isRegular: boolean = isRegularProperty(decorators); + if (!partialUpdateConfig.partialUpdateMode || !isRegular) { + paramsStatement = createParamsWithUnderlineStatement(name); + } + if (partialUpdateConfig.partialUpdateMode && !isRegular) { + updateStatements.push(createElmtIdWithUnderlineStatement(name)); + } + deleteStatements.push(paramsStatement); + }); + if (partialUpdateConfig.partialUpdateMode && updateRecyle) { + return createRecycleElmt(updateStatements); + } + const defaultStatement: ts.ExpressionStatement = + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(CREATE_CONSTRUCTOR_SUBSCRIBER_MANAGER), + ts.factory.createIdentifier(CREATE_CONSTRUCTOR_GET_FUNCTION)), undefined, []), + ts.factory.createIdentifier(CREATE_CONSTRUCTOR_DELETE_FUNCTION)), + undefined, [ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), ts.factory.createIdentifier( + !partialUpdateConfig.partialUpdateMode ? + ABOUT_TO_BE_DELETE_FUNCTION_ID : ABOUT_TO_BE_DELETE_FUNCTION_ID__)), + undefined, [])])); + deleteStatements.push(defaultStatement); + if (partialUpdateConfig.partialUpdateMode) { + const aboutToBeDeletedInternalStatement: ts.ExpressionStatement = createDeletedInternalStatement(); + deleteStatements.push(aboutToBeDeletedInternalStatement); + } + const deleteParamsMethod: ts.MethodDeclaration = + createParamsInitBlock(COMPONENT_CONSTRUCTOR_DELETE_PARAMS, deleteStatements); + return deleteParamsMethod; +} + +function createRecycleElmt(statements: ts.Statement[]): ts.MethodDeclaration { + return ts.factory.createMethodDeclaration(undefined, undefined, + ts.factory.createIdentifier(UPDATE_RECYCLE_ELMT_ID), undefined, undefined, [ + ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(OLD_ELMT_ID)), + ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(NEW_ELMT_ID)) + ], undefined, ts.factory.createBlock(statements, true)); +} + +function createParamsWithUnderlineStatement(name: ts.Identifier): ts.ExpressionStatement { + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + ts.factory.createIdentifier(`__${name.escapedText.toString()}`)), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_DELETE_PARAMS)), undefined, [])); +} + +function createElmtIdWithUnderlineStatement(name: ts.Identifier): ts.ExpressionStatement { + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + ts.factory.createIdentifier(`__${name.escapedText.toString()}`)), + ts.factory.createIdentifier(COMPONENT_UPDATE_ELMT_ID)), undefined, [ + ts.factory.createIdentifier(OLD_ELMT_ID), ts.factory.createIdentifier(NEW_ELMT_ID) + ])); +} + +function createDeletedInternalStatement(): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + ts.factory.createIdentifier(ABOUTTOBEDELETEDINTERNAL)), undefined, [])); +} + +export function addRerenderFunc(statements: ts.Statement[]): ts.MethodDeclaration { + const updateDirtyElementStatement: ts.Statement = ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), ts.factory.createIdentifier(UPDATEDIRTYELEMENTS)), undefined, [])); + statements.push(updateDirtyElementStatement); + if (storedFileInfo.hasLocalBuilderInFile) { + statements.unshift(contextStackPushOrPop(ts.factory.createIdentifier(PUSH), [ts.factory.createThis()])); + statements.push(contextStackPushOrPop(ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), [])); + } + return ts.factory.createMethodDeclaration(undefined, undefined, + ts.factory.createIdentifier(COMPONENT_RERENDER_FUNCTION), undefined, undefined, [], undefined, + ts.factory.createBlock(statements, true)); +} + +function createParamsInitBlock(express: string, statements: ts.Statement[], + parentComponentName?: ts.Identifier): ts.MethodDeclaration { + const methodDeclaration: ts.MethodDeclaration = ts.factory.createMethodDeclaration( + undefined, undefined, ts.factory.createIdentifier(express), undefined, undefined, + [ts.factory.createParameterDeclaration(undefined, undefined, + express === COMPONENT_CONSTRUCTOR_DELETE_PARAMS ? undefined : + ts.factory.createIdentifier(CREATE_CONSTRUCTOR_PARAMS), undefined, + express === COMPONENT_CONSTRUCTOR_DELETE_PARAMS ? undefined : + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier(parentComponentName.getText() + INTERFACE_NAME_SUFFIX), undefined), + undefined)], undefined, ts.factory.createBlock(statements, true)); + return methodDeclaration; +} + +export function validateBuildMethodCount(buildCount: BuildCount, parentComponentName: ts.Identifier, + log: LogInfo[]): void { + if (buildCount.count !== 1) { + log.push({ + type: LogType.ERROR, + message: `struct '${parentComponentName.getText()}' must be at least or at most one 'build' method.`, + pos: parentComponentName.getStart(), + code: '10905103', + solutions: [`A structurally modified page must have at least one and no more than one 'build' method.`] + }); + } +} + +function validateHasControllerAndControllerCount(componentName: ts.Identifier, checkController: ControllerType, + log: LogInfo[]): void { + if (!checkController.hasController) { + log.push({ + type: LogType.ERROR, + message: '@CustomDialog component should have a property of the CustomDialogController type.', + pos: componentName.pos, + code: '10905211' + }); + } + if (checkController.unassignedControllerSet.size >= 2) { + log.push({ + type: LogType.WARN, + message: 'A @CustomDialog component can only have one uninitialized CustomDialogController.', + pos: componentName.pos + }); + } +} + +function createHeritageClause(isComponentV2: boolean = false): ts.HeritageClause { + if (partialUpdateConfig.partialUpdateMode) { + return ts.factory.createHeritageClause( + ts.SyntaxKind.ExtendsKeyword, + [ts.factory.createExpressionWithTypeArguments( + ts.factory.createIdentifier(isComponentV2 ? constantDefine.STRUCT_PARENT : BASE_COMPONENT_NAME_PU), + [])] + ); + } + return ts.factory.createHeritageClause( + ts.SyntaxKind.ExtendsKeyword, + [ts.factory.createExpressionWithTypeArguments(ts.factory.createIdentifier(BASE_COMPONENT_NAME), [])] + ); +} + +function createTypeReference(decoratorName: string, type: ts.TypeNode, log: LogInfo[], + program: ts.Program): ts.TypeNode { + let newType: ts.TypeNode; + switch (decoratorName) { + case COMPONENT_STATE_DECORATOR: + case COMPONENT_PROVIDE_DECORATOR: + newType = ts.factory.createTypeReferenceNode( + isSimpleType(type, program, log) ? + OBSERVED_PROPERTY_SIMPLE : + OBSERVED_PROPERTY_OBJECT, + [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)] + ); + break; + case COMPONENT_LINK_DECORATOR: + case COMPONENT_CONSUME_DECORATOR: + newType = ts.factory.createTypeReferenceNode( + isSimpleType(type, program, log) ? + SYNCHED_PROPERTY_SIMPLE_TWO_WAY : + SYNCHED_PROPERTY_SIMPLE_ONE_WAY, + [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)] + ); + break; + case COMPONENT_PROP_DECORATOR: + newType = ts.factory.createTypeReferenceNode( + SYNCHED_PROPERTY_SIMPLE_ONE_WAY, + [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)] + ); + break; + case COMPONENT_OBJECT_LINK_DECORATOR: + newType = ts.factory.createTypeReferenceNode( + SYNCHED_PROPERTY_NESED_OBJECT, + [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)] + ); + break; + case COMPONENT_STORAGE_PROP_DECORATOR: + case COMPONENT_STORAGE_LINK_DECORATOR: + newType = ts.factory.createTypeReferenceNode(OBSERVED_PROPERTY_ABSTRACT, [ + type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ]); + break; + case COMPONENT_LOCAL_STORAGE_LINK_DECORATOR: + case COMPONENT_LOCAL_STORAGE_PROP_DECORATOR: + newType = ts.factory.createTypeReferenceNode(OBSERVED_PROPERTY_ABSTRACT, [ + type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ]); + break; + } + return newType; +} + +function createTypeReferencePU(decoratorName: string, type: ts.TypeNode, log: LogInfo[], + program: ts.Program): ts.TypeNode { + let newType: ts.TypeNode; + switch (decoratorName) { + case COMPONENT_STATE_DECORATOR: + case COMPONENT_PROVIDE_DECORATOR: + newType = ts.factory.createTypeReferenceNode( + isSimpleType(type, program, log) ? + OBSERVED_PROPERTY_SIMPLE_PU : + OBSERVED_PROPERTY_OBJECT_PU, + [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)] + ); + break; + case COMPONENT_LINK_DECORATOR: + newType = ts.factory.createTypeReferenceNode( + isSimpleType(type, program, log) ? + SYNCHED_PROPERTY_SIMPLE_TWO_WAY_PU : + SYNCHED_PROPERTY_SIMPLE_ONE_WAY_PU, + [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)] + ); + break; + case COMPONENT_PROP_DECORATOR: + newType = ts.factory.createTypeReferenceNode( + SYNCHED_PROPERTY_SIMPLE_ONE_WAY_PU, + [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)] + ); + break; + case COMPONENT_OBJECT_LINK_DECORATOR: + newType = ts.factory.createTypeReferenceNode( + SYNCHED_PROPERTY_NESED_OBJECT_PU, + [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)] + ); + break; + case COMPONENT_CONSUME_DECORATOR: + case COMPONENT_STORAGE_PROP_DECORATOR: + case COMPONENT_STORAGE_LINK_DECORATOR: + newType = ts.factory.createTypeReferenceNode(OBSERVED_PROPERTY_ABSTRACT_PU, [ + type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ]); + break; + case COMPONENT_LOCAL_STORAGE_LINK_DECORATOR: + case COMPONENT_LOCAL_STORAGE_PROP_DECORATOR: + newType = ts.factory.createTypeReferenceNode(OBSERVED_PROPERTY_ABSTRACT_PU, [ + type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ]); + break; + } + return newType; +} + +export function checkFinalizeConstruction(): ts.Statement { + return ts.factory.createIfStatement( + ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, + ts.factory.createParenthesizedExpression(ts.factory.createBinaryExpression( + ts.factory.createStringLiteral(FINALIZE_CONSTRUCTION), + ts.factory.createToken(ts.SyntaxKind.InKeyword), ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(BASE_COMPONENT_NAME_PU), ts.factory.createIdentifier(PROTOTYPE)))) + ), + ts.factory.createBlock( + [ + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(REFLECT), ts.factory.createIdentifier(CREATE_SET_METHOD) + ), undefined, + [ + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(BASE_COMPONENT_NAME_PU), ts.factory.createIdentifier(PROTOTYPE) + ), + ts.factory.createStringLiteral(FINALIZE_CONSTRUCTION), + ts.factory.createArrowFunction(undefined, undefined, [], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock([], false) + ) + ] + )) + ], true), undefined); +} + +export function checkContextStack(): ts.Statement { + return ts.factory.createIfStatement( + ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(PUV2_VIEW_BASE), + ts.factory.createIdentifier(CONTEXT_STACK) + ), + ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED) + ), + ts.factory.createBlock( + [ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(REFLECT), + ts.factory.createIdentifier(CREATE_SET_METHOD) + ), + undefined, + [ + ts.factory.createIdentifier(PUV2_VIEW_BASE), + ts.factory.createStringLiteral(CONTEXT_STACK), + ts.factory.createArrayLiteralExpression( + [], + false + ) + ] + ))], + true + ), + undefined + ); +} + +export function contextStackPushOrPop(pushOrPop: ts.Identifier, param: ts.ThisExpression[] | []): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(PUV2_VIEW_BASE), + ts.factory.createIdentifier(CONTEXT_STACK) + ), + ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(PUV2_VIEW_BASE), + ts.factory.createIdentifier(CONTEXT_STACK) + ), + pushOrPop + ), + undefined, + param + ) + )); +} diff --git a/compiler/src/interop/src/process_component_constructor.ts b/compiler/src/interop/src/process_component_constructor.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd875fe2bba6d989a63569023931cf8ed80bcc1c --- /dev/null +++ b/compiler/src/interop/src/process_component_constructor.ts @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2021 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 ts from 'typescript'; + +import { + COMPONENT_CONSTRUCTOR_ID, + COMPONENT_CONSTRUCTOR_PARENT, + COMPONENT_CONSTRUCTOR_PARAMS, + COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, + COMPONENT_CONSTRUCTOR_INITIAL_PARAMS, + COMPONENT_WATCH_FUNCTION, + BASE_COMPONENT_NAME, + INTERFACE_NAME_SUFFIX, + COMPONENT_CONSTRUCTOR_LOCALSTORAGE, + BASE_COMPONENT_NAME_PU, + COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU, + COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU, + ELMTID, + COMPONENT_PARAMS_LAMBDA_FUNCTION, + COMPONENT_IF_UNDEFINED, + CUSTOM_COMPONENT_EXTRAINFO +} from './pre_define'; +import { partialUpdateConfig } from '../main'; +import createAstNodeUtils from './create_ast_node_utils'; + +export function getInitConstructor(members: ts.NodeArray, parentComponentName: ts.Identifier +): ts.ConstructorDeclaration { + let ctorNode: any = members.find(item => { + return ts.isConstructorDeclaration(item); + }); + if (ctorNode) { + ctorNode = updateConstructor(ctorNode, [], [], [], true); + } + return initConstructorParams(ctorNode, parentComponentName); +} + +export function updateConstructor(ctorNode: ts.ConstructorDeclaration, para: ts.ParameterDeclaration[], + addStatements: ts.Statement[], decoratorComponentParent: ts.IfStatement[], isSuper: boolean = false, + isAdd: boolean = false, parentComponentName?: ts.Identifier): ts.ConstructorDeclaration { + let modifyPara: ts.ParameterDeclaration[]; + if (para && para.length) { + modifyPara = Array.from(ctorNode.parameters); + if (modifyPara) { + modifyPara.push(...para); + } + } + let modifyBody: ts.Statement[]; + if (addStatements && addStatements.length && ctorNode) { + modifyBody = Array.from(ctorNode.body.statements); + if (modifyBody) { + if (isSuper) { + modifyBody.unshift(...addStatements); + if (decoratorComponentParent && decoratorComponentParent.length) { + modifyBody.push(...decoratorComponentParent); + } + } else { + modifyBody.push(...addStatements); + } + } + } + if (ctorNode) { + let ctorPara: ts.ParameterDeclaration[] | ts.NodeArray = + modifyPara || ctorNode.parameters; + if (isAdd) { + ctorPara = addParamsType(ctorNode, modifyPara, parentComponentName); + } + ctorNode = ts.factory.updateConstructorDeclaration(ctorNode, + ts.getModifiers(ctorNode), modifyPara || ctorNode.parameters, + ts.factory.createBlock(modifyBody || ctorNode.body.statements, true)); + } + return ctorNode; +} + +function initConstructorParams(node: ts.ConstructorDeclaration, parentComponentName: ts.Identifier): + ts.ConstructorDeclaration { + if (!ts.isIdentifier(parentComponentName)) { + return node; + } + const paramNames: Set = !partialUpdateConfig.partialUpdateMode ? new Set([ + COMPONENT_CONSTRUCTOR_ID, + COMPONENT_CONSTRUCTOR_PARENT, + COMPONENT_CONSTRUCTOR_PARAMS, + COMPONENT_CONSTRUCTOR_LOCALSTORAGE + ]) : new Set([ + COMPONENT_CONSTRUCTOR_PARENT, + COMPONENT_CONSTRUCTOR_PARAMS, + COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU, + ELMTID, + COMPONENT_PARAMS_LAMBDA_FUNCTION, + CUSTOM_COMPONENT_EXTRAINFO + ]); + const newParameters: ts.ParameterDeclaration[] = Array.from(node.parameters); + if (newParameters.length !== 0) { + // @ts-ignore + newParameters.splice(0, newParameters.length); + } + paramNames.forEach((paramName: string) => { + newParameters.push(ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(paramName), undefined, undefined, + paramName === ELMTID || paramName === COMPONENT_PARAMS_LAMBDA_FUNCTION ? paramName === ELMTID ? + ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral('1')) : + ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED) : undefined)); + }); + + return ts.factory.updateConstructorDeclaration(node, ts.getModifiers(node), newParameters, node.body); +} + +function addParamsType(ctorNode: ts.ConstructorDeclaration, modifyPara: ts.ParameterDeclaration[], + parentComponentName: ts.Identifier): ts.ParameterDeclaration[] { + const tsPara: ts.ParameterDeclaration[] | ts.NodeArray = + modifyPara || ctorNode.parameters; + const newTSPara: ts.ParameterDeclaration[] = []; + tsPara.forEach((item) => { + let parameter: ts.ParameterDeclaration = item; + switch (item.name.escapedText) { + case COMPONENT_CONSTRUCTOR_ID: + parameter = ts.factory.updateParameterDeclaration(item, ts.getModifiers(item), + item.dotDotDotToken, item.name, item.questionToken, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), item.initializer); + break; + case COMPONENT_CONSTRUCTOR_PARENT: + parameter = ts.factory.createParameterDeclaration(ts.getModifiers(item), + item.dotDotDotToken, item.name, item.questionToken, + ts.factory.createTypeReferenceNode(ts.factory.createIdentifier( + !partialUpdateConfig.partialUpdateMode ? BASE_COMPONENT_NAME : BASE_COMPONENT_NAME_PU), undefined), + item.initializer); + break; + case COMPONENT_CONSTRUCTOR_PARAMS: + parameter = ts.factory.updateParameterDeclaration(item, ts.getModifiers(item), + item.dotDotDotToken, item.name, item.questionToken, + ts.factory.createTypeReferenceNode(ts.factory.createIdentifier( + parentComponentName.getText() + INTERFACE_NAME_SUFFIX), undefined), item.initializer); + break; + case COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU: + parameter = ts.factory.createParameterDeclaration(ts.getModifiers(item), item.dotDotDotToken, + item.name, ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU), undefined), item.initializer); + break; + } + newTSPara.push(parameter); + }); + return newTSPara; +} + +export function addConstructor(ctorNode: any, watchMap: Map, + parentComponentName: ts.Identifier): ts.ConstructorDeclaration { + const watchStatements: ts.ExpressionStatement[] = []; + watchMap.forEach((value, key) => { + const watchNode: ts.ExpressionStatement = ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(COMPONENT_WATCH_FUNCTION) + ), + undefined, + [ + ts.factory.createStringLiteral(key), + ts.isStringLiteral(value) ? + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + ts.factory.createIdentifier(value.text)) : value as ts.PropertyAccessExpression + ] + )); + watchStatements.push(watchNode); + }); + const callSuperStatement: ts.Statement = createCallSuperStatement(); + const updateWithValueParamsStatement: ts.Statement = createUPdWithValStatement(); + const newBody: ts.Statement[] = [updateWithValueParamsStatement, ...watchStatements]; + if (partialUpdateConfig.partialUpdateMode) { + newBody.push(createAstNodeUtils.createFinalizeConstruction()); + } + return updateConstructor(updateConstructor(ctorNode, [], [callSuperStatement], [], true), [], + newBody, [], false, true, parentComponentName); +} + +function createCallSuperStatement(): ts.Statement { + if (!partialUpdateConfig.partialUpdateMode) { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createSuper(), undefined, + [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_ID), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE)])); + } else { + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createSuper(), undefined, + [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU), + ts.factory.createIdentifier(ELMTID), + ts.factory.createIdentifier(CUSTOM_COMPONENT_EXTRAINFO)])); + } +} + +function createUPdWithValStatement(): ts.Statement { + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier( + !partialUpdateConfig.partialUpdateMode ? + COMPONENT_CONSTRUCTOR_UPDATE_PARAMS : COMPONENT_CONSTRUCTOR_INITIAL_PARAMS + ) + ), + undefined, + [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS)] + ) + ); +} diff --git a/compiler/src/interop/src/process_component_member.ts b/compiler/src/interop/src/process_component_member.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d0e948d61feae9a60bdf4b6db66a101cd371520 --- /dev/null +++ b/compiler/src/interop/src/process_component_member.ts @@ -0,0 +1,1454 @@ +/* + * Copyright (c) 2021 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 ts from 'typescript'; +const path = require('path'); + +import { + INNER_COMPONENT_MEMBER_DECORATORS, + COMPONENT_NON_DECORATOR, + COMPONENT_STATE_DECORATOR, + COMPONENT_PROP_DECORATOR, + COMPONENT_LINK_DECORATOR, + COMPONENT_STORAGE_PROP_DECORATOR, + COMPONENT_STORAGE_LINK_DECORATOR, + COMPONENT_PROVIDE_DECORATOR, + COMPONENT_CONSUME_DECORATOR, + COMPONENT_OBJECT_LINK_DECORATOR, + COMPONENT_WATCH_DECORATOR, + COMPONENT_OBSERVED_DECORATOR, + OBSERVED_PROPERTY_SIMPLE, + OBSERVED_PROPERTY_OBJECT, + SYNCHED_PROPERTY_SIMPLE_ONE_WAY, + SYNCHED_PROPERTY_SIMPLE_TWO_WAY, + SYNCHED_PROPERTY_OBJECT_TWO_WAY, + SYNCHED_PROPERTY_NESED_OBJECT, + CREATE_GET_METHOD, + CREATE_SET_METHOD, + CREATE_NEWVALUE_IDENTIFIER, + CREATE_CONSTRUCTOR_PARAMS, + ADD_PROVIDED_VAR, + INITIALIZE_CONSUME_FUNCTION, + APP_STORAGE, + APP_STORAGE_SET_AND_PROP, + APP_STORAGE_SET_AND_LINK, + COMPONENT_CONSTRUCTOR_UNDEFINED, + SET_CONTROLLER_METHOD, + SET_CONTROLLER_CTR, + SET_CONTROLLER_CTR_TYPE, + BASE_COMPONENT_NAME, + COMPONENT_CREATE_FUNCTION, + COMPONENT_BUILDERPARAM_DECORATOR, + COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, + COMPONENT_LOCAL_STORAGE_PROP_DECORATOR, + EXTNAME_ETS, + _GENERATE_ID, + RMELMTID, + PURGEDEPENDENCYONELMTID, + BASICDECORATORS, + BASE_COMPONENT_NAME_PU, + OBSERVED_PROPERTY_SIMPLE_PU, + OBSERVED_PROPERTY_OBJECT_PU, + SYNCHED_PROPERTY_SIMPLE_TWO_WAY_PU, + SYNCHED_PROPERTY_OBJECT_TWO_WAY_PU, + SYNCHED_PROPERTY_SIMPLE_ONE_WAY_PU, + SYNCHED_PROPERTY_OBJECT_ONE_WAY_PU, + SYNCHED_PROPERTY_NESED_OBJECT_PU, + COMPONENT_CUSTOM_DECORATOR, + THIS, + CREATE_STORAGE_LINK, + CREATE_STORAGE_PROP, + ELMTID, + COMPONENT_CONSTRUCTOR_PARAMS, + RESERT, + COMPONENT_IF_UNDEFINED, + COMPONENT_REQUIRE_DECORATOR, + TRUE, + FALSE, + MIN_OBSERVED, + COMPONENT_OBSERVEDV2_DECORATOR +} from './pre_define'; +import { + forbiddenUseStateType, + BUILDIN_STYLE_NAMES +} from './component_map'; +import { + observedClassCollection, + enumCollection, + componentCollection, + classMethodCollection +} from './validate_ui_syntax'; +import { updateConstructor } from './process_component_constructor'; +import { + LogType, + LogInfo, + componentInfo, + storedFileInfo +} from './utils'; +import { + createReference, + isProperty, + isRegularProperty +} from './process_component_class'; +import { transformLog, resourceFileName } from './process_ui_syntax'; +import { + globalProgram, + projectConfig, + partialUpdateConfig +} from '../main'; +import { + parentConditionalExpression, + createFunction, + getRealNodePos, + isWrappedBuilder +} from './process_component_build'; +import { + CUSTOM_BUILDER_METHOD, + INNER_CUSTOM_LOCALBUILDER_METHOD +} from './component_map'; +import { + isAllowedTypeForBasic, + isFunctionType +} from './process_custom_component'; +export type ControllerType = { + hasController: boolean; + unassignedControllerSet: Set; +}; + +export const observedPropertyDecorators: Set = + new Set([COMPONENT_STATE_DECORATOR, COMPONENT_PROVIDE_DECORATOR]); + +export const propAndLinkDecorators: Set = + new Set([COMPONENT_PROP_DECORATOR, COMPONENT_LINK_DECORATOR]); + +export const appStorageDecorators: Set = + new Set([COMPONENT_STORAGE_PROP_DECORATOR, COMPONENT_STORAGE_LINK_DECORATOR, + COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, COMPONENT_LOCAL_STORAGE_PROP_DECORATOR]); + +export const mandatorySpecifyDefaultValueDecorators: Set = + new Set([...observedPropertyDecorators, ...appStorageDecorators]); + +export const requireCanReleaseMandatoryDecorators: Set = + new Set([COMPONENT_PROP_DECORATOR, COMPONENT_BUILDERPARAM_DECORATOR, + ...observedPropertyDecorators]); + +export const forbiddenSpecifyDefaultValueDecorators: Set = + new Set([COMPONENT_LINK_DECORATOR, COMPONENT_CONSUME_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]); + +export const mandatoryToInitViaParamDecorators: Set = + new Set([...propAndLinkDecorators, COMPONENT_OBJECT_LINK_DECORATOR]); + +export const setUpdateParamsDecorators: Set = + new Set([...observedPropertyDecorators, COMPONENT_PROP_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR, + COMPONENT_BUILDERPARAM_DECORATOR + ]); + +export const setStateVarsDecorators: Set = new Set([COMPONENT_OBJECT_LINK_DECORATOR]); + +export const immutableDecorators: Set = + new Set([COMPONENT_OBJECT_LINK_DECORATOR, COMPONENT_BUILDERPARAM_DECORATOR]); + +export const simpleTypes: Set = new Set([ts.SyntaxKind.StringKeyword, + ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.BooleanKeyword, ts.SyntaxKind.EnumDeclaration]); + +export const decoratorParamSet: Set = new Set(); + +export const stateObjectCollection: Set = new Set(); + +export class UpdateResult { + private itemUpdate: boolean = false; + private ctorUpdate: boolean = false; + private properity: ts.PropertyDeclaration; + private ctor: ts.ConstructorDeclaration; + private variableGet: ts.GetAccessorDeclaration; + private variableSet: ts.SetAccessorDeclaration; + private updateParams: ts.Statement; + private deleteParams: boolean = false; + private controllerSet: ts.MethodDeclaration; + private purgeVariableDepStatement: ts.Statement; + private decoratorName: string; + private stateVarsParams: ts.Statement; + + public setProperity(updateItem: ts.PropertyDeclaration) { + this.itemUpdate = true; + this.properity = updateItem; + } + + public setCtor(updateCtor: ts.ConstructorDeclaration) { + this.ctorUpdate = true; + this.ctor = updateCtor; + } + + public setControllerSet(updateControllerSet: ts.MethodDeclaration) { + this.controllerSet = updateControllerSet; + } + + public getControllerSet(): ts.MethodDeclaration { + return this.controllerSet; + } + + public setVariableGet(updateVariableGet: ts.GetAccessorDeclaration) { + this.variableGet = updateVariableGet; + } + + public setVariableSet(updateVariableSet: ts.SetAccessorDeclaration) { + this.variableSet = updateVariableSet; + } + + public setUpdateParams(updateParams: ts.Statement) { + this.updateParams = updateParams; + } + + public setStateVarsParams(stateVarsParams: ts.Statement) { + this.stateVarsParams = stateVarsParams; + } + + public setDeleteParams(deleteParams: boolean) { + this.deleteParams = deleteParams; + } + + public setPurgeVariableDepStatement(purgeVariableDepStatement: ts.Statement) { + this.purgeVariableDepStatement = purgeVariableDepStatement; + } + + public setDecoratorName(decoratorName: string) { + this.decoratorName = decoratorName; + } + + public isItemUpdate(): boolean { + return this.itemUpdate; + } + + public isCtorUpdate(): boolean { + return this.ctorUpdate; + } + + public getProperity(): ts.PropertyDeclaration { + return this.properity; + } + + public getCtor(): ts.ConstructorDeclaration { + return this.ctor; + } + + public getUpdateParams(): ts.Statement { + return this.updateParams; + } + + public getStateVarsParams(): ts.Statement { + return this.stateVarsParams; + } + + public getPurgeVariableDepStatement(): ts.Statement { + return this.purgeVariableDepStatement; + } + + public getVariableGet(): ts.GetAccessorDeclaration { + return this.variableGet; + } + + public getVariableSet(): ts.SetAccessorDeclaration { + return this.variableSet; + } + + public getDecoratorName(): string { + return this.decoratorName; + } + + public isDeleteParams(): boolean { + return this.deleteParams; + } +} + +export class PropMapManager { + static curPropMap: Map = new Map(); + static logInfoMap: Map = new Map(); + + public static register(identifierName: string, decoratorName: string): void { + PropMapManager.curPropMap.set(identifierName, decoratorName); + + if (decoratorName !== COMPONENT_NON_DECORATOR) { + PropMapManager.releaseLogs(identifierName, COMPONENT_NON_DECORATOR); + } + } + + public static find(identifierName: string): string { + return PropMapManager.curPropMap.get(identifierName); + } + + public static reserveLog(identifierName: string, decoratorName: string, log: LogInfo): void { + const key: string = `${identifierName}-${decoratorName}`; + const logInfos: LogInfo[] = PropMapManager.logInfoMap.get(key) ?? []; + PropMapManager.logInfoMap.set(key, [...logInfos, log]); + } + + public static releaseLogs(identifierName: string, decoratorName: string): void { + const key: string = `${identifierName}-${decoratorName}`; + if (PropMapManager.logInfoMap.has(key)) { + PropMapManager.logInfoMap.delete(key); + } + } + + public static reset(): void { + PropMapManager.curPropMap.clear(); + PropMapManager.logInfoMap.clear(); + } +} + +export function processMemberVariableDecorators(parentName: ts.Identifier, + item: ts.PropertyDeclaration, ctorNode: ts.ConstructorDeclaration, watchMap: Map, + checkController: ControllerType, log: LogInfo[], program: ts.Program, context: ts.TransformationContext, + hasPreview: boolean, interfaceNode: ts.InterfaceDeclaration): UpdateResult { + const updateResult: UpdateResult = new UpdateResult(); + const name: ts.Identifier = item.name as ts.Identifier; + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item); + if (isRegularProperty(decorators)) { + if (!name.escapedText) { + return updateResult; + } + PropMapManager.register(name.escapedText.toString(), COMPONENT_NON_DECORATOR); + updateResult.setProperity(undefined); + updateResult.setUpdateParams(createUpdateParams(name, COMPONENT_NON_DECORATOR)); + updateResult.setCtor(updateConstructor(ctorNode, [], [ + createVariableInitStatement(item, COMPONENT_NON_DECORATOR, log, program, context, hasPreview, + interfaceNode)], [])); + updateResult.setControllerSet(createControllerSet(item, parentName, name, checkController)); + if (partialUpdateConfig.partialUpdateMode) { + updateResult.setDeleteParams(true); + } + } else if (!item.type) { + validatePropertyNonType(name, log); + return updateResult; + } else if (validateCustomDecorator(decorators, log)) { + updateResult.setUpdateParams(createUpdateParams(name, COMPONENT_CUSTOM_DECORATOR)); + } else { + processPropertyNodeDecorator(parentName, item, updateResult, ctorNode, name, watchMap, + log, program, context, hasPreview, interfaceNode); + } + if (decorators && decorators.length && validatePropDecorator(decorators)) { + updateResult.setStateVarsParams(createStateVarsBody(name)); + } + return updateResult; +} + +function createStateVarsBody(name: ts.Identifier): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier('__' + name.escapedText.toString()) + ), + ts.factory.createIdentifier(RESERT) + ), + undefined, + [ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS), + name + )] + )); +} + +function createControllerSet(node: ts.PropertyDeclaration, componentName: ts.Identifier, + name: ts.Identifier, checkController: ControllerType): ts.MethodDeclaration { + if (componentCollection.customDialogs.has(componentName.getText()) && node.type && + node.type.getText() === SET_CONTROLLER_CTR_TYPE) { + checkController.unassignedControllerSet.add(name.getText()); + checkController.hasController = true; + return ts.factory.createMethodDeclaration(undefined, undefined, + ts.factory.createIdentifier(SET_CONTROLLER_METHOD), undefined, undefined, + [ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(SET_CONTROLLER_CTR), undefined, + ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(SET_CONTROLLER_CTR_TYPE), + undefined), undefined)], undefined, ts.factory.createBlock( + [ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), name), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + ts.factory.createIdentifier(SET_CONTROLLER_CTR)))], true)); + } + return undefined; +} + +function processPropertyNodeDecorator(parentName: ts.Identifier, node: ts.PropertyDeclaration, + updateResult: UpdateResult, ctorNode: ts.ConstructorDeclaration, name: ts.Identifier, + watchMap: Map, log: LogInfo[], program: ts.Program, + context: ts.TransformationContext, hasPreview: boolean, interfaceNode: ts.InterfaceDeclaration): void { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + const propertyDecorators: string[] = []; + for (let i = 0; i < decorators.length; i++) { + const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); + const includeWatchAndRequire: boolean = + [COMPONENT_WATCH_DECORATOR, COMPONENT_REQUIRE_DECORATOR].includes(decoratorName); + if (!includeWatchAndRequire) { + PropMapManager.register(name.escapedText.toString(), decoratorName); + } + checkDecoratorIsDuplicated(decoratorName, decorators[i], log); + if (checkDecoratorIsForbidden(node, includeWatchAndRequire, decoratorName, name, log)) { + return; + } + checkDecoratorIsIllegalInEntry(parentName, decoratorName, name, log); + if (checkDecoratorIsIllegalInPropertyInit(node, decoratorName, name, log)) { + return; + } + checkDecoratorHasIllegalQuestionToken(node, decoratorName, name, log); + if (!isSimpleType(node.type, program) && + decoratorName !== COMPONENT_BUILDERPARAM_DECORATOR) { + stateObjectCollection.add(name.escapedText.toString()); + } + if (decoratorName === COMPONENT_WATCH_DECORATOR && + validateWatchDecorator(name, decorators, log)) { + processWatch(node, decorators[i], watchMap, log); + } else if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { + propertyDecorators.push(decoratorName); + if (decoratorName !== COMPONENT_REQUIRE_DECORATOR) { + processStateDecorators(node, decoratorName, updateResult, ctorNode, log, program, context, + hasPreview, interfaceNode); + } + } + } + validatePropertyDecorator(propertyDecorators, name, log); +} + +function checkDecoratorHasIllegalQuestionToken( + node: ts.PropertyDeclaration, decoratorName: string, name: ts.Identifier, log: LogInfo[]): void { + if (node.questionToken && mandatoryToInitViaParamDecorators.has(decoratorName) && + !(decoratorName === COMPONENT_PROP_DECORATOR && node.initializer)) { + validateHasIllegalQuestionToken(name, decoratorName, log); + } +} + +function checkDecoratorIsIllegalInPropertyInit( + node: ts.PropertyDeclaration, decoratorName: string, name: ts.Identifier, log: LogInfo[]): boolean { + if (node.initializer && forbiddenSpecifyDefaultValueDecorators.has(decoratorName)) { + validatePropertyDefaultValue(name, decoratorName, log); + return true; + } else if (!node.initializer && !isRequireCanReleaseMandatoryDecorators(node, decoratorName) && + mandatorySpecifyDefaultValueDecorators.has(decoratorName)) { + validatePropertyNonDefaultValue(name, decoratorName, log); + return true; + } + return false; +} + +function checkDecoratorIsIllegalInEntry( + parentName: ts.Identifier, decoratorName: string, name: ts.Identifier, log: LogInfo[]): void { + if (parentName.getText() === componentCollection.entryComponent && + mandatoryToInitViaParamDecorators.has(decoratorName)) { + validateHasIllegalDecoratorInEntry(parentName, name, decoratorName, log); + } +} + +function checkDecoratorIsForbidden( + node: ts.PropertyDeclaration, includeWatchAndRequire: boolean, decoratorName: string, + name: ts.Identifier, log: LogInfo[]): boolean { + if (!includeWatchAndRequire && ts.isTypeReferenceNode(node.type) && isForbiddenUseStateType(node.type)) { + validateForbiddenUseStateType(name, decoratorName, node.type.typeName.getText(), log); + return true; + } + return false; +} + +function checkDecoratorIsDuplicated(decoratorName: string, decorator: ts.Decorator, log: LogInfo[]): void { + if (BUILDIN_STYLE_NAMES.has(decoratorName.replace('@', ''))) { + validateDuplicateDecorator(decorator, log); + } +} + +function isRequireCanReleaseMandatoryDecorators(node: ts.PropertyDeclaration, decoratorName: string): boolean { + if (decoratorName === COMPONENT_REQUIRE_DECORATOR) { + return true; + } + + const decoratorIsNotMandatory: boolean = ts.getAllDecorators(node).find( + (decorator: ts.Decorator) => decorator.getText() === COMPONENT_REQUIRE_DECORATOR) && + requireCanReleaseMandatoryDecorators.has(decoratorName); + return decoratorIsNotMandatory; +} + +function validatePropertyDecorator(propertyDecorators: string[], name: ts.Identifier, + log: LogInfo[]): void { + if (propertyDecorators.length > 1 && !validateRequireDecorator(propertyDecorators)) { + validateMultiDecorators(name, log); + } +} + +const DECORATOR_LENGTH: number = 2; +const SUPPORT_REQUIRE_DECORATOR: string[] = [COMPONENT_PROP_DECORATOR, + COMPONENT_BUILDERPARAM_DECORATOR, COMPONENT_STATE_DECORATOR, COMPONENT_PROVIDE_DECORATOR, + COMPONENT_WATCH_DECORATOR +]; + +function validateRequireDecorator(propertyDecorators: string[]): boolean { + const isSupportRequire: boolean = propertyDecorators.some((item: string) => { + return SUPPORT_REQUIRE_DECORATOR.includes(item); + }); + return propertyDecorators.length === DECORATOR_LENGTH && + propertyDecorators.includes(COMPONENT_REQUIRE_DECORATOR) && isSupportRequire; +} + +function processStateDecorators(node: ts.PropertyDeclaration, decorator: string, + updateResult: UpdateResult, ctorNode: ts.ConstructorDeclaration, log: LogInfo[], + program: ts.Program, context: ts.TransformationContext, hasPreview:boolean, + interfaceNode: ts.InterfaceDeclaration): void { + const name: ts.Identifier = node.name as ts.Identifier; + updateResult.setProperity(undefined); + const updateState: ts.Statement[] = []; + const variableInitStatement: ts.Statement = + createVariableInitStatement(node, decorator, log, program, context, hasPreview, interfaceNode); + if (variableInitStatement) { + updateState.push(variableInitStatement); + } + addAddProvidedVar(node, name, decorator, updateState); + updateResult.setCtor(updateConstructor(ctorNode, [], [...updateState], [], false)); + if (decorator !== COMPONENT_BUILDERPARAM_DECORATOR) { + updateResult.setVariableGet(createGetAccessor(name, CREATE_GET_METHOD)); + updateResult.setDeleteParams(true); + } + if (!immutableDecorators.has(decorator)) { + updateResult.setVariableSet(createSetAccessor(name, CREATE_SET_METHOD, node.type)); + } + if (setUpdateParamsDecorators.has(decorator)) { + updateResult.setUpdateParams(createUpdateParams(name, decorator, node)); + } + if (projectConfig.optLazyForEach) { + setStateVarsDecorators.add(COMPONENT_STATE_DECORATOR); + } + if (setStateVarsDecorators.has(decorator)) { + updateResult.setStateVarsParams(createStateVarsParams(name, decorator)); + } + if (partialUpdateConfig.partialUpdateMode && BASICDECORATORS.has(decorator)) { + const variableWithUnderLink: string = '__' + name.escapedText.toString(); + updateResult.setDecoratorName(decorator); + updateResult.setPurgeVariableDepStatement(createPurgeVariableDepStatement(variableWithUnderLink)); + } +} + +function createPurgeVariableDepStatement(variableWithUnderLink: string): ts.Statement { + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(variableWithUnderLink) + ), + ts.factory.createIdentifier(PURGEDEPENDENCYONELMTID) + ), + undefined, + [ts.factory.createIdentifier(RMELMTID)] + ) + ); +} + +function processWatch(node: ts.PropertyDeclaration, decorator: ts.Decorator, + watchMap: Map, log: LogInfo[]): void { + if (node.name) { + const propertyName: string = node.name.getText(); + if (decorator.expression && ts.isCallExpression(decorator.expression) && + decorator.expression.arguments && decorator.expression.arguments.length === 1) { + const currentClassMethod: Set = getClassMethod(node); + const argument: ts.Node = decorator.expression.arguments[0]; + if (ts.isStringLiteral(argument)) { + if (currentClassMethod && currentClassMethod.has(argument.text)) { + watchMap.set(propertyName, argument); + } else { + log.push({ + type: LogType.ERROR, + message: `Cannot find name ${argument.getText()} in struct '${node.parent.name.getText()}'.`, + pos: argument.getStart(), + code: '10905301' + }); + } + } else if (ts.isIdentifier(decorator.expression.arguments[0])) { + const content: string = decorator.expression.arguments[0].getText(); + const propertyNode: ts.PropertyAccessExpression = createPropertyAccessExpressionWithThis(content); + watchMap.set(propertyName, propertyNode); + decoratorParamSet.add(content); + validateWatchParam(LogType.WARN, argument.getStart(), log); + } else if (ts.isPropertyAccessExpression(decorator.expression.arguments[0])) { + watchMap.set(propertyName, decorator.expression.arguments[0]); + validateWatchParam(LogType.WARN, argument.getStart(), log); + } else { + validateWatchParam(LogType.ERROR, argument.getStart(), log); + } + } + } +} + +function getClassMethod(node: ts.PropertyDeclaration): Set { + const sourceFile: ts.SourceFile = node.getSourceFile(); + const filePath: string = sourceFile ? sourceFile.fileName : undefined; + if (filePath && classMethodCollection.get(filePath)) { + return classMethodCollection.get(filePath).get(node.parent.name.getText()); + } + return new Set(); +} + +function createVariableInitStatement(node: ts.PropertyDeclaration, decorator: string, + log: LogInfo[], program: ts.Program, context: ts.TransformationContext, hasPreview: boolean, + interfaceNode: ts.InterfaceDeclaration): ts.Statement { + const name: ts.Identifier = node.name as ts.Identifier; + let type: ts.TypeNode; + let updateState: ts.ExpressionStatement; + if (node.type) { + type = node.type; + } + switch (decorator) { + case COMPONENT_NON_DECORATOR: + updateState = updateNormalProperty(node, name, log, context); + break; + case COMPONENT_STATE_DECORATOR: + case COMPONENT_PROVIDE_DECORATOR: + updateState = !partialUpdateConfig.partialUpdateMode ? + updateObservedProperty(node, name, type, program) : updateObservedPropertyPU(node, name, type, program); + break; + case COMPONENT_LINK_DECORATOR: + wrongDecoratorInPreview(node, COMPONENT_LINK_DECORATOR, hasPreview, log); + updateState = !partialUpdateConfig.partialUpdateMode ? + updateSynchedPropertyTwoWay(name, type, program) : updateSynchedPropertyTwoWayPU(name, type, program); + break; + case COMPONENT_PROP_DECORATOR: + wrongDecoratorInPreview(node, COMPONENT_PROP_DECORATOR, hasPreview, log); + updateState = !partialUpdateConfig.partialUpdateMode ? + updateSynchedPropertyOneWay(name, type, decorator, log, program) : + updateSynchedPropertyOneWayPU(name, type, decorator, log, program); + break; + case COMPONENT_STORAGE_PROP_DECORATOR: + case COMPONENT_STORAGE_LINK_DECORATOR: + updateState = updateStoragePropAndLinkProperty(node, name, decorator); + break; + case COMPONENT_OBJECT_LINK_DECORATOR: + updateState = !partialUpdateConfig.partialUpdateMode ? + updateSynchedPropertyNesedObject(name, type, decorator, log) : + updateSynchedPropertyNesedObjectPU(name, type, decorator, log); + break; + case COMPONENT_CONSUME_DECORATOR: + wrongDecoratorInPreview(node, COMPONENT_CONSUME_DECORATOR, hasPreview, log); + updateState = updateConsumeProperty(node, name); + break; + case COMPONENT_BUILDERPARAM_DECORATOR: + updateState = updateBuilderParamProperty(node, name, log); + } + const members = interfaceNode.members; + members.push(ts.factory.createPropertySignature(undefined, name, + ts.factory.createToken(ts.SyntaxKind.QuestionToken), type)); + interfaceNode = ts.factory.updateInterfaceDeclaration(interfaceNode, + ts.getModifiers(interfaceNode), interfaceNode.name, interfaceNode.typeParameters, + interfaceNode.heritageClauses, members); + return updateState; +} + +function wrongDecoratorInPreview(node: ts.PropertyDeclaration, decorator: string, + hasPreview: boolean, log: LogInfo[]): void { + if (hasPreview && projectConfig.isPreview) { + log.push({ + type: LogType.WARN, + message: `The variable with ${decorator} in component with @Preview may ` + + `cause error in component preview mode`, + pos: node.getStart() + }); + } +} + +function createUpdateParams(name: ts.Identifier, decorator: string, + localInitializationNode: ts.PropertyDeclaration = undefined): ts.Statement { + let updateParamsNode: ts.Statement; + switch (decorator) { + case COMPONENT_NON_DECORATOR: + case COMPONENT_STATE_DECORATOR: + case COMPONENT_PROVIDE_DECORATOR: + case COMPONENT_CUSTOM_DECORATOR: + updateParamsNode = createUpdateParamsWithIf(name); + break; + case COMPONENT_PROP_DECORATOR: + if (!partialUpdateConfig.partialUpdateMode) { + updateParamsNode = createUpdateParamsWithoutIf(name); + } else { + if (localInitializationNode && localInitializationNode.initializer) { + updateParamsNode = createUpdateParamsWithIf(name, true, + localInitializationNode.initializer); + } + } + break; + case COMPONENT_BUILDERPARAM_DECORATOR: + updateParamsNode = createUpdateParamsWithIf(name); + break; + case COMPONENT_OBJECT_LINK_DECORATOR: + updateParamsNode = createUpdateParamsWithSet(name); + break; + } + return updateParamsNode; +} + +function createStateVarsParams(name: ts.Identifier, decorator: string): ts.Statement { + let updateParamsNode: ts.Statement; + switch (decorator) { + case COMPONENT_OBJECT_LINK_DECORATOR: + updateParamsNode = createUpdateParamsWithSet(name); + break; + case COMPONENT_STATE_DECORATOR: + updateParamsNode = createUpdateParamsForState(name); + break; + } + return updateParamsNode; +} + +function createUpdateParamsWithIf(name: ts.Identifier, isProp: boolean = false, + initializeNode: ts.Expression = undefined): ts.IfStatement { + return ts.factory.createIfStatement(ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(CREATE_CONSTRUCTOR_PARAMS), + ts.factory.createIdentifier(name.escapedText.toString())), + isProp ? ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken) : + ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED)), + isProp ? ts.factory.createBlock([createUpdateParamsWithSet(name, true, initializeNode)]) : + ts.factory.createBlock([ + createUpdateParamsWithoutIf(name)], true), undefined); +} + +function createUpdateParamsForState(name: ts.Identifier): ts.IfStatement { + return ts.factory.createIfStatement(createPropertyAccessExpressionWithParams(name.getText()), + ts.factory.createBlock([createUpdateParamsWithSet(name)])); +} + +function createUpdateParamsWithoutIf(name: ts.Identifier): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(name.getText()), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + createPropertyAccessExpressionWithParams(name.getText()))); +} + +function createUpdateParamsWithSet(name: ts.Identifier, hasElse: boolean = false, + initializeNode: ts.Expression = undefined): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(createPropertyAccessExpressionWithThis(`__${name.getText()}`), + ts.factory.createIdentifier(CREATE_SET_METHOD)), undefined, + [hasElse ? initializeNode : createPropertyAccessExpressionWithParams(name.getText())])); +} + +function updateNormalProperty(node: ts.PropertyDeclaration, name: ts.Identifier, + log: LogInfo[], context: ts.TransformationContext): ts.ExpressionStatement { + const init: ts.Expression = + ts.visitNode(node.initializer, visitDialogController); + function visitDialogController(node: ts.Node): ts.Node { + if (isProperty(node)) { + node = createReference(node as ts.PropertyAssignment, log); + } + return ts.visitEachChild(node, visitDialogController, context); + } + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(name.getText()), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), init || + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED))); +} + +function updateObservedProperty(item: ts.PropertyDeclaration, name: ts.Identifier, + type: ts.TypeNode, program: ts.Program): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(`__${name.getText()}`), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createNewExpression( + ts.factory.createIdentifier(isSimpleType(type, program) ? OBSERVED_PROPERTY_SIMPLE : + OBSERVED_PROPERTY_OBJECT), undefined, [item.initializer, ts.factory.createThis(), + ts.factory.createStringLiteral(name.escapedText.toString())]))); +} + +function updateSynchedPropertyTwoWay(nameIdentifier: ts.Identifier, type: ts.TypeNode, + program: ts.Program): ts.ExpressionStatement { + const name: string = nameIdentifier.escapedText.toString(); + const functionName: string = isSimpleType(type, program) ? + SYNCHED_PROPERTY_SIMPLE_TWO_WAY : SYNCHED_PROPERTY_OBJECT_TWO_WAY; + return createInitExpressionStatementForDecorator(name, functionName, + createPropertyAccessExpressionWithParams(name)); +} + +function updateSynchedPropertyOneWay(nameIdentifier: ts.Identifier, type: ts.TypeNode, + decoractor: string, log: LogInfo[], program: ts.Program): ts.ExpressionStatement { + const name: string = nameIdentifier.escapedText.toString(); + if (isSimpleType(type, program)) { + return createInitExpressionStatementForDecorator(name, SYNCHED_PROPERTY_SIMPLE_ONE_WAY, + createPropertyAccessExpressionWithParams(name)); + } else { + validateNonSimpleType(nameIdentifier, decoractor, log); + return undefined; + } +} + +export function findDecoratorIndex(decorators: readonly ts.Decorator[], nameList: string[]): number { + return decorators.findIndex((item: ts.Decorator) => { + return nameList.includes(item.getText().replace(/\(.*\)$/, '').trim()); + }); +} + +function updateStoragePropAndLinkProperty(node: ts.PropertyDeclaration, name: ts.Identifier, + decorator: string): ts.ExpressionStatement { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (isSingleKey(node)) { + let setFuncName: string; + let storageFuncName: string; + const index: number = findDecoratorIndex(decorators, [decorator]); + const storageValue: ts.Expression[] = [ + decorators[index].expression.arguments[0], + node.initializer, + ts.factory.createThis(), + ts.factory.createStringLiteral(name.getText()) + ]; + if (!partialUpdateConfig.partialUpdateMode) { + setFuncName = decorator === COMPONENT_STORAGE_PROP_DECORATOR ? + APP_STORAGE_SET_AND_PROP : APP_STORAGE_SET_AND_LINK; + storageFuncName = APP_STORAGE; + } else { + setFuncName = decorator === COMPONENT_STORAGE_PROP_DECORATOR ? + CREATE_STORAGE_PROP : CREATE_STORAGE_LINK; + storageFuncName = THIS; + storageValue.splice(2, 1); + } + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(`__${name.getText()}`), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(storageFuncName), + ts.factory.createIdentifier(setFuncName)), undefined, storageValue))); + } + return undefined; +} + +function getDecoratorKey(node: ts.PropertyDeclaration, isProvided: boolean = false): [string, boolean, ts.Node, boolean] { + let key: string; + let isStringKey: boolean = false; + let isProvidedParamObj: boolean = false; + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + const index: number = findDecoratorIndex(decorators, [COMPONENT_PROVIDE_DECORATOR, COMPONENT_CONSUME_DECORATOR]); + // @ts-ignore + let keyNameNode: ts.Node = decorators[index].expression.arguments[0]; + if (ts.isIdentifier(keyNameNode)) { + key = keyNameNode.getText(); + decoratorParamSet.add(key); + } else if (ts.isStringLiteral(keyNameNode)) { + key = keyNameNode.text; + isStringKey = true; + } else if (isProvided && ts.isObjectLiteralExpression(keyNameNode) && keyNameNode.properties.length === 1 && + ts.isPropertyAssignment(keyNameNode.properties[0]) && keyNameNode.properties[0].initializer) { + key = keyNameNode.properties[0].initializer.text; + keyNameNode = keyNameNode.properties[0].initializer; + isProvidedParamObj = true; + } else { + key = keyNameNode.getText(); + } + return [key, isStringKey, keyNameNode, isProvidedParamObj]; +} + +function updateSynchedPropertyNesedObject(nameIdentifier: ts.Identifier, + type: ts.TypeNode, decoractor: string, log: LogInfo[]): ts.ExpressionStatement { + if (isObservedClassType(type)) { + return createInitExpressionStatementForDecorator(nameIdentifier.getText(), SYNCHED_PROPERTY_NESED_OBJECT, + createPropertyAccessExpressionWithParams(nameIdentifier.getText())); + } else { + validateNonObservedClassType(nameIdentifier, decoractor, log); + } + return undefined; +} + +function updateConsumeProperty(node: ts.PropertyDeclaration, + nameIdentifier: ts.Identifier): ts.ExpressionStatement { + const name: string = nameIdentifier.getText(); + let propertyOrAliasName: string; + const propertyAndStringKey: [string?, boolean?, ts.Node?, boolean?] = []; + if (isSingleKey(node, true)) { + propertyAndStringKey.push(...getDecoratorKey(node)); + propertyOrAliasName = propertyAndStringKey[0]; + } else { + propertyOrAliasName = name; + } + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(`__${name}`), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createCallExpression( + createPropertyAccessExpressionWithThis(INITIALIZE_CONSUME_FUNCTION), undefined, [ + propertyAndStringKey.length === 0 ? ts.factory.createStringLiteral(propertyOrAliasName) : + propertyAndStringKey.length === 4 && propertyAndStringKey[2] as ts.Expression, ts.factory.createStringLiteral(name)]))); +} + +function updateBuilderParamProperty(node: ts.PropertyDeclaration, + nameIdentifier: ts.Identifier, log: LogInfo[]): ts.ExpressionStatement { + const name: string = nameIdentifier.getText(); + if (judgeBuilderParamAssignedByBuilder(node)) { + log.push({ + type: LogType.ERROR, + message: 'BuilderParam property can only initialized by Builder function or LocalBuilder method in struct.', + pos: node.getStart(), + code: '10905101' + }); + } + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(name), ts.factory.createToken(ts.SyntaxKind.EqualsToken), + node.initializer || ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) + )); +} + +export function judgeBuilderParamAssignedByBuilder(node: ts.PropertyDeclaration): boolean { + return node.initializer && !(node.initializer && (ts.isIdentifier(node.initializer) && + CUSTOM_BUILDER_METHOD.has(node.initializer.escapedText.toString()) || + ts.isPropertyAccessExpression(node.initializer) && node.initializer.name && + ts.isIdentifier(node.initializer.name) && + (CUSTOM_BUILDER_METHOD.has(node.initializer.name.escapedText.toString()) || + INNER_CUSTOM_LOCALBUILDER_METHOD.has(node.initializer.name.escapedText.toString())) || + isWrappedBuilder(node.initializer as ts.PropertyAccessExpression))); +} + +export function createViewCreate(node: ts.NewExpression | ts.Identifier): ts.CallExpression { + if (partialUpdateConfig.partialUpdateMode) { + return createFunction(ts.factory.createIdentifier(BASE_COMPONENT_NAME_PU), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), ts.factory.createNodeArray([node])); + } + return createFunction(ts.factory.createIdentifier(BASE_COMPONENT_NAME), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), ts.factory.createNodeArray([node])); +} + +export function createCustomComponentNewExpression(node: ts.CallExpression, name: string, + isBuilder: boolean = false, isGlobalBuilder: boolean = false, + isCutomDialog: boolean = false): ts.NewExpression { + const newNode: ts.NewExpression = ts.factory.createNewExpression(node.expression, + node.typeArguments, node.arguments.length ? node.arguments : []); + return addCustomComponentId(newNode, node, name, isBuilder, isGlobalBuilder, isCutomDialog); +} + +function addCustomComponentId(node: ts.NewExpression, oldNode: ts.CallExpression, componentName: string, + isBuilder: boolean = false, isGlobalBuilder: boolean = false, + isCutomDialog: boolean = false): ts.NewExpression { + const posOfNode = transformLog.sourceFile.getLineAndCharacterOfPosition(getRealNodePos(node)); + const line: number = posOfNode.line + 1; + const col: number = posOfNode.character + 1; + for (const item of componentCollection.customComponents) { + componentInfo.componentNames.add(item); + } + componentInfo.componentNames.forEach((name: string) => { + let argumentsArray: ts.Expression[]; + if (node.arguments && node.arguments.length) { + argumentsArray = Array.from(node.arguments); + if (partialUpdateConfig.partialUpdateMode && node.arguments.length === 1) { + manageLocalStorageComponents(oldNode, argumentsArray); + } + } + if (componentName === name) { + if (!argumentsArray) { + argumentsArray = [ts.factory.createObjectLiteralExpression([], true)]; + if (partialUpdateConfig.partialUpdateMode) { + argumentsArray.push(ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED)); + } + } + if (!partialUpdateConfig.partialUpdateMode) { + ++componentInfo.id; + argumentsArray.unshift(isBuilder ? ts.factory.createBinaryExpression( + ts.factory.createStringLiteral(path.basename(transformLog.sourceFile.fileName, EXTNAME_ETS) + '_'), + ts.factory.createToken(ts.SyntaxKind.PlusToken), ts.factory.createIdentifier(_GENERATE_ID)) : + ts.factory.createStringLiteral(componentInfo.id.toString()), + isBuilder ? parentConditionalExpression() : ts.factory.createThis()); + } else { + argumentsArray.unshift((isGlobalBuilder || storedFileInfo.processLocalBuilder) ? parentConditionalExpression() : ts.factory.createThis()); + argumentsArray.push(isCutomDialog ? ts.factory.createPrefixUnaryExpression( + ts.SyntaxKind.MinusToken, + ts.factory.createNumericLiteral('1')) : ts.factory.createIdentifier(ELMTID), + createArrowFunctionNode(), componentParamRowAndColumn(line, col)); + } + node = + ts.factory.updateNewExpression(node, node.expression, node.typeArguments, argumentsArray); + } else if (argumentsArray) { + node = + ts.factory.updateNewExpression(node, node.expression, node.typeArguments, argumentsArray); + } + }); + return node; +} + +function manageLocalStorageComponents(node: ts.CallExpression, argumentsArray: ts.Expression[]): void { + if (isLocalStorageParameter(node)) { + argumentsArray.unshift(ts.factory.createObjectLiteralExpression([], false)); + } else { + argumentsArray.push(ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED)); + } +} + +export function isLocalStorageParameter(node: ts.CallExpression): boolean { + return globalProgram.checker && globalProgram.checker.getResolvedSignature && + globalProgram.checker.getResolvedSignature(node) && + globalProgram.checker.getResolvedSignature(node).parameters && + globalProgram.checker.getResolvedSignature(node).parameters.length === 1 && + globalProgram.checker.getResolvedSignature(node).parameters[0].escapedName === '##storage'; +} + +function componentParamRowAndColumn(line: number, col: number): ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression( + [ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier('page'), + ts.factory.createStringLiteral(path.relative(process.cwd(), resourceFileName).replace(/\\/g, '/')) + ), + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier('line'), + ts.factory.createNumericLiteral(line) + ), + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier('col'), + ts.factory.createNumericLiteral(col) + ) + ], + false + ); +} + +function createArrowFunctionNode(): ts.ArrowFunction { + return ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [], + false + ) + ); +} + +function createInitExpressionStatementForDecorator(propertyName: string, functionName: string, + parameterNode: ts.Expression): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(`__${propertyName}`), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createNewExpression( + ts.factory.createIdentifier(functionName), undefined, [parameterNode, ts.factory.createThis(), + ts.factory.createStringLiteral(propertyName)]))); +} + +function createPropertyAccessExpressionWithParams(propertyName: string): ts.PropertyAccessExpression { + return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(CREATE_CONSTRUCTOR_PARAMS), + ts.factory.createIdentifier(propertyName)); +} + +function createPropertyAccessExpressionWithThis(propertyName: string): ts.PropertyAccessExpression { + return ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + ts.factory.createIdentifier(propertyName)); +} + +function addAddProvidedVar(node: ts.PropertyDeclaration, name: ts.Identifier, + decoratorName: string, updateState: ts.Statement[]): void { + if (decoratorName === COMPONENT_PROVIDE_DECORATOR) { + let parameterName: string; + const parameterNameAndStringKey: [string?, boolean?, ts.Node?, boolean?] = []; + if (isSingleKey(node, true)) { + parameterNameAndStringKey.push(...getDecoratorKey(node, true)); + parameterName = parameterNameAndStringKey[0]; + updateState.push(createAddProvidedVar(parameterName, name, parameterNameAndStringKey[1], parameterNameAndStringKey[2], + parameterNameAndStringKey[3])); + } + if (parameterName !== name.getText()) { + updateState.push(createAddProvidedVar(name.getText(), name, true, undefined, parameterNameAndStringKey[3])); + } + } +} + +function createAddProvidedVar(propertyOrAliasName: string, + name: ts.Identifier, isString: boolean, decoratorKeyNode: ts.Node, + isProvidedParamObj: boolean): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + createPropertyAccessExpressionWithThis(ADD_PROVIDED_VAR), undefined, [ + isString ? ts.factory.createStringLiteral(propertyOrAliasName) : decoratorKeyNode as ts.Expression, + createPropertyAccessExpressionWithThis(`__${name.getText()}`), + isProvidedParamObj ? ts.factory.createIdentifier(TRUE) : ts.factory.createIdentifier(FALSE)])); +} + +function createGetAccessor(item: ts.Identifier, express: string): ts.GetAccessorDeclaration { + const getAccessorStatement: ts.GetAccessorDeclaration = + ts.factory.createGetAccessorDeclaration(undefined, item, [], undefined, + ts.factory.createBlock([ts.factory.createReturnStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + createPropertyAccessExpressionWithThis(`__${item.getText()}`), + ts.factory.createIdentifier(express)), undefined, []))], true)); + return getAccessorStatement; +} + +function createSetAccessor(item: ts.Identifier, express: string, type: ts.TypeNode): + ts.SetAccessorDeclaration { + const setAccessorStatement: ts.SetAccessorDeclaration = + ts.factory.createSetAccessorDeclaration(undefined, item, + [ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(CREATE_NEWVALUE_IDENTIFIER), undefined, type, + undefined)], ts.factory.createBlock([ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + createPropertyAccessExpressionWithThis(`__${item.getText()}`), + ts.factory.createIdentifier(express)), undefined, + [ts.factory.createIdentifier(CREATE_NEWVALUE_IDENTIFIER)]))], true)); + return setAccessorStatement; +} + +function isForbiddenUseStateType(typeNode: ts.TypeNode): boolean { + if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName) && + forbiddenUseStateType.has(typeNode.typeName.getText())) { + return true; + } + return false; +} + +export function isSimpleType(typeNode: ts.TypeNode, program: ts.Program, log?: LogInfo[]): boolean { + typeNode = typeNode || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword); + let checker: ts.TypeChecker; + if (globalProgram.program) { + checker = globalProgram.program.getTypeChecker(); + } else if (globalProgram.watchProgram) { + checker = globalProgram.watchProgram.getCurrentProgram().getProgram().getTypeChecker(); + } else if (program) { + checker = program.getTypeChecker(); + } + return getDeclarationType(typeNode, checker, log); +} + +function getDeclarationType(typeNode: ts.TypeNode, checker: ts.TypeChecker, log: LogInfo[]): boolean { + if (simpleTypes.has(typeNode.kind)) { + return true; + } + if (ts.isTypeReferenceNode(typeNode) && typeNode.typeName && ts.isIdentifier(typeNode.typeName) && + enumCollection.has(typeNode.typeName.escapedText.toString())) { + return true; + } + if (checker) { + const type: ts.Type = checker.getTypeFromTypeNode(typeNode); + /* Enum */ + if (type.flags & (32 | 1024)) { + return true; + } + // @ts-ignore + if (type.types && type.types.length) { + // @ts-ignore + const types = type.types; + let referenceType: boolean = false; + for (let i = 0; i < types.length; i++) { + if (!isBasicType(types[i].flags)) { + referenceType = true; + } + } + if (!referenceType) { + return true; + } + } + } + return false; +} + +export function isBasicType(flags: number): boolean { + if (flags & (4 | /* String */ 8 | /* Number */ 16 | /* Boolean */ 32 | /* Enum */ 64 | /* BigInt */ + 128 | /* StringLiteral */ 256 | /* NumberLiteral */ 512 /* BooleanLiteral */| 1024 /* EnumLiteral */| + 2048 /* BigIntLiteral */)) { + return true; + } + return false; +} + +function isObservedClassType(type: ts.TypeNode): boolean { + if (judgmentTypedeclaration(type) && observedClassCollection.has(type.typeName.escapedText.toString())) { + return true; + } else if (ts.isUnionTypeNode(type) && type.types) { + const types: ts.NodeArray = type.types; + for (let i = 0; i < types.length; i++) { + if (judgmentTypedeclaration(types[i]) && !observedClassCollection.has(types[i].typeName.escapedText.toString())) { + return false; + } + } + return true; + } + return false; +} + +function judgmentTypedeclaration(type: ts.TypeNode): boolean { + return ts.isTypeReferenceNode(type) && type.typeName && ts.isIdentifier(type.typeName); +} + +export function isSingleKey(node: ts.PropertyDeclaration, isOptionalKey: boolean = false): boolean { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + const optionalKeyDecorator: Set = new Set(['Provide', 'Consume']); + return decorators.some((item: ts.Decorator) => { + return ts.isCallExpression(item.expression) && + item.expression.arguments && + item.expression.arguments.length === 1 && + ts.isIdentifier(item.expression.expression) && + (!isOptionalKey || optionalKeyDecorator.has(item.expression.expression.escapedText.toString())); + }); +} + +function validateMultiDecorators(name: ts.Identifier, log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The property '${name.escapedText.toString()}' cannot have mutilate state management decorators.`, + pos: name.getStart(), + code: '10905302' + }); +} + +function validatePropertyNonDefaultValue(propertyName: ts.Identifier, decorator: string, + log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The ${decorator} property '${propertyName.getText()}' must be specified a default value.`, + pos: propertyName.getStart(), + code: '10905303' + }); +} + +function validatePropertyDefaultValue(propertyName: ts.Identifier, decorator: string, + log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The ${decorator} property '${propertyName.getText()}' cannot be specified a default value.`, + pos: propertyName.getStart(), + code: '10905304', + solutions: ['Please initialize the rules according to the decorator.'] + }); +} + +function validatePropertyNonType(propertyName: ts.Identifier, log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The property '${propertyName.getText()}' must specify a type.`, + pos: propertyName.getStart(), + code: '10905305' + }); +} + +function validateNonSimpleType(propertyName: ts.Identifier, decorator: string, + log: LogInfo[]): void { + log.push({ + type: projectConfig.optLazyForEach ? LogType.WARN : LogType.ERROR, + message: `The type of the ${decorator} property '${propertyName.getText()}' ` + + `can only be string, number or boolean.`, + pos: propertyName.getStart(), + code: '10905306' + }); +} + +function validateNonObservedClassType(propertyName: ts.Identifier, decorator: string, + log: LogInfo[], isEsmoduleAndUpdateMode: boolean = false): void { + if (isEsmoduleAndUpdateMode) { + log.push({ + type: projectConfig.optLazyForEach ? LogType.WARN : LogType.ERROR, + message: `The type of the ${decorator} property '${propertyName.getText()}' cannot be an ` + + `objects of classes decorated with ${COMPONENT_OBSERVEDV2_DECORATOR} class decorator in ets (not ts).`, + pos: propertyName.getStart(), + code: '10905307' + }); + } else { + log.push({ + type: projectConfig.optLazyForEach ? LogType.WARN : LogType.ERROR, + message: `The type of the ${decorator} property '${propertyName.getText()}' can only be ` + + `objects of classes decorated with ${COMPONENT_OBSERVED_DECORATOR} class decorator in ets (not ts).`, + pos: propertyName.getStart(), + code: '10905307' + }); + } +} + +function validateHasIllegalQuestionToken(propertyName: ts.Identifier, decorator: string, + log: LogInfo[]): void { + log.push({ + type: LogType.WARN, + message: `The ${decorator} property '${propertyName.getText()}' cannot be an optional parameter.`, + pos: propertyName.getStart() + }); +} + +function validateHasIllegalDecoratorInEntry(parentName: ts.Identifier, propertyName: ts.Identifier, + decorator: string, log: LogInfo[]): void { + log.push({ + type: LogType.WARN, + message: `The @Entry component '${parentName.getText()}' cannot have the ` + + `${decorator} property '${propertyName.getText()}'.`, + pos: propertyName.getStart() + }); +} + +function validateForbiddenUseStateType(propertyName: ts.Identifier, decorator: string, type: string, + log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The ${decorator} property '${propertyName.getText()}' cannot be a '${type}' object.`, + pos: propertyName.getStart(), + code: '10905308' + }); +} + +function validateDuplicateDecorator(decorator: ts.Decorator, log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The decorator '${decorator.getText()}' cannot have the same name as the built-in ` + + `style attribute '${decorator.getText().replace('@', '')}'.`, + pos: decorator.getStart(), + code: '10905309' + }); +} + +function validateWatchDecorator(propertyName: ts.Identifier, decorators: readonly ts.Decorator[], + log: LogInfo[]): boolean { + let isRegular: boolean = false; + if (decorators.length === DECORATOR_LENGTH) { + isRegular = decorators.some((item: ts.Decorator) => { + return item.getText() === COMPONENT_REQUIRE_DECORATOR; + }); + } + if (decorators.length === 1 || isRegular) { + log.push({ + type: LogType.ERROR, + message: `Regular variable '${propertyName.escapedText.toString()}' can not be decorated with @Watch.`, + pos: propertyName.getStart(), + code: '10905310' + }); + return false; + } + return true; +} + +function validateWatchParam(type: LogType, pos: number, log: LogInfo[]): void { + log.push({ + type: type, + message: 'The parameter should be a string.', + pos, + code: '10905311' + }); +} + +function updateObservedPropertyPU(item: ts.PropertyDeclaration, name: ts.Identifier, + type: ts.TypeNode, program: ts.Program): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(`__${name.getText()}`), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createNewExpression( + ts.factory.createIdentifier(isSimpleType(type, program) ? OBSERVED_PROPERTY_SIMPLE_PU : + OBSERVED_PROPERTY_OBJECT_PU), undefined, [ + item.initializer ?? ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED), + ts.factory.createThis(), + ts.factory.createStringLiteral(name.escapedText.toString()) + ] + ) + ) + ); +} + +function updateSynchedPropertyTwoWayPU(nameIdentifier: ts.Identifier, type: ts.TypeNode, + program: ts.Program): ts.ExpressionStatement { + const name: string = nameIdentifier.escapedText.toString(); + const functionName: string = isSimpleType(type, program) ? + SYNCHED_PROPERTY_SIMPLE_TWO_WAY_PU : SYNCHED_PROPERTY_OBJECT_TWO_WAY_PU; + return createInitExpressionStatementForDecorator(name, functionName, + createPropertyAccessExpressionWithParams(name)); +} + +function updateSynchedPropertyOneWayPU(nameIdentifier: ts.Identifier, type: ts.TypeNode, + decoractor: string, log: LogInfo[], program: ts.Program): ts.ExpressionStatement { + const name: string = nameIdentifier.escapedText.toString(); + if (isSimpleType(type, program, log)) { + return createInitExpressionStatementForDecorator(name, SYNCHED_PROPERTY_SIMPLE_ONE_WAY_PU, + createPropertyAccessExpressionWithParams(name)); + } else { + return createInitExpressionStatementForDecorator(name, SYNCHED_PROPERTY_OBJECT_ONE_WAY_PU, + createPropertyAccessExpressionWithParams(name)); + } +} + +function updateSynchedPropertyNesedObjectPU(nameIdentifier: ts.Identifier, + type: ts.TypeNode, decoractor: string, log: LogInfo[]): ts.ExpressionStatement { + const isEsmoduleAndUpdateMode: boolean = partialUpdateConfig.partialUpdateMode && projectConfig.compileMode === 'esmodule'; + if (isEsmoduleAndUpdateMode && checkObjectLinkType(type)) { + return createInitExpressionStatementForDecorator(nameIdentifier.getText(), SYNCHED_PROPERTY_NESED_OBJECT_PU, + createPropertyAccessExpressionWithParams(nameIdentifier.getText())); + } else if ((projectConfig.compileMode !== 'esmodule' || !partialUpdateConfig.partialUpdateMode) && isObservedClassType(type)) { + return createInitExpressionStatementForDecorator(nameIdentifier.getText(), SYNCHED_PROPERTY_NESED_OBJECT_PU, + createPropertyAccessExpressionWithParams(nameIdentifier.getText())); + } else { + validateNonObservedClassType(nameIdentifier, decoractor, log, isEsmoduleAndUpdateMode); + return undefined; + } +} + +// check @ObjectLink type Non basic types and @Observedv2. +function checkObjectLinkType(typeNode: ts.TypeNode): boolean { + if (globalProgram.checker) { + const type: ts.Type = globalProgram.checker.getTypeFromTypeNode(typeNode); + const isPropertyDeclaration: boolean = typeNode.parent && ts.isPropertyDeclaration(typeNode.parent); + if (isPropertyDeclaration) { + if (type.types && type.types.length) { + return checkTypes(type); + } else { + return !(isObservedV2(type) || isAllowedTypeForBasic(type.flags) || isFunctionType(type)); + } + } + } + return false; +} + +// check union type. +function checkTypes(type: ts.Type): boolean { + return !type.types.some((item: ts.Type) => { + return (isAllowedTypeForBasic(item.flags) || isObservedV2(item) || isFunctionType(item)); + }); +} + +function validateCustomDecorator(decorators: readonly ts.Decorator[], log: LogInfo[]): boolean { + let hasInnerDecorator: boolean = false; + let hasCustomDecorator: boolean = false; + let innerDecorator: ts.Decorator; + for (let i = 0; i < decorators.length; i++) { + const decorator: ts.Decorator = decorators[i]; + const decoratorName: string = decorator.getText().replace(/\(.*\)$/, '').trim(); + if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { + hasInnerDecorator = true; + innerDecorator = innerDecorator || decorator; + } else { + hasCustomDecorator = true; + } + } + if (hasCustomDecorator && hasInnerDecorator) { + log.push({ + type: LogType.ERROR, + message: `The inner decorator ${innerDecorator.getText()} cannot be used together with custom decorator.`, + pos: innerDecorator.getStart(), + code: '10905312' + }); + } else if (!hasInnerDecorator) { + return true; + } + return false; +} + +function validatePropDecorator(decorators: readonly ts.Decorator[]): boolean { + for (let i = 0; i < decorators.length; i++) { + const decorator: ts.Decorator = decorators[i]; + const decoratorName: string = decorator.getText().replace(/\(.*\)$/, '').trim(); + if (COMPONENT_PROP_DECORATOR === decoratorName) { + return true; + } + } + return false; +} + +export function isObservedV2(type: ts.Type): boolean { + if (type && type.getSymbol() && type.getSymbol().declarations) { + return type.getSymbol().declarations.some((classDeclaration: ts.ClassDeclaration) => { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(classDeclaration); + if (ts.isClassDeclaration(classDeclaration) && decorators) { + return decorators.some((decorator: ts.Decorator) => { + return ts.isIdentifier(decorator.expression) && decorator.expression.escapedText.toString() === MIN_OBSERVED; + }); + } + return false; + }); + } + return false; +} + +export function resetProcessComponentMember(): void { + decoratorParamSet.clear(); +} diff --git a/compiler/src/interop/src/process_custom_component.ts b/compiler/src/interop/src/process_custom_component.ts new file mode 100644 index 0000000000000000000000000000000000000000..87abfe98373d2be902e64ae7a1a7ed8d1038e16f --- /dev/null +++ b/compiler/src/interop/src/process_custom_component.ts @@ -0,0 +1,1516 @@ +/* + * Copyright (c) 2021 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 ts from 'typescript'; + +import { + COMPONENT_NON_DECORATOR, + COMPONENT_STATE_DECORATOR, + COMPONENT_PROP_DECORATOR, + COMPONENT_LINK_DECORATOR, + COMPONENT_STORAGE_LINK_DECORATOR, + COMPONENT_PROVIDE_DECORATOR, + COMPONENT_OBJECT_LINK_DECORATOR, + COMPONENT_CREATE_FUNCTION, + COMPONENT_POP_FUNCTION, + BASE_COMPONENT_NAME, + CUSTOM_COMPONENT_EARLIER_CREATE_CHILD, + COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, + CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID, + COMPONENT_CONSTRUCTOR_UNDEFINED, + CUSTOM_COMPONENT_NEEDS_UPDATE_FUNCTION, + CUSTOM_COMPONENT_MARK_STATIC_FUNCTION, + COMPONENT_COMMON, + GENERATE_ID, + ELMTID, + STARTGETACCESSRECORDINGFOR, + STOPGETACCESSRECORDING, + BASE_COMPONENT_NAME_PU, + OBSERVECOMPONENTCREATION, + OBSERVECOMPONENTCREATION2, + ISINITIALRENDER, + UPDATE_STATE_VARS_OF_CHIND_BY_ELMTID, + $$, + COMPONENT_RECYCLE, + COMPONENT_CREATE_RECYCLE, + RECYCLE_NODE, + ABOUT_TO_REUSE, + COMPONENT_RERENDER_FUNCTION, + OBSERVE_RECYCLE_COMPONENT_CREATION, + FUNCTION, + COMPONENT_IF_UNDEFINED, + COMPONENT_PARAMS_LAMBDA_FUNCTION, + COMPONENT_PARAMS_FUNCTION, + COMPONENT_ABOUTTOREUSEINTERNAL_FUNCTION, + NAME, + COMPONENT_CALL +} from './pre_define'; +import { + stateCollection, + linkCollection, + propCollection, + regularCollection, + storagePropCollection, + storageLinkCollection, + provideCollection, + consumeCollection, + objectLinkCollection, + isStaticViewCollection, + builderParamObjectCollection, + getLocalStorageCollection, + builderParamInitialization, + propInitialization, + regularInitialization, + stateInitialization, + provideInitialization, + privateCollection, + regularStaticCollection, + componentCollection +} from './validate_ui_syntax'; +import { + PropMapManager, + createViewCreate, + createCustomComponentNewExpression, + isLocalStorageParameter, + isBasicType, + isObservedV2 +} from './process_component_member'; +import { + LogType, + LogInfo, + componentInfo, + storedFileInfo +} from './utils'; +import { + bindComponentAttr, + parentConditionalExpression, + createComponentCreationStatement, + createFunction, + ComponentAttrInfo, + ifRetakeId, + transferBuilderCall, + createCollectElmtIdNode, + createViewStackProcessorStatement, + BuilderParamsResult +} from './process_component_build'; +import { + partialUpdateConfig, + projectConfig, + globalProgram +} from '../main'; +import { + GLOBAL_CUSTOM_BUILDER_METHOD +} from './component_map'; +import { + createReference, + isProperty +} from './process_component_class'; +import processStructComponentV2, { StructInfo, ParamDecoratorInfo } from './process_struct_componentV2'; +import constantDefine from './constant_define'; +import createAstNodeUtils from './create_ast_node_utils'; +import logMessageCollection from './log_message_collection'; + +let decoractorMap: Map>>; + +export function processCustomComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[], + log: LogInfo[], name: string, isBuilder: boolean = false, isGlobalBuilder: boolean = false, + idName: ts.Expression = undefined, builderParamsResult: BuilderParamsResult = null, + isInRepeatTemplate: boolean = false): void { + decoractorMap = new Map( + [[COMPONENT_STATE_DECORATOR, stateCollection], + [COMPONENT_LINK_DECORATOR, linkCollection], + [COMPONENT_PROP_DECORATOR, propCollection], + [COMPONENT_NON_DECORATOR, regularCollection], + [COMPONENT_PROVIDE_DECORATOR, provideCollection], + [COMPONENT_OBJECT_LINK_DECORATOR, objectLinkCollection]]); + const componentNode: ts.CallExpression = getCustomComponentNode(node); + if (componentNode) { + const parentComponentType: ParentType = componentCollection.currentClassName ? + getParentComponentType(componentCollection.currentClassName) : getParentComponentType(''); + const isRecycleComponent: boolean = isRecycle(name); + const isReuseComponentInV2: boolean = isReuseInV2(name); + logMessageCollection.checkNestedComponents(parentComponentType, isRecycleComponent, isReuseComponentInV2, node, log); + logMessageCollection.checkIfReuseV2InRepeatTemplate(isInRepeatTemplate, isReuseComponentInV2, node, log); + const hasChainCall: boolean = componentNode.parent && + ts.isPropertyAccessExpression(componentNode.parent); + let ischangeNode: boolean = false; + let customComponentNewExpression: ts.NewExpression = createCustomComponentNewExpression( + componentNode, name, isBuilder, isGlobalBuilder); + let argumentsArray: ts.PropertyAssignment[]; + const componentAttrInfo: ComponentAttrInfo = { reuseId: null, hasIdAttr: false, attrCount: 0, reuse: '' }; + if (isHasChild(componentNode)) { + // @ts-ignore + argumentsArray = componentNode.arguments[0].properties.slice(); + argumentsArray.forEach((item: ts.PropertyAssignment, index: number) => { + if (isToChange(item, name)) { + ischangeNode = true; + const propertyAssignmentNode: ts.PropertyAssignment = ts.factory.updatePropertyAssignment( + item, item.name, changeNodeFromCallToArrow(item.initializer as ts.CallExpression)); + argumentsArray.splice(index, 1, propertyAssignmentNode); + } + }); + if (ischangeNode) { + const newNode: ts.ExpressionStatement = ts.factory.updateExpressionStatement(node, + ts.factory.createNewExpression(componentNode.expression, componentNode.typeArguments, + [ts.factory.createObjectLiteralExpression(argumentsArray, true)])); + customComponentNewExpression = createCustomComponentNewExpression( + newNode.expression as ts.CallExpression, name, isBuilder); + } + } + let judgeIdStart: number; + if (partialUpdateConfig.partialUpdateMode && idName) { + judgeIdStart = newStatements.length; + } + let needCommon: boolean = false; + if (hasChainCall) { + if (partialUpdateConfig.partialUpdateMode) { + const commomComponentNode: ts.Statement[] = [ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_COMMON), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null))]; + const immutableStatements: ts.Statement[] = []; + bindComponentAttr(node, ts.factory.createIdentifier(COMPONENT_COMMON), commomComponentNode, + log, true, false, immutableStatements, false, componentAttrInfo, isReuseComponentInV2); + needCommon = commomComponentNode.length > 1 || immutableStatements.length > 0; + if (componentAttrInfo.hasIdAttr && componentAttrInfo.attrCount === 1) { + commomComponentNode[0] = createCommonIdAttrNode(); + } + if (needCommon) { + newStatements.push(createComponentCreationStatement(componentAttributes(COMPONENT_COMMON), + commomComponentNode, COMPONENT_COMMON, isGlobalBuilder, false, undefined, immutableStatements, + builderParamsResult, isRecycleComponent)); + } + } else { + newStatements.push(ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_COMMON), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null))); + bindComponentAttr(node, ts.factory.createIdentifier(COMPONENT_COMMON), newStatements, log); + } + } + if (isRecycleComponent && partialUpdateConfig.partialUpdateMode) { + newStatements.push(createRecycleComponent(isGlobalBuilder)); + } + addCustomComponent(node, newStatements, customComponentNewExpression, log, name, componentNode, + isBuilder, isGlobalBuilder, isRecycleComponent, componentAttrInfo, builderParamsResult, isReuseComponentInV2); + if (hasChainCall && (!partialUpdateConfig.partialUpdateMode || needCommon)) { + newStatements.push(ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_COMMON), + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); + } + if (isRecycleComponent && partialUpdateConfig.partialUpdateMode) { + newStatements.push(componentAttributes(COMPONENT_RECYCLE)); + } + if (partialUpdateConfig.partialUpdateMode && idName) { + newStatements.splice(judgeIdStart, newStatements.length - judgeIdStart, + ifRetakeId(newStatements.slice(judgeIdStart), idName)); + } + } +} + +function createCommonIdAttrNode(): ts.ExpressionStatement { + return ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_COMMON), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), + // @ts-ignore + [ts.factory.createTrue()])); +} + +export function isRecycle(componentName: string): boolean { + return storedFileInfo.getCurrentArkTsFile().recycleComponents.has(componentName); +} + +export function isReuseInV2(componentName: string): boolean { + return storedFileInfo.getCurrentArkTsFile().reuseComponentsV2.has(componentName); +} + +function createRecycleComponent(isGlobalBuilder: boolean): ts.Statement { + return createComponentCreationStatement(componentAttributes(COMPONENT_RECYCLE), + [ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_RECYCLE), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null)) + ], COMPONENT_RECYCLE, isGlobalBuilder); +} + +function componentAttributes(componentName: string): ts.Statement { + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(componentName), + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION) + ), undefined, [] + )); +} + +function isHasChild(node: ts.CallExpression): boolean { + return node.arguments && node.arguments[0] && ts.isObjectLiteralExpression(node.arguments[0]) && + node.arguments[0].properties && node.arguments[0].properties.length > 0; +} + +function isToChange(item: ts.PropertyAssignment, name: string): boolean { + const builderParamName: Set = builderParamObjectCollection.get(name); + if (item.initializer && ts.isCallExpression(item.initializer) && builderParamName && + builderParamName.has(item.name.getText()) && + !/\.(bind|call|apply)/.test(item.initializer.getText())) { + return true; + } + return false; +} + +function changeNodeFromCallToArrow(node: ts.CallExpression): ts.ConditionalExpression { + let builderBindThis: ts.ExpressionStatement = ts.factory.createExpressionStatement(node); + if (ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && + GLOBAL_CUSTOM_BUILDER_METHOD.has(node.expression.escapedText.toString())) { + builderBindThis = transferBuilderCall(ts.factory.createExpressionStatement(node), node.expression.escapedText.toString()); + } + return changeNodeFromCallToArrowDetermine(node, builderBindThis); +} + +function changeNodeFromCallToArrowDetermine(node: ts.CallExpression, builderBindThis: ts.ExpressionStatement): ts.ConditionalExpression { + if (ts.isCallExpression(node)) { + return ts.factory.createConditionalExpression( + ts.factory.createBinaryExpression( + ts.factory.createTypeOfExpression(node), + ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + ts.factory.createStringLiteral(FUNCTION) + ), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + node, + ts.factory.createToken(ts.SyntaxKind.ColonToken), + ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [builderBindThis], + true + ) + ) + ); + } + return undefined; +} + +function addCustomComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[], + newNode: ts.NewExpression, log: LogInfo[], name: string, componentNode: ts.CallExpression, + isBuilder: boolean, isGlobalBuilder: boolean, isRecycleComponent: boolean, + componentAttrInfo: ComponentAttrInfo, builderParamsResult: BuilderParamsResult, + isReuseComponentInV2: boolean): void { + if (ts.isNewExpression(newNode)) { + const propertyArray: ts.ObjectLiteralElementLike[] = []; + validateCustomComponentPrams(componentNode, name, propertyArray, log, isBuilder); + addCustomComponentStatements(node, newStatements, newNode, name, propertyArray, componentNode, + isBuilder, isGlobalBuilder, isRecycleComponent, componentAttrInfo, builderParamsResult, log, isReuseComponentInV2); + } +} + +function addCustomComponentStatements(node: ts.ExpressionStatement, newStatements: ts.Statement[], + newNode: ts.NewExpression, name: string, props: ts.ObjectLiteralElementLike[], + componentNode: ts.CallExpression, isBuilder: boolean, isGlobalBuilder: boolean, + isRecycleComponent: boolean, componentAttrInfo: ComponentAttrInfo, + builderParamsResult: BuilderParamsResult, log: LogInfo[], isReuseComponentInV2: boolean): void { + if (!partialUpdateConfig.partialUpdateMode) { + const id: string = componentInfo.id.toString(); + newStatements.push(createFindChildById(id, name, isBuilder), createCustomComponentIfStatement(id, + ts.factory.updateExpressionStatement(node, createViewCreate(newNode)), + ts.factory.createObjectLiteralExpression(props, true), name)); + } else { + newStatements.push(createCustomComponent(newNode, name, componentNode, isGlobalBuilder, isBuilder, + isRecycleComponent, componentAttrInfo, builderParamsResult, log, isReuseComponentInV2)); + } +} + +function createChildElmtId(node: ts.CallExpression, name: string, log: LogInfo[]): ts.PropertyAssignment[] { + const childParam: ts.PropertyAssignment[] = []; + const propsAndObjectLinks: string[] = []; + if (propCollection.get(name)) { + propsAndObjectLinks.push(...propCollection.get(name)); + } + if (objectLinkCollection.get(name)) { + propsAndObjectLinks.push(...objectLinkCollection.get(name)); + } + if (projectConfig.optLazyForEach && storedFileInfo.processLazyForEach && stateCollection.get(name)) { + propsAndObjectLinks.push(...stateCollection.get(name)); + } + parseChildProperties(name, node, childParam, propsAndObjectLinks, log); + return childParam; +} + +class ChildAndParentComponentInfo { + childStructInfo: StructInfo; + parentStructInfo: StructInfo; + paramDecoratorMap: Map; + updatePropsDecoratorsV2: string[]; + propsAndObjectLinks: string[]; + childName: string; + forbiddenInitPropsV2: string[]; + updatePropsForV1Parent: string[]; + updatePropsForV2Parent: string[]; + constructor(childName: string, childNode: ts.CallExpression, propsAndObjectLinks: string[]) { + this.childName = childName; + this.propsAndObjectLinks = propsAndObjectLinks; + this.childStructInfo = processStructComponentV2.getAliasStructInfo(childNode) || + processStructComponentV2.getOrCreateStructInfo(childName); + this.paramDecoratorMap = this.childStructInfo.paramDecoratorMap; + this.updatePropsDecoratorsV2 = [...this.childStructInfo.eventDecoratorMap.keys(), ...this.paramDecoratorMap.keys()]; + this.parentStructInfo = componentCollection.currentClassName ? + processStructComponentV2.getOrCreateStructInfo(componentCollection.currentClassName) : + new StructInfo(); + this.forbiddenInitPropsV2 = [...this.childStructInfo.localDecoratorSet, + ...this.childStructInfo.providerDecoratorSet, ...this.childStructInfo.consumerDecoratorSet, + ...this.childStructInfo.regularSet]; + this.updatePropsForV1Parent = getUpdatePropsForV1Parent(); + this.updatePropsForV2Parent = [...this.parentStructInfo.localDecoratorSet, + ...this.parentStructInfo.paramDecoratorMap.keys(), ...this.parentStructInfo.providerDecoratorSet, + ...this.parentStructInfo.consumerDecoratorSet]; + } +} + +function getUpdatePropsForV1Parent(): string[] { + const propertiesMapArr: Array>> = [ + stateCollection, linkCollection, propCollection, + provideCollection, consumeCollection, objectLinkCollection, + storagePropCollection, storageLinkCollection + ]; + const updatePropsForParent: string[] = []; + if (componentCollection.currentClassName) { + const localStorageSet: Set = new Set(); + getLocalStorageCollection(componentCollection.currentClassName, localStorageSet); + updatePropsForParent.push(...localStorageSet); + propertiesMapArr.forEach((item: Map>) => { + const value: Set = item.get(componentCollection.currentClassName); + if (value) { + updatePropsForParent.push(...value); + } + }); + } + return updatePropsForParent; +} + +function parseChildProperties(childName: string, node: ts.CallExpression, + childParam: ts.PropertyAssignment[], propsAndObjectLinks: string[], log: LogInfo[]): void { + const childAndParentComponentInfo: ChildAndParentComponentInfo = + new ChildAndParentComponentInfo(childName, node, propsAndObjectLinks); + if (node.arguments[0].properties) { + node.arguments[0].properties.forEach((item: ts.PropertyAssignment) => { + if (ts.isIdentifier(item.name)) { + const itemName: string = item.name.escapedText.toString(); + validateChildProperty(item, itemName, childParam, log, childAndParentComponentInfo); + } + }); + } +} + +function validateChildProperty(item: ts.PropertyAssignment, itemName: string, + childParam: ts.PropertyAssignment[], log: LogInfo[], info: ChildAndParentComponentInfo): void { + if (info.childStructInfo.isComponentV2) { + if (info.forbiddenInitPropsV2.includes(itemName)) { + log.push({ + type: LogType.ERROR, + message: `Property '${itemName}' in the custom component '${info.childName}'` + + ` cannot be initialized here (forbidden to specify).`, + pos: item.getStart(), + code: '10905324' + }); + return; + } + if (info.paramDecoratorMap.has(itemName)) { + childParam.push(item); + } + if (isForbiddenAssignToComponentV2(item, itemName, info)) { + log.push({ + type: LogType.ERROR, + message: `Property '${itemName}' in the @ComponentV2 component '${info.childName}' are not allowed to be assigned values here.`, + pos: item.getStart(), + code: '10905323' + }); + } + } else { + if (info.propsAndObjectLinks.includes(itemName)) { + childParam.push(item); + } + } + logMessageCollection.checkIfAssignToStaticProps(item, itemName, info.childStructInfo.staticPropertySet, log); +} + +function isForbiddenTypeToComponentV1(type: ts.Type): boolean { + // @ts-ignore + if (type.types && type.types.length) { + // @ts-ignore + return !type.types.some((item: ts.Type) => { + return !isForbiddenTypeToComponentV1(item); + }); + } + const allowedTypes: string[] = ['Set', 'Map', 'Date', 'Array']; + const name: string = type?.getSymbol()?.getName(); + if (name && allowedTypes.includes(name)) { + return true; + } + return false; +} + +function isForbiddenAssignToComponentV2(item: ts.PropertyAssignment, itemName: string, + info: ChildAndParentComponentInfo): boolean { + if (!info.parentStructInfo.isComponentV2 && info.updatePropsDecoratorsV2.includes(itemName) && + isObervedProperty(item.initializer, info) && globalProgram.strictChecker) { + const type: ts.Type = globalProgram.strictChecker.getTypeAtLocation(item.initializer); + return !(isAllowedTypeToComponentV2(type) || isForbiddenTypeToComponentV1(type) || !(isObservedV2(type) || isFunctionType(type))); + } + return false; +} + +// isFunctionType +export function isFunctionType(type: ts.Type): boolean { + if (type.types && type.types.length) { + return !type.types.some((item: ts.Type) => { + return !isFunctionType(item); + }); + } + const name: string = type?.getSymbol()?.getName(); + if (name && name === 'Function') { + return true; + } + return false; +} + +function isObervedProperty(value: ts.Expression, info: ChildAndParentComponentInfo, + isV1Parent: boolean = true): boolean { + if (value && ts.isPropertyAccessExpression(value) && value.expression.kind === ts.SyntaxKind.ThisKeyword && + ts.isIdentifier(value.name)) { + const propertyName: string = value.name.escapedText.toString(); + return isV1Parent ? info.updatePropsForV1Parent.includes(propertyName) : + info.updatePropsForV2Parent.includes(propertyName); + } + return false; +} + +export function isAllowedTypeToComponentV2(type: ts.Type): boolean { + if (type) { + // @ts-ignore + if (type.types && type.types.length) { + // @ts-ignore + return type.types.some((item: ts.Type) => { + return isAllowedTypeToComponentV2(item); + }); + } + // string|number|boolean|enum|null|undefined + if (isAllowedTypeForBasic(type.flags)) { + return true; + } + } + return false; +} + +export function isAllowedTypeForBasic(flags: ts.TypeFlags): boolean { + if (isBasicType(flags) || (flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined))) { + return true; + } + return false; +} + +function validateInitParam(childName: string, curChildProps: Set, + node: ts.CallExpression, log: LogInfo[], parentStructInfo: StructInfo): void { + const childStructInfo: StructInfo = processStructComponentV2.getAliasStructInfo(node) || + processStructComponentV2.getOrCreateStructInfo(childName); + const paramDecoratorMap: Map = childStructInfo.paramDecoratorMap; + if (childStructInfo.isComponentV2) { + const needInitParam: string[] = []; + for (const item of paramDecoratorMap) { + if (item[1].hasRequire) { + needInitParam.push(item[0]); + } + } + needInitParam.forEach((paramName: string) => { + if (!curChildProps.has(paramName)) { + log.push({ + type: LogType.ERROR, + message: `Property '${paramName}' must be initialized through the component constructor.`, + pos: node.getStart(), + code: '10905321' + }); + } + }); + } else if (parentStructInfo.isComponentV2 && childStructInfo.linkDecoratorsV1.length) { + log.push({ + type: LogType.ERROR, + message: 'The @ComponentV2 struct must not contain any @Component with an @Link decorated variable', + pos: node.getStart(), + code: '10905213' + }); + } +} + +function createCustomComponent(newNode: ts.NewExpression, name: string, componentNode: ts.CallExpression, + isGlobalBuilder: boolean, isBuilder: boolean, isRecycleComponent: boolean, + componentAttrInfo: ComponentAttrInfo, builderParamsResult: BuilderParamsResult, log: LogInfo[], + isReuseComponentInV2:boolean): ts.Block { + let componentParameter: ts.ObjectLiteralExpression; + if (componentNode.arguments && componentNode.arguments.length) { + componentParameter = ts.factory.createObjectLiteralExpression(createChildElmtId(componentNode, name, log), true); + } else { + componentParameter = ts.factory.createObjectLiteralExpression([], false); + } + const arrowArgArr: ts.ParameterDeclaration[] = [ + ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(ELMTID) + ), + ts.factory.createParameterDeclaration(undefined, undefined, + ts.factory.createIdentifier(ISINITIALRENDER) + ) + ]; + const arrowBolck: ts.Statement[] = [ + projectConfig.optLazyForEach && storedFileInfo.processLazyForEach ? createCollectElmtIdNode() : undefined, + createIfCustomComponent(newNode, componentNode, componentParameter, name, isGlobalBuilder, + isBuilder, isRecycleComponent, componentAttrInfo, log) + ]; + if (isRecycleComponent) { + arrowArgArr.push(ts.factory.createParameterDeclaration( + undefined, undefined, ts.factory.createIdentifier(RECYCLE_NODE), + undefined, undefined, ts.factory.createNull() + )); + } else if (partialUpdateConfig.optimizeComponent && isGlobalBuilder && + builderParamsResult && builderParamsResult.firstParam) { + const paramName: ts.Identifier = builderParamsResult.firstParam.name as ts.Identifier; + arrowArgArr.push(ts.factory.createParameterDeclaration(undefined, undefined, + paramName, undefined, undefined, ts.factory.createIdentifier(`__${paramName.escapedText.toString()}__`) + )); + } + if (isRecycleComponent || !partialUpdateConfig.optimizeComponent) { + arrowBolck.unshift(createViewStackProcessorStatement(STARTGETACCESSRECORDINGFOR, ELMTID)); + arrowBolck.push(createViewStackProcessorStatement(STOPGETACCESSRECORDING)); + } + const observeArgArr: ts.Node[] = [ + ts.factory.createArrowFunction(undefined, undefined, arrowArgArr, undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock(arrowBolck, true)) + ]; + if (isRecycleComponent) { + componentAttrInfo.reuseId ? observeArgArr.unshift(componentAttrInfo.reuseId) : + observeArgArr.unshift(ts.factory.createStringLiteral(name)); + } else if (partialUpdateConfig.optimizeComponent) { + observeArgArr.push(componentPop(name)); + } + const reuseOrCreateArgArr: ts.ObjectLiteralExpression[] = [ts.factory.createObjectLiteralExpression( + generateReuseOrCreateArgArr(componentNode, componentAttrInfo, name, newNode), true)]; + return ts.factory.createBlock( + [ + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(isGlobalBuilder ? + ts.factory.createParenthesizedExpression(parentConditionalExpression()) : ts.factory.createThis(), + isRecycleComponent ? + ts.factory.createIdentifier(OBSERVE_RECYCLE_COMPONENT_CREATION) : + isReuseComponentInV2 ? ts.factory.createIdentifier(constantDefine.REUSE_OR_CREATE_METHOD) : + ts.factory.createIdentifier(partialUpdateConfig.optimizeComponent ? + OBSERVECOMPONENTCREATION2 : OBSERVECOMPONENTCREATION) + ), + undefined, isReuseComponentInV2 ? reuseOrCreateArgArr as ts.Expression[] : observeArgArr as ts.Expression[])) + ], + true + ); +} + +function generateReuseOrCreateArgArr(componentNode: ts.CallExpression, componentAttrInfo: ComponentAttrInfo, + name: string, newNode: ts.NewExpression): ts.ObjectLiteralElementLike[] { + const reuseParamsArr: ts.ObjectLiteralElementLike[] = []; + if (componentNode.expression && ts.isIdentifier(componentNode.expression)) { + reuseParamsArr.push(ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(constantDefine.COMPONENT_CLASS), + componentNode.expression + )); + } + reuseParamsArr.push(createReuseParameterArrowFunction(getArgumentsToPass(componentNode), constantDefine.GET_PARAMS, name)); + reuseParamsArr.push(createReuseParameterArrowFunction( + [], constantDefine.GET_REUSE_ID, componentAttrInfo.reuse ? componentAttrInfo.reuse : name)); + if (newNode.arguments.length >= 6 && ts.isObjectLiteralExpression(newNode.arguments[5])) { + reuseParamsArr.push(generateExtraInfo(true, newNode.arguments[5])); + } else { + reuseParamsArr.push(generateExtraInfo(false)); + } + return reuseParamsArr; +} + +function getArgumentsToPass(componentNode: ts.CallExpression): ts.NodeArray | undefined[] { + if (componentNode.arguments && componentNode.arguments.length === 1 && + ts.isObjectLiteralExpression(componentNode.arguments[0]) && componentNode.arguments[0].properties && + componentNode.arguments[0].properties.length) { + return componentNode.arguments[0].properties; + } + return []; +} + +function createReuseParameterArrowFunction(propertyArray: ts.NodeArray | undefined[], + identifierName: string, name: string): ts.PropertyAssignment { + return ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(identifierName), + ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + identifierName === constantDefine.GET_REUSE_ID ? + ts.factory.createStringLiteral(name) : + ts.factory.createParenthesizedExpression(ts.factory.createObjectLiteralExpression( + propertyArray.length ? propertyArray : [], + true + )) + ) + ); +} + +function generateExtraInfo(hasPositionInfo: boolean, positionInfo?: ts.ObjectLiteralExpression): ts.PropertyAssignment { + return ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(constantDefine.EXTRA_INFO), + hasPositionInfo ? + positionInfo : + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) + ); +} + +function componentPop(name: string): ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression( + [ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(NAME), + ts.factory.createStringLiteral(name) + )], + false + ); +} + +export function assignComponentParams(componentNode: ts.CallExpression, + isBuilder: boolean = false): ts.VariableStatement { + const isParamsLambda: boolean = true; + const [keyArray, valueArray]: [ts.Node[], ts.Node[]] = splitComponentParams(componentNode, isBuilder, isParamsLambda); + let integrateParams: boolean = false; + if (!keyArray.length && componentNode.arguments && componentNode.arguments.length > 0 && componentNode.arguments[0]) { + integrateParams = true; + } + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(COMPONENT_PARAMS_LAMBDA_FUNCTION), + undefined, + undefined, + ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [ts.factory.createReturnStatement( + integrateParams ? paramsLambdaCallBack(componentNode) : ts.factory.createObjectLiteralExpression( + reWriteComponentParams(keyArray, valueArray), + true + ) + )], + true + ) + ) + )], + ts.NodeFlags.Let + )); +} + +function paramsLambdaCallBack(componentNode: ts.CallExpression): ts.Expression { + if (partialUpdateConfig.partialUpdateMode && componentNode.arguments.length === 1 && + isLocalStorageParameter(componentNode)) { + return ts.factory.createObjectLiteralExpression([], true); + } else { + return componentNode.arguments[0]; + } +} + +function reWriteComponentParams(keyArray: ts.Node[], valueArray: ts.Node[]): (ts.PropertyAssignment | + ts.ShorthandPropertyAssignment)[] { + const returnProperties: (ts.PropertyAssignment | ts.ShorthandPropertyAssignment)[] = []; + keyArray.forEach((item: ts.Identifier, index: number) => { + if (!valueArray[index]) { + returnProperties.push(ts.factory.createShorthandPropertyAssignment( + item, + undefined + )); + } else { + returnProperties.push(ts.factory.createPropertyAssignment( + item, + valueArray[index] as ts.Identifier + )); + } + }); + return returnProperties; +} + +function splitComponentParams(componentNode: ts.CallExpression, isBuilder: boolean, isParamsLambda: boolean): [ts.Node[], ts.Node[]] { + const keyArray: ts.Node[] = []; + const valueArray: ts.Node[] = []; + if (componentNode.arguments && componentNode.arguments.length > 0 && + ts.isObjectLiteralExpression(componentNode.arguments[0]) && componentNode.arguments[0].properties) { + componentNode.arguments[0].properties.forEach((propertyItem: ts.PropertyAssignment) => { + const newPropertyItem: ts.PropertyAssignment = + isProperty(propertyItem) ? createReference(propertyItem, [], isBuilder, isParamsLambda) : propertyItem; + keyArray.push(newPropertyItem.name); + valueArray.push(newPropertyItem.initializer); + }); + } + return [keyArray, valueArray]; +} + +function createIfCustomComponent(newNode: ts.NewExpression, componentNode: ts.CallExpression, + componentParameter: ts.ObjectLiteralExpression, name: string, isGlobalBuilder: boolean, isBuilder: boolean, + isRecycleComponent: boolean, componentAttrInfo: ComponentAttrInfo, log: LogInfo[]): ts.IfStatement { + return ts.factory.createIfStatement( + ts.factory.createIdentifier(ISINITIALRENDER), + ts.factory.createBlock( + [componentParamDetachment(newNode, isRecycleComponent, name, log, componentNode), + isRecycleComponent ? createNewRecycleComponent(newNode, componentNode, name, componentAttrInfo) : + createNewComponent(COMPONENT_CALL, name, componentNode), + assignComponentParams(componentNode, isBuilder), + assignmentFunction(COMPONENT_CALL) + ], true), + ts.factory.createBlock( + [ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(isGlobalBuilder ? + ts.factory.createParenthesizedExpression(parentConditionalExpression()) : ts.factory.createThis(), + ts.factory.createIdentifier(UPDATE_STATE_VARS_OF_CHIND_BY_ELMTID) + ), undefined, + [ts.factory.createIdentifier(ELMTID), componentParameter]))], true) + ); +} + +export function assignmentFunction(componeParamName: string): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(componeParamName), + ts.factory.createIdentifier(COMPONENT_PARAMS_FUNCTION) + ), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + ts.factory.createIdentifier(COMPONENT_PARAMS_LAMBDA_FUNCTION) + )); +} + +function traverseChildComponentArgs(childParam: ts.Expression[], name: string, log: LogInfo[], + componentNode: ts.CallExpression): ts.Expression[] { + const childStructInfo: StructInfo = processStructComponentV2.getAliasStructInfo(componentNode) || + processStructComponentV2.getOrCreateStructInfo(name); + if (!childStructInfo.isComponentV2) { + return childParam; + } + const objectLiteralIndex: number = 2; + if (childParam.length > objectLiteralIndex && ts.isObjectLiteralExpression(childParam[1]) && + childParam[1].properties) { + const newProperties: ts.PropertyAssignment[] = []; + childParam[1].properties.forEach((item: ts.PropertyAssignment) => { + if (item.name && ts.isIdentifier(item.name)) { + const itemName: string = item.name.escapedText.toString(); + updatePropertyAssignment(newProperties, itemName, item, childStructInfo, log); + } + }); + if (newProperties.length) { + return getNewArgsForCustomComponent(childParam, newProperties); + } + } + return childParam; +} + +function getNewArgsForCustomComponent(childParam: ts.Expression[], + newProperties: ts.PropertyAssignment[]): ts.Expression[] { + const newArr: ts.Expression[] = []; + const newObjectLiteralNode: ts.ObjectLiteralExpression = + ts.factory.updateObjectLiteralExpression(childParam[1], [...childParam[1].properties, ...newProperties]); + childParam.forEach((item: ts.Expression, index: number) => { + if (index === 1) { + newArr.push(newObjectLiteralNode); + } else { + newArr.push(item); + } + }); + return newArr; +} + +function updatePropertyAssignment(newProperties: ts.PropertyAssignment[], + itemName: string, item: ts.PropertyAssignment, childStructInfo: StructInfo, log: LogInfo[]): void { + if (isDoubleNonNullExpression(item.initializer)) { + if (isLeftHandExpression(item.initializer.expression.expression)) { + const result: Record = { hasQuestionToken: false }; + traverseExpressionNode(item.initializer.expression.expression, result); + if (result.hasQuestionToken) { + log.push({ + type: LogType.ERROR, + message: `The optional character can not be used in the initial value of property '${itemName}'.`, + pos: item.getStart(), + code: '10905320' + }); + return; + } + if (childStructInfo.paramDecoratorMap.has(itemName) && + childStructInfo.eventDecoratorMap.has('$' + itemName)) { + newProperties.push(createUpdateTwoWayNode(itemName, item.initializer.expression.expression)); + return; + } + log.push({ + type: LogType.ERROR, + message: 'When the two-way binding syntax is used, ' + + `the variable '${itemName}' must be decorated with @Param, ` + + `and the @Event variable '$` + `${itemName}' ` + `must be defined in the ${childStructInfo.structName}.`, + pos: item.getStart(), + code: '10905319' + }); + return; + } + log.push({ + type: LogType.ERROR, + message: 'When the two-way binding syntax is used, ' + + `the initial value of property '${itemName}' must be a variable.`, + pos: item.getStart(), + code: '10905318' + }); + return; + } +} + +function validateTwoWayComputed(node: ts.PropertyAccessExpression, log: LogInfo[]): void { + if (ts.isPropertyAccessExpression(node) && node.expression && + node.expression.kind === ts.SyntaxKind.ThisKeyword && globalProgram.checker) { + const symbol: ts.Symbol = globalProgram.checker.getSymbolAtLocation(node); + logMessageCollection.checkTwoWayComputed(node, symbol, log); + } +} + +function createUpdateTwoWayNode(itemName: string, leftHandExpression: ts.Expression): ts.PropertyAssignment { + return ts.factory.createPropertyAssignment( + ts.factory.createIdentifier('$' + itemName), + ts.factory.createArrowFunction(undefined, undefined, + [createAstNodeUtils.createParameterDeclaration('value')], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock([ + ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + leftHandExpression, ts.factory.createToken(ts.SyntaxKind.EqualsToken), + ts.factory.createIdentifier('value') + )) + ], false) + ) + ); +} + +function isDoubleNonNullExpression(node: ts.Expression): boolean { + return node && ts.isNonNullExpression(node) && ts.isNonNullExpression(node.expression); +} + +function isLeftHandExpression(node: ts.Expression): boolean { + return node && (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node)); +} + +function traverseExpressionNode(node: ts.Node, result: Record): void { + if (ts.isOptionalChain(node) && !ts.isNonNullExpression(node) && node.questionDotToken) { + result.hasQuestionToken = true; + } + if (!result.hasQuestionToken) { + ts.forEachChild(node, (child: ts.Node) => traverseExpressionNode(child, result)); + } +} + +function componentParamDetachment(newNode: ts.NewExpression, isRecycleComponent: boolean, + name: string, log: LogInfo[], componentNode: ts.CallExpression): ts.VariableStatement { + const paramsArray: ts.Expression[] = newNode.arguments.length ? newNode.arguments : []; + const updateNewNode = ts.factory.updateNewExpression(newNode, newNode.expression, + newNode.typeArguments, traverseChildComponentArgs(paramsArray, name, log, componentNode)); + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(COMPONENT_CALL), + undefined, + undefined, + isRecycleComponent ? ts.factory.createConditionalExpression( + ts.factory.createIdentifier(RECYCLE_NODE), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createIdentifier(RECYCLE_NODE), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + newNode) : updateNewNode + )], + ts.NodeFlags.Let + )); +} + +function createNewComponent(componeParamName: string, name: string, + componentNode: ts.CallExpression): ts.Statement { + const childStructInfo: StructInfo = processStructComponentV2.getAliasStructInfo(componentNode) || + processStructComponentV2.getOrCreateStructInfo(name); + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier( + childStructInfo.isComponentV2 ? constantDefine.STRUCT_PARENT : BASE_COMPONENT_NAME_PU), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION) + ), undefined, [ts.factory.createIdentifier(componeParamName)])); +} + +function createNewRecycleComponent(newNode: ts.NewExpression, componentNode: ts.CallExpression, + name: string, componentAttrInfo: ComponentAttrInfo): ts.Statement { + let argNode: ts.Expression[] = []; + const componentParam: ts.PropertyAssignment[] = []; + if (componentNode.arguments && componentNode.arguments.length > 0 && + ts.isObjectLiteralExpression(componentNode.arguments[0]) && componentNode.arguments[0].properties) { + componentNode.arguments[0].properties.forEach((propertyItem: ts.PropertyAssignment) => { + const newPropertyItem: ts.PropertyAssignment = isProperty(propertyItem) ? + createReference(propertyItem, [], false, false, true) : propertyItem; + componentParam.push(newPropertyItem); + }); + argNode = [ts.factory.createObjectLiteralExpression(componentParam, false)]; + } else { + argNode = [ts.factory.createObjectLiteralExpression([], false)]; + } + const recycleNode: ts.CallExpression = ts.factory.createCallExpression( + createRecyclePropertyNode(ABOUT_TO_REUSE), undefined, argNode); + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(BASE_COMPONENT_NAME_PU), + ts.factory.createIdentifier(COMPONENT_CREATE_RECYCLE) + ), undefined, + [ + ts.factory.createIdentifier(COMPONENT_CALL), + ts.factory.createBinaryExpression( + ts.factory.createIdentifier(RECYCLE_NODE), + ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), + ts.factory.createNull() + ), + componentAttrInfo.reuseId ? componentAttrInfo.reuseId as ts.Expression : + ts.factory.createStringLiteral(name), + ts.factory.createArrowFunction(undefined, undefined, [], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock([ + ts.factory.createIfStatement( + ts.factory.createBinaryExpression( + ts.factory.createIdentifier(RECYCLE_NODE), + ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + ts.factory.createBinaryExpression( + ts.factory.createTypeOfExpression( + createRecyclePropertyNode(COMPONENT_ABOUTTOREUSEINTERNAL_FUNCTION)), + ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + ts.factory.createStringLiteral(FUNCTION) + )), + ts.factory.createBlock([ + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + createRecyclePropertyNode(COMPONENT_ABOUTTOREUSEINTERNAL_FUNCTION), + undefined, + [] + )) + ], true), + ts.factory.createBlock( + [ + ts.factory.createIfStatement(ts.factory.createBinaryExpression( + createRecyclePropertyNode(ABOUT_TO_REUSE), ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + ts.factory.createBinaryExpression( + ts.factory.createTypeOfExpression(createRecyclePropertyNode(ABOUT_TO_REUSE)), + ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + ts.factory.createStringLiteral(FUNCTION) + )), + ts.factory.createBlock([ts.factory.createExpressionStatement(recycleNode)], true)), + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + createRecyclePropertyNode(COMPONENT_RERENDER_FUNCTION), undefined, [] + )) + ], + true + ) + )], true)) + ])); +} + +function createRecyclePropertyNode(recycleFunctionName: string): ts.PropertyAccessExpression { + return ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(RECYCLE_NODE), ts.factory.createIdentifier(recycleFunctionName)); +} + +function validateCustomComponentPrams(node: ts.CallExpression, name: string, + props: ts.ObjectLiteralElementLike[], log: LogInfo[], isBuilder: boolean): void { + const curChildProps: Set = new Set([]); + const nodeArguments: ts.NodeArray = node.arguments; + const linkSet: Set = getCollectionSet(name, linkCollection); + if (nodeArguments && nodeArguments.length >= 1 && + ts.isObjectLiteralExpression(nodeArguments[0])) { + const nodeArgument: ts.ObjectLiteralExpression = nodeArguments[0] as ts.ObjectLiteralExpression; + const doubleExclamationCollection: string[] = []; + const dollarPropertyCollection: string[] = []; + nodeArgument.properties.forEach(item => { + if (item.name && ts.isIdentifier(item.name)) { + const propName: string = item.name.escapedText.toString(); + curChildProps.add(propName); + logMessageCollection.checkIfAssignToStaticProps(item, propName, regularStaticCollection.get(name) || new Set(), log); + } + validateStateManagement(item, name, log, isBuilder, doubleExclamationCollection, dollarPropertyCollection); + if (isNonThisProperty(item, linkSet)) { + if (isToChange(item as ts.PropertyAssignment, name)) { + item = ts.factory.updatePropertyAssignment(item as ts.PropertyAssignment, + item.name, changeNodeFromCallToArrow(item.initializer)); + } + props.push(item); + } + }); + logMessageCollection.checkIfNeedDollarEvent(doubleExclamationCollection, dollarPropertyCollection, node, log); + } + const parentStructInfo: StructInfo = componentCollection.currentClassName ? + processStructComponentV2.getOrCreateStructInfo(componentCollection.currentClassName) : + new StructInfo(); + validateInitDecorator(node, name, curChildProps, log); + validateMandatoryToInitViaParam(node, name, curChildProps, log, parentStructInfo); + validateInitParam(name, curChildProps, node, log, parentStructInfo); +} + +function getCustomComponentNode(node: ts.ExpressionStatement): ts.CallExpression { + let temp: any = node.expression; + let child: any = null; + let componentNode: any = null; + while (temp) { + if (ts.isIdentifier(temp)) { + child = temp; + break; + } + temp = temp.expression; + } + if (child) { + let parent = child.parent; + while (parent) { + if (ts.isExpressionStatement(parent)) { + break; + } + if (ts.isCallExpression(parent) || ts.isEtsComponentExpression(parent)) { + componentNode = parent; + break; + } + parent = parent.parent; + } + } + return componentNode; +} + +export enum ParentType { + NormalComponentV1, + NormalComponentV2, + ReuseComponentV1, + ReuseComponentV2, + InvalidComponentType +} + +function getParentComponentType(parentName: string): ParentType { + const parentCustomComponentInfo: StructInfo = parentName ? + processStructComponentV2.getOrCreateStructInfo(parentName) : new StructInfo(); + let type: ParentType = ParentType.InvalidComponentType; + if (parentCustomComponentInfo.isComponentV1 && + parentCustomComponentInfo.isReusable) { + type = ParentType.ReuseComponentV1; + } else if (parentCustomComponentInfo.isComponentV1 && + !parentCustomComponentInfo.isReusable) { + type = ParentType.NormalComponentV1; + } else if (parentCustomComponentInfo.isComponentV2 && + parentCustomComponentInfo.isReusableV2) { + type = ParentType.ReuseComponentV2; + } else if (parentCustomComponentInfo.isComponentV2 && + !parentCustomComponentInfo.isReusableV2) { + type = ParentType.NormalComponentV2; + } + return type; +} + +function getCollectionSet(name: string, collection: Map>): Set { + if (!collection) { + return new Set([]); + } + return collection.get(name) || new Set([]); +} + +function isThisProperty(node: ts.ObjectLiteralElementLike, propertySet: Set): boolean { + if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && + propertySet.has(node.name.escapedText.toString())) { + return true; + } + return false; +} + +function isNonThisProperty(node: ts.ObjectLiteralElementLike, propertySet: Set): boolean { + if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && + (node.initializer.escapedText && node.initializer.escapedText.includes('$') || + ts.isPropertyAccessExpression(node.initializer) && node.initializer.expression && + node.initializer.expression.kind === ts.SyntaxKind.ThisKeyword && + ts.isIdentifier(node.initializer.name) && node.initializer.name.escapedText.toString().includes('$'))) { + return false; + } + if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && + !propertySet.has(node.name.escapedText.toString())) { + return true; + } + return false; +} + +function validateStateManagement(node: ts.ObjectLiteralElementLike, customComponentName: string, log: LogInfo[], + isBuilder: boolean, doubleExclamationCollection: string[], dollarPropertyCollection: string[]): void { + validateForbiddenToInitViaParam(node, customComponentName, log); + if (componentCollection.currentClassName) { + const parentStructInfo: StructInfo = + processStructComponentV2.getOrCreateStructInfo(componentCollection.currentClassName); + if (parentStructInfo.isComponentV2) { + if (ts.isPropertyAssignment(node)) { + checkDoubleExclamationPropertyAssignment(node, log, doubleExclamationCollection); + checkDollarPropertyAssignment(node, dollarPropertyCollection); + } + return; + } + } + checkFromParentToChild(node, customComponentName, log, isBuilder); +} + +function checkDoubleExclamationPropertyAssignment(node: ts.PropertyAssignment, log: LogInfo[], doubleExclamationCollection: string[]): void { + if (node.initializer && isDoubleNonNullExpression(node.initializer)) { + if (node.initializer.expression && node.initializer.expression.expression && + isLeftHandExpression(node.initializer.expression.expression)) { + doubleExclamationCollection.push(node.name.getText()); + validateTwoWayComputed(node.initializer.expression.expression, log); + } + } +} + +function checkDollarPropertyAssignment(node: ts.PropertyAssignment, dollarPropertyCollection: string[]): void { + const regex = /^\$[a-zA-Z_][a-zA-Z0-9_]*$/; + if (node.name && ts.isIdentifier(node.name) && regex.test(node.name.escapedText.toString())) { + dollarPropertyCollection.push(node.name.escapedText.toString()); + } +} + +function checkFromParentToChild(node: ts.ObjectLiteralElementLike, customComponentName: string, + log: LogInfo[], isBuilder: boolean): void { + let propertyName: string; + if (ts.isIdentifier(node.name)) { + propertyName = node.name.escapedText.toString(); + } + const curPropertyKind: string = getPropertyDecoratorKind(propertyName, customComponentName); + let parentPropertyName: string; + if (curPropertyKind) { + if (isInitFromParent(node)) { + parentPropertyName = + getParentPropertyName(node as ts.PropertyAssignment, curPropertyKind, log); + let parentPropertyKind: string = PropMapManager.find(parentPropertyName); + if (parentPropertyKind && !isCorrectInitFormParent(parentPropertyKind, curPropertyKind)) { + validateIllegalInitFromParent( + node, propertyName, curPropertyKind, parentPropertyName, parentPropertyKind, log); + } + } else if (isInitFromLocal(node) && ts.isPropertyAssignment(node) && + curPropertyKind !== COMPONENT_OBJECT_LINK_DECORATOR) { + if (!isCorrectInitFormParent(COMPONENT_NON_DECORATOR, curPropertyKind)) { + validateIllegalInitFromParent(node, propertyName, curPropertyKind, + node.initializer.getText(), COMPONENT_NON_DECORATOR, log); + } + } else if (curPropertyKind === COMPONENT_OBJECT_LINK_DECORATOR && node.initializer && + (ts.isPropertyAccessExpression(node.initializer) || + ts.isElementAccessExpression(node.initializer) || ts.isIdentifier(node.initializer))) { + return; + } else { + parentPropertyName = + getParentPropertyName(node as ts.PropertyAssignment, curPropertyKind, log) || propertyName; + const parentPropertyKind = COMPONENT_NON_DECORATOR; + if (!isCorrectInitFormParent(parentPropertyKind, curPropertyKind)) { + if (isBuilder && judgeStructAssignedDollar(node)) { + log.push({ + type: LogType.WARN, + message: `Unrecognized property '${parentPropertyName}', make sure it can be assigned to ` + + `${curPropertyKind} property '${propertyName}' by yourself.`, + // @ts-ignore + pos: node.initializer ? node.initializer.getStart() : node.getStart() + }); + } else { + validateIllegalInitFromParent( + node, propertyName, curPropertyKind, parentPropertyName, parentPropertyKind, log, LogType.WARN); + } + } + } + } +} + +function judgeStructAssignedDollar(node: ts.ObjectLiteralElementLike): boolean { + return partialUpdateConfig.partialUpdateMode && node.initializer && + ts.isPropertyAccessExpression(node.initializer) && + node.initializer.expression && ts.isIdentifier(node.initializer.expression) && + node.initializer.expression.escapedText.toString() === $$; +} + +function isInitFromParent(node: ts.ObjectLiteralElementLike): boolean { + if (ts.isPropertyAssignment(node) && node.initializer) { + if (ts.isPropertyAccessExpression(node.initializer) && node.initializer.expression && + node.initializer.expression.kind === ts.SyntaxKind.ThisKeyword && + ts.isIdentifier(node.initializer.name)) { + return true; + } else if (ts.isIdentifier(node.initializer) && + matchStartWithDollar(node.initializer.getText())) { + return true; + } + } + return false; +} + +function isInitFromLocal(node: ts.ObjectLiteralElementLike): boolean { + if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.initializer) && + !matchStartWithDollar(node.initializer.getText())) { + return true; + } + return false; +} + +function getParentPropertyName(node: ts.PropertyAssignment, curPropertyKind: string, + log: LogInfo[]): string { + const initExpression: ts.Expression = node.initializer; + if (!initExpression) { + return undefined; + } + let parentPropertyName: string = initExpression.getText(); + const symbol = globalProgram.checker?.getSymbolAtLocation(initExpression); + if (curPropertyKind === COMPONENT_LINK_DECORATOR) { + // @ts-ignore + const initName: ts.Identifier = initExpression.name || initExpression; + if (!symbol && hasDollar(initExpression)) { + parentPropertyName = initName.getText().replace(/^\$/, ''); + } else { + parentPropertyName = initName.getText(); + } + } else { + if (!symbol && hasDollar(initExpression)) { + validateNonLinkWithDollar(node, log); + } + // @ts-ignore + if (node.initializer && node.initializer.name) { + parentPropertyName = node.initializer.name.getText(); + } + } + + return parentPropertyName; +} + +function isCorrectInitFormParent(parent: string, child: string): boolean { + switch (child) { + case COMPONENT_STATE_DECORATOR: + case COMPONENT_PROP_DECORATOR: + case COMPONENT_PROVIDE_DECORATOR: + return true; + case COMPONENT_NON_DECORATOR: + if ([COMPONENT_NON_DECORATOR, COMPONENT_STATE_DECORATOR, COMPONENT_LINK_DECORATOR, COMPONENT_PROP_DECORATOR, + COMPONENT_OBJECT_LINK_DECORATOR, COMPONENT_STORAGE_LINK_DECORATOR].includes(parent)) { + return true; + } + break; + case COMPONENT_LINK_DECORATOR: + case COMPONENT_OBJECT_LINK_DECORATOR: + return ![COMPONENT_NON_DECORATOR].includes(parent); + } + return false; +} + +function getPropertyDecoratorKind(propertyName: string, customComponentName: string): string { + for (const item of decoractorMap.entries()) { + if (getCollectionSet(customComponentName, item[1]).has(propertyName)) { + return item[0]; + } + } + return undefined; +} + +function createFindChildById(id: string, name: string, isBuilder: boolean = false): ts.VariableStatement { + return ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration(ts.factory.createIdentifier( + `${CUSTOM_COMPONENT_EARLIER_CREATE_CHILD}${id}`), undefined, ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier(name)), + ts.factory.createConditionalExpression( + ts.factory.createParenthesizedExpression( + ts.factory.createBinaryExpression( + createConditionParent(isBuilder), + ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + ts.factory.createPropertyAccessExpression( + createConditionParent(isBuilder), + ts.factory.createIdentifier(CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID) + ))), ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createAsExpression(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(createConditionParent(isBuilder), + ts.factory.createIdentifier(`${CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID}`)), undefined, + [isBuilder ? ts.factory.createCallExpression(ts.factory.createIdentifier(GENERATE_ID), + undefined, []) : ts.factory.createStringLiteral(id)]), + ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(name))), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED)))], ts.NodeFlags.Let)); +} + +export function createConditionParent(isBuilder: boolean): ts.ParenthesizedExpression | ts.ThisExpression { + return isBuilder ? ts.factory.createParenthesizedExpression(parentConditionalExpression()) : ts.factory.createThis(); +} + +function createCustomComponentIfStatement(id: string, node: ts.ExpressionStatement, + newObjectLiteralExpression: ts.ObjectLiteralExpression, parentName: string): ts.IfStatement { + const viewName: string = `${CUSTOM_COMPONENT_EARLIER_CREATE_CHILD}${id}`; + return ts.factory.createIfStatement(ts.factory.createBinaryExpression( + ts.factory.createIdentifier(viewName), + ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken), + ts.factory.createIdentifier(`${COMPONENT_CONSTRUCTOR_UNDEFINED}`)), + ts.factory.createBlock([node], true), + ts.factory.createBlock([ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier( + viewName), ts.factory.createIdentifier( + `${COMPONENT_CONSTRUCTOR_UPDATE_PARAMS}`)), undefined, [newObjectLiteralExpression])), + isStaticViewCollection.get(parentName) ? createStaticIf(viewName) : undefined, + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(`${BASE_COMPONENT_NAME}`), + ts.factory.createIdentifier(`${COMPONENT_CREATE_FUNCTION}`)), undefined, + [ts.factory.createIdentifier(viewName)]))], true)); +} + +function createStaticIf(name: string): ts.IfStatement { + return ts.factory.createIfStatement(ts.factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name), + ts.factory.createIdentifier(CUSTOM_COMPONENT_NEEDS_UPDATE_FUNCTION)), undefined, [])), + ts.factory.createBlock([ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name), + ts.factory.createIdentifier(CUSTOM_COMPONENT_MARK_STATIC_FUNCTION)), + undefined, []))], true), undefined); +} + +function hasDollar(initExpression: ts.Expression): boolean { + if (ts.isPropertyAccessExpression(initExpression) && + matchStartWithDollar(initExpression.name.getText())) { + return true; + } else if (ts.isIdentifier(initExpression) && matchStartWithDollar(initExpression.getText())) { + return true; + } else { + return false; + } +} + +function matchStartWithDollar(name: string): boolean { + return /^\$/.test(name); +} + +function validateForbiddenToInitViaParam(node: ts.ObjectLiteralElementLike, + customComponentName: string, log: LogInfo[]): void { + const forbiddenToInitViaParamSet: Set = new Set([ + ...getCollectionSet(customComponentName, storageLinkCollection), + ...getCollectionSet(customComponentName, storagePropCollection), + ...getCollectionSet(customComponentName, consumeCollection) + ]); + const localStorageSet: Set = new Set(); + getLocalStorageCollection(customComponentName, localStorageSet); + if (isThisProperty(node, forbiddenToInitViaParamSet) || isThisProperty(node, localStorageSet)) { + log.push({ + type: LogType.ERROR, + message: `Property '${node.name.getText()}' in the custom component '${customComponentName}'` + + ` cannot be initialized here (forbidden to specify).`, + pos: node.name.getStart(), + code: '10905317' + }); + } +} + +function validateMandatoryToInitViaParam(node: ts.CallExpression, customComponentName: string, + curChildProps: Set, log: LogInfo[], parentStructInfo: StructInfo): void { + let mandatoryToInitViaParamSet: Set; + if (projectConfig.compileMode === 'esmodule' && process.env.compileTool === 'rollup' && node.expression) { + const overAll: string[] = [ + ...getCollectionSet(node.expression.getText(), storedFileInfo.overallObjectLinkCollection)]; + if (!parentStructInfo.isComponentV2) { + overAll.unshift(...getCollectionSet(node.expression.getText(), storedFileInfo.overallLinkCollection)); + } + mandatoryToInitViaParamSet = new Set(overAll); + customComponentName = node.expression.getText(); + } else { + const arr: string[] = [...getCollectionSet(customComponentName, objectLinkCollection)]; + if (!parentStructInfo.isComponentV2) { + arr.unshift(...getCollectionSet(customComponentName, linkCollection)); + } + mandatoryToInitViaParamSet = new Set(arr); + } + mandatoryToInitViaParamSet.forEach(item => { + if (item && !curChildProps.has(item)) { + log.push({ + type: LogType.ERROR, + message: `Property '${item}' in the custom component '${customComponentName}'` + + ` is missing (mandatory to specify).`, + pos: node.getStart(), + code: '10905316' + }); + } + }); +} + +function validateInitDecorator(node: ts.CallExpression, customComponentName: string, + curChildProps: Set, log: LogInfo[]): void { + const mandatoryToInitViaParamSet: Set = new Set([ + ...getCollectionSet(customComponentName, builderParamObjectCollection), + ...getCollectionSet(customComponentName, propCollection), + ...getCollectionSet(customComponentName, regularCollection), + ...getCollectionSet(customComponentName, stateCollection), + ...getCollectionSet(customComponentName, provideCollection) + ]); + const decoratorVariable: Set = new Set([ + ...(builderParamInitialization.get(customComponentName) || []), + ...(propInitialization.get(customComponentName) || []), + ...(regularInitialization.get(customComponentName) || []), + ...(stateInitialization.get(customComponentName) || []), + ...(provideInitialization.get(customComponentName) || []) + ]); + mandatoryToInitViaParamSet.forEach((item: string) => { + if (item && !curChildProps.has(item) && decoratorVariable && decoratorVariable.has(item)) { + log.push({ + type: LogType.ERROR, + message: `Property '${item}' must be initialized through the component constructor.`, + pos: node.getStart(), + code: '10905359' + }); + } + }); + const privateParamSet: Set = privateCollection.get(customComponentName) || new Set([]); + curChildProps.forEach((item: string) => { + if (privateParamSet.has(item)) { + log.push({ + type: LogType.WARN, + message: `Property '${item}' is private and can not be initialized through the component constructor.`, + pos: node.getStart() + }); + } + }); +} + +function validateIllegalInitFromParent(node: ts.ObjectLiteralElementLike, propertyName: string, + curPropertyKind: string, parentPropertyName: string, parentPropertyKind: string, + log: LogInfo[], inputType: LogType = undefined): void { + let type: LogType = LogType.ERROR; + if (inputType) { + type = inputType; + } else if ([COMPONENT_STATE_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR].includes( + parentPropertyKind) && curPropertyKind === COMPONENT_OBJECT_LINK_DECORATOR) { + type = LogType.WARN; + } + PropMapManager.reserveLog(parentPropertyName, parentPropertyKind, { + type: type, + message: `The ${parentPropertyKind} property '${parentPropertyName}' cannot be assigned to ` + + `the ${curPropertyKind} property '${propertyName}'.`, + // @ts-ignore + pos: node.initializer ? node.initializer.getStart() : node.getStart(), + code: type === LogType.ERROR ? '10905315' : undefined + }); +} + +function validateNonLinkWithDollar(node: ts.PropertyAssignment, log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `Property '${node.name.getText()}' cannot initialize` + + ` using '$' to create a reference to a variable.`, + pos: node.initializer.getStart(), + code: '10905314' + }); +} diff --git a/compiler/src/interop/src/process_dts_file.ts b/compiler/src/interop/src/process_dts_file.ts new file mode 100644 index 0000000000000000000000000000000000000000..f9606445e1339fb3b3b9c3272a0367e5392d2cbf --- /dev/null +++ b/compiler/src/interop/src/process_dts_file.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 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. + */ + +function ignoringFiles(): string { + this.cacheable && this.cacheable(); + return ''; +} + +module.exports = ignoringFiles; diff --git a/compiler/src/interop/src/process_har_writejs.ts b/compiler/src/interop/src/process_har_writejs.ts new file mode 100644 index 0000000000000000000000000000000000000000..7863e46977d22e334a36bed3cebca7940ed559e2 --- /dev/null +++ b/compiler/src/interop/src/process_har_writejs.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 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 { generateSourceFilesInHar } from './utils'; +import { projectConfig } from '../main.js'; + +module.exports = function writejsfile(source: string): string { + generateSourceFilesInHar(this.resourcePath, source, '.js', projectConfig); + return source; +}; diff --git a/compiler/src/interop/src/process_import.ts b/compiler/src/interop/src/process_import.ts new file mode 100644 index 0000000000000000000000000000000000000000..e7ddf43276108168a87f428ca7445d2b57d84a88 --- /dev/null +++ b/compiler/src/interop/src/process_import.ts @@ -0,0 +1,987 @@ +/* + * Copyright (c) 2021 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 ts from 'typescript'; +import fs from 'fs'; +import path from 'path'; +import JSON5 from 'json5'; + +import { + EXTNAME_ETS, + EXTNAME_TS, + INDEX_ETS, + INDEX_TS, + CUSTOM_COMPONENT_DEFAULT, + CUSTOM_DECORATOR_NAME, + COMPONENT_DECORATOR_ENTRY, + COMPONENT_BUILDER_DECORATOR, + DECORATOR_REUSEABLE, + DECORATOR_REUSABLE_V2 +} from './pre_define'; +import { + propertyCollection, + linkCollection, + componentCollection, + preprocessExtend, + preprocessNewExtend, + processSystemApi, + propCollection, + isObservedClass, + isCustomDialogClass, + observedClassCollection, + enumCollection, + getComponentSet, + IComponentSet, + builderParamObjectCollection, + stateCollection, + regularCollection, + storagePropCollection, + storageLinkCollection, + provideCollection, + consumeCollection, + objectLinkCollection, + localStorageLinkCollection, + localStoragePropCollection, + builderParamInitialization, + propInitialization, + regularInitialization, + stateInitialization, + provideInitialization, + privateCollection, + regularStaticCollection +} from './validate_ui_syntax'; +import { + getExtensionIfUnfullySpecifiedFilepath, + hasDecorator, + LogInfo, + LogType, + storedFileInfo +} from './utils'; +import { + projectConfig, + sdkConfigs, + sdkConfigPrefix, + globalProgram +} from '../main'; +import { + CUSTOM_BUILDER_METHOD, + INNER_COMPONENT_NAMES, + GLOBAL_CUSTOM_BUILDER_METHOD +} from './component_map'; +import { + type ResolveModuleInfo, + SOURCE_FILES +} from './ets_checker'; +import { + getRealModulePath, + validateModuleSpecifier +} from './fast_build/system_api/api_check_utils'; +import processStructComponentV2, { StructInfo } from './process_struct_componentV2'; + +const IMPORT_FILE_ASTCACHE: Map = + process.env.watchMode === 'true' ? new Map() : (SOURCE_FILES || new Map()); + +export default function processImport(node: ts.ImportDeclaration | ts.ImportEqualsDeclaration | + ts.ExportDeclaration, pagesDir: string, log: LogInfo[], asName: Map = new Map(), + isEntryPage: boolean = true, pathCollection: Set = new Set()): void { + let filePath: string; + let defaultName: string; + if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) { + filePath = node.moduleSpecifier.getText().replace(/'|"/g, ''); + if (ts.isImportDeclaration(node) && node.importClause && node.importClause.name && + ts.isIdentifier(node.importClause.name)) { + defaultName = node.importClause.name.escapedText.toString(); + if (isEntryPage) { + asName.set(defaultName, defaultName); + } + } + if (ts.isImportDeclaration(node) && node.importClause && node.importClause.namedBindings && + ts.isNamedImports(node.importClause.namedBindings) && + node.importClause.namedBindings.elements && isEntryPage) { + validateModuleSpecifier(node.moduleSpecifier, log); + node.importClause.namedBindings.elements.forEach(item => { + if (item.name && ts.isIdentifier(item.name)) { + validateModuleName(item.name, log); + if (item.propertyName && ts.isIdentifier(item.propertyName) && asName) { + asName.set(item.propertyName.escapedText.toString(), item.name.escapedText.toString()); + } else { + asName.set(item.name.escapedText.toString(), item.name.escapedText.toString()); + } + } + }); + } + } else { + if (node.moduleReference && ts.isExternalModuleReference(node.moduleReference) && + node.moduleReference.expression && ts.isStringLiteral(node.moduleReference.expression)) { + filePath = node.moduleReference.expression.text; + defaultName = node.name.escapedText.toString(); + if (isEntryPage) { + asName.set(defaultName, defaultName); + } + } + } + + try { + const fileResolvePath: string = getFileFullPath(filePath, pagesDir); + if (fs.existsSync(fileResolvePath) && fs.statSync(fileResolvePath).isFile() && + !pathCollection.has(fileResolvePath)) { + let sourceFile: ts.SourceFile; + pathCollection.add(fileResolvePath); + if (IMPORT_FILE_ASTCACHE.has(fileResolvePath)) { + sourceFile = IMPORT_FILE_ASTCACHE.get(fileResolvePath); + } else { + sourceFile = generateSourceFileAST(fileResolvePath, filePath); + IMPORT_FILE_ASTCACHE[fileResolvePath] = sourceFile; + } + visitAllNode(sourceFile, sourceFile, defaultName, asName, path.dirname(fileResolvePath), log, + new Set(), new Set(), new Set(), new Map(), pathCollection, fileResolvePath, /\.d\.ets$/.test(fileResolvePath)); + } + } catch (e) { + // ignore + } +} + +function generateSourceFileAST(fileResolvePath: string, filePath: string): ts.SourceFile { + const originContent: string = fs.readFileSync(fileResolvePath, { encoding: 'utf-8' }); + const content: string = path.extname(fileResolvePath) === EXTNAME_ETS ? + preprocessNewExtend(preprocessExtend(processSystemApi(originContent))) : originContent; + return ts.createSourceFile(fileResolvePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); +} + +type structDecoratorResult = { + hasRecycle: boolean +}; + +const MODIFIER_LENGTH: number = 2; + +function visitAllNode(node: ts.Node, sourceFile: ts.SourceFile, defaultNameFromParent: string, + asNameFromParent: Map, pagesDir: string, log: LogInfo[], entryCollection: Set, + exportCollection: Set, defaultCollection: Set, asExportCollection: Map, + pathCollection: Set, fileResolvePath: string, isDETS: boolean): void { + if (isObservedClass(node)) { + collectSpecialFunctionNode(node as ts.ClassDeclaration, asNameFromParent, defaultNameFromParent, defaultCollection, + asExportCollection, observedClassCollection); + // @ts-ignore + observedClassCollection.add(node.name.getText()); + } + if (isCustomDialogClass(node)) { + collectSpecialFunctionNode(node as ts.StructDeclaration, asNameFromParent, defaultNameFromParent, defaultCollection, + asExportCollection, componentCollection.customDialogs); + // @ts-ignore + componentCollection.customDialogs.add(node.name.getText()); + } + if (ts.isEnumDeclaration(node) && node.name) { + enumCollection.add(node.name.getText()); + } + const structDecorator: structDecoratorResult = { hasRecycle: false }; + if (ts.isStructDeclaration(node) && ts.isIdentifier(node.name) && isCustomComponent(node, structDecorator)) { + addDependencies(node, defaultNameFromParent, asNameFromParent, isDETS, structDecorator); + isExportEntry(node, log, entryCollection, exportCollection, defaultCollection, fileResolvePath, sourceFile); + if (asExportCollection.has(node.name.getText())) { + componentCollection.customComponents.add(asExportCollection.get(node.name.getText())); + if (isDETS) { + storedFileInfo.getCurrentArkTsFile().compFromDETS.add(asExportCollection.get(node.name.getText())); + } + if (structDecorator.hasRecycle) { + storedFileInfo.getCurrentArkTsFile().recycleComponents.add(asExportCollection.get(node.name.getText())); + } + } + const modifiers: readonly ts.Modifier[] = + ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + if (modifiers && modifiers.length >= MODIFIER_LENGTH && modifiers[0] && + modifiers[0].kind === ts.SyntaxKind.ExportKeyword && modifiers[1] && + modifiers[1].kind === ts.SyntaxKind.DefaultKeyword) { + if (!defaultNameFromParent && hasCollection(node.name)) { + addDefaultExport(node, isDETS, structDecorator); + } else if (defaultNameFromParent && asNameFromParent.has(defaultNameFromParent)) { + componentCollection.customComponents.add(asNameFromParent.get(defaultNameFromParent)); + if (isDETS) { + storedFileInfo.getCurrentArkTsFile().compFromDETS.add(asNameFromParent.get(defaultNameFromParent)); + } + if (structDecorator.hasRecycle) { + storedFileInfo.getCurrentArkTsFile().recycleComponents.add(asNameFromParent.get(defaultNameFromParent)); + } + } + } + if (defaultCollection.has(node.name.getText())) { + componentCollection.customComponents.add(CUSTOM_COMPONENT_DEFAULT); + if (isDETS) { + storedFileInfo.getCurrentArkTsFile().compFromDETS.add(CUSTOM_COMPONENT_DEFAULT); + } + if (structDecorator.hasRecycle) { + storedFileInfo.getCurrentArkTsFile().recycleComponents.add(CUSTOM_COMPONENT_DEFAULT); + } + } + } + if (ts.isFunctionDeclaration(node) && hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { + collectSpecialFunctionNode(node, asNameFromParent, defaultNameFromParent, defaultCollection, + asExportCollection, CUSTOM_BUILDER_METHOD); + collectSpecialFunctionNode(node, asNameFromParent, defaultNameFromParent, defaultCollection, + asExportCollection, GLOBAL_CUSTOM_BUILDER_METHOD); + } + if (ts.isExportAssignment(node) && node.expression && ts.isIdentifier(node.expression) && + hasCollection(node.expression)) { + if (defaultNameFromParent) { + const propertiesName: string = node.expression.escapedText.toString(); + setDependencies(defaultNameFromParent, undefined, linkCollection.get(propertiesName), + propertyCollection.get(propertiesName), propCollection.get(propertiesName), + builderParamObjectCollection.get(propertiesName), stateCollection.get(propertiesName), + regularCollection.get(propertiesName), storagePropCollection.get(propertiesName), + storageLinkCollection.get(propertiesName), provideCollection.get(propertiesName), + consumeCollection.get(propertiesName), objectLinkCollection.get(propertiesName), + localStorageLinkCollection.get(propertiesName), localStoragePropCollection.get(propertiesName), + builderParamInitialization.get(propertiesName), propInitialization.get(propertiesName), isDETS, + structDecorator); + } + addDefaultExport(node, isDETS, structDecorator); + } + if (ts.isExportAssignment(node) && node.expression && ts.isIdentifier(node.expression)) { + if (defaultNameFromParent) { + asNameFromParent.set(node.expression.getText(), asNameFromParent.get(defaultNameFromParent)); + } + defaultCollection.add(node.expression.getText()); + } + if (ts.isExportDeclaration(node) && node.exportClause && + ts.isNamedExports(node.exportClause) && node.exportClause.elements) { + node.exportClause.elements.forEach(item => { + if (process.env.watchMode === 'true') { + exportCollection.add((item.propertyName ? item.propertyName : item.name).escapedText.toString()); + } + if (item.name && ts.isIdentifier(item.name)) { + if (!item.propertyName) { + asExportCollection.set(item.name.escapedText.toString(), item.name.escapedText.toString()); + } else if (item.propertyName && ts.isIdentifier(item.propertyName)) { + validateModuleName(item.name, log, sourceFile, fileResolvePath); + if (hasCollection(item.propertyName)) { + let asExportName: string = item.name.escapedText.toString(); + const asExportPropertyName: string = item.propertyName.escapedText.toString(); + if (asNameFromParent.has(asExportName)) { + asExportName = asNameFromParent.get(asExportName); + } + setDependencies(asExportName, undefined, linkCollection.get(asExportPropertyName), + propertyCollection.get(asExportPropertyName), + propCollection.get(asExportPropertyName), + builderParamObjectCollection.get(asExportPropertyName), + stateCollection.get(asExportPropertyName), regularCollection.get(asExportPropertyName), + storagePropCollection.get(asExportPropertyName), storageLinkCollection.get(asExportPropertyName), + provideCollection.get(asExportPropertyName), consumeCollection.get(asExportPropertyName), + objectLinkCollection.get(asExportPropertyName), localStorageLinkCollection.get(asExportPropertyName), + localStoragePropCollection.get(asExportPropertyName), builderParamInitialization.get(asExportPropertyName), + propInitialization.get(asExportPropertyName), isDETS, structDecorator); + } + asExportCollection.set(item.propertyName.escapedText.toString(), item.name.escapedText.toString()); + } + } + if (item.name && ts.isIdentifier(item.name) && asNameFromParent.has(item.name.escapedText.toString()) && + item.propertyName && ts.isIdentifier(item.propertyName)) { + asNameFromParent.set(item.propertyName.escapedText.toString(), + asNameFromParent.get(item.name.escapedText.toString())); + } + }); + } + if (ts.isExportDeclaration(node) && node.moduleSpecifier && + ts.isStringLiteral(node.moduleSpecifier)) { + if (process.env.watchMode === 'true' && node.exportClause && ts.isNamedExports(node.exportClause) && + node.exportClause.elements) { + node.exportClause.elements.forEach(item => { + exportCollection.add((item.propertyName ? item.propertyName : item.name).escapedText.toString()); + if (item.propertyName && ts.isIdentifier(item.propertyName) && item.name && + ts.isIdentifier(item.name) && asNameFromParent.has(item.name.escapedText.toString())) { + asNameFromParent.set(item.propertyName.escapedText.toString(), + asNameFromParent.get(item.name.escapedText.toString())); + defaultCollection.add(item.name.escapedText.toString()); + } + }); + } + processImport(node, pagesDir, log, asNameFromParent, true, new Set(pathCollection)); + } + if (ts.isImportDeclaration(node)) { + if (node.importClause && node.importClause.name && ts.isIdentifier(node.importClause.name) && + asNameFromParent.has(node.importClause.name.getText())) { + processImport(node, pagesDir, log, asNameFromParent, false, new Set(pathCollection)); + } else if (node.importClause && node.importClause.namedBindings && + ts.isNamedImports(node.importClause.namedBindings) && node.importClause.namedBindings.elements) { + let nested: boolean = false; + node.importClause.namedBindings.elements.forEach(item => { + if (item.name && ts.isIdentifier(item.name) && asNameFromParent.has(item.name.escapedText.toString())) { + nested = true; + if (item.propertyName && ts.isIdentifier(item.propertyName)) { + asNameFromParent.set(item.propertyName.escapedText.toString(), + asNameFromParent.get(item.name.escapedText.toString())); + } + } + }); + if (nested) { + processImport(node, pagesDir, log, asNameFromParent, false, new Set(pathCollection)); + } + } + } + node.getChildren().reverse().forEach((item: ts.Node) => visitAllNode(item, sourceFile, + defaultNameFromParent, asNameFromParent, pagesDir, log, entryCollection, exportCollection, + defaultCollection, asExportCollection, pathCollection, fileResolvePath, isDETS)); +} + +function collectSpecialFunctionNode(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.StructDeclaration, + asNameFromParent: Map, defaultNameFromParent: string, defaultCollection: Set, + asExportCollection: Map, collection: Set): void { + const name: string = node.name.getText(); + const modifiers: readonly ts.Modifier[] = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + if (asNameFromParent.has(name)) { + collection.add(asNameFromParent.get(name)); + } else if (modifiers && modifiers.length >= 1 && modifiers[0] && + modifiers[0].kind === ts.SyntaxKind.ExportKeyword) { + if (modifiers.length === 1) { + collection.add(name); + } else if (modifiers.length >= MODIFIER_LENGTH && modifiers[1] && modifiers[1].kind === + ts.SyntaxKind.DefaultKeyword) { + collection.add(CUSTOM_COMPONENT_DEFAULT); + if (defaultNameFromParent && asNameFromParent.has(defaultNameFromParent)) { + collection.add(asNameFromParent.get(defaultNameFromParent)); + } + } + } else if (defaultCollection.has(name)) { + collection.add(CUSTOM_COMPONENT_DEFAULT); + } else if (asExportCollection.has(name)) { + collection.add(asExportCollection.get(name)); + } +} + +function isExportEntry(node: ts.StructDeclaration, log: LogInfo[], entryCollection: Set, + exportCollection: Set, defaultCollection: Set, fileResolvePath: string, + sourceFile: ts.SourceFile): void { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (process.env.watchMode === 'true' && node && decorators) { + let existExport: boolean = false; + let existEntry: boolean = false; + const modifiers: readonly ts.Modifier[] = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + if (modifiers) { + for (let i = 0; i < modifiers.length; i++) { + if (modifiers[i].kind === ts.SyntaxKind.ExportKeyword) { + existExport = true; + break; + } + } + } + for (let i = 0; i < decorators.length; i++) { + if (decorators[i].getText() === COMPONENT_DECORATOR_ENTRY) { + entryCollection.add(node.name.escapedText.toString()); + existEntry = true; + break; + } + } + } +} + +function addDependencies(node: ts.StructDeclaration, defaultNameFromParent: string, + asNameFromParent: Map, isDETS: boolean, structDecorator: structDecoratorResult): void { + const componentName: string = node.name.getText(); + const ComponentSet: IComponentSet = getComponentSet(node, false); + const modifiers: readonly ts.Modifier[] = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + if (defaultNameFromParent && modifiers && modifiers.length >= MODIFIER_LENGTH && modifiers[0] && + modifiers[1] && modifiers[0].kind === ts.SyntaxKind.ExportKeyword && + modifiers[1].kind === ts.SyntaxKind.DefaultKeyword) { + setDependencies(defaultNameFromParent, undefined, ComponentSet.links, ComponentSet.properties, + ComponentSet.props, ComponentSet.builderParams, ComponentSet.states, ComponentSet.regulars, + ComponentSet.storageProps, ComponentSet.storageLinks, ComponentSet.provides, + ComponentSet.consumes, ComponentSet.objectLinks, ComponentSet.localStorageLink, + ComponentSet.localStorageProp, ComponentSet.builderParamData, ComponentSet.propData, isDETS, + structDecorator); + } else if (asNameFromParent.has(componentName)) { + setDependencies(asNameFromParent.get(componentName), undefined, ComponentSet.links, ComponentSet.properties, + ComponentSet.props, ComponentSet.builderParams, ComponentSet.states, ComponentSet.regulars, + ComponentSet.storageProps, ComponentSet.storageLinks, ComponentSet.provides, + ComponentSet.consumes, ComponentSet.objectLinks, ComponentSet.localStorageLink, + ComponentSet.localStorageProp, ComponentSet.builderParamData, ComponentSet.propData, isDETS, + structDecorator); + } else { + setDependencies(componentName, undefined, ComponentSet.links, ComponentSet.properties, ComponentSet.props, + ComponentSet.builderParams, ComponentSet.states, ComponentSet.regulars, + ComponentSet.storageProps, ComponentSet.storageLinks, ComponentSet.provides, + ComponentSet.consumes, ComponentSet.objectLinks, ComponentSet.localStorageLink, + ComponentSet.localStorageProp, ComponentSet.builderParamData, ComponentSet.propData, isDETS, + structDecorator); + } +} + +function addDefaultExport(node: ts.StructDeclaration | ts.ExportAssignment, isDETS: boolean, + structDecorator: structDecoratorResult): void { + let name: string; + if (ts.isStructDeclaration(node) && node.name && ts.isIdentifier(node.name)) { + name = node.name.escapedText.toString(); + } else if (ts.isExportAssignment(node) && node.expression && ts.isIdentifier(node.expression)) { + name = node.expression.escapedText.toString(); + } else { + return; + } + setDependencies(CUSTOM_COMPONENT_DEFAULT, undefined, + linkCollection.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...linkCollection.get(CUSTOM_COMPONENT_DEFAULT), ...linkCollection.get(name)]) : + linkCollection.get(name), + propertyCollection.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...propertyCollection.get(CUSTOM_COMPONENT_DEFAULT), + ...propertyCollection.get(name)]) : propertyCollection.get(name), + propCollection.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...propCollection.get(CUSTOM_COMPONENT_DEFAULT), ...propCollection.get(name)]) : + propCollection.get(name), + builderParamObjectCollection.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...builderParamObjectCollection.get(CUSTOM_COMPONENT_DEFAULT), + ...builderParamObjectCollection.get(name)]) : builderParamObjectCollection.get(name), + stateCollection.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...stateCollection.get(CUSTOM_COMPONENT_DEFAULT), + ...stateCollection.get(name)]) : stateCollection.get(name), + regularCollection.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...regularCollection.get(CUSTOM_COMPONENT_DEFAULT), + ...regularCollection.get(name)]) : regularCollection.get(name), + storagePropCollection.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...storagePropCollection.get(CUSTOM_COMPONENT_DEFAULT), + ...storagePropCollection.get(name)]) : storagePropCollection.get(name), + storageLinkCollection.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...storageLinkCollection.get(CUSTOM_COMPONENT_DEFAULT), + ...storageLinkCollection.get(name)]) : storageLinkCollection.get(name), + provideCollection.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...provideCollection.get(CUSTOM_COMPONENT_DEFAULT), + ...provideCollection.get(name)]) : provideCollection.get(name), + consumeCollection.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...consumeCollection.get(CUSTOM_COMPONENT_DEFAULT), + ...consumeCollection.get(name)]) : consumeCollection.get(name), + objectLinkCollection.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...objectLinkCollection.get(CUSTOM_COMPONENT_DEFAULT), + ...objectLinkCollection.get(name)]) : objectLinkCollection.get(name), + getNewLocalStorageMap(localStorageLinkCollection, name), + getNewLocalStorageMap(localStoragePropCollection, name), + builderParamInitialization.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...builderParamInitialization.get(CUSTOM_COMPONENT_DEFAULT), + ...builderParamInitialization.get(name)]) : builderParamInitialization.get(name), + propInitialization.has(CUSTOM_COMPONENT_DEFAULT) ? + new Set([...propInitialization.get(CUSTOM_COMPONENT_DEFAULT), + ...propInitialization.get(name)]) : propInitialization.get(name), isDETS, + structDecorator + ); +} + +function getNewLocalStorageMap(collection: Map>>, name: string) + : Map> { + let localStorageLinkMap: Map> = new Map(); + if (collection.has(CUSTOM_COMPONENT_DEFAULT)) { + const tempSet: Set = new Set(); + if (collection.get(CUSTOM_COMPONENT_DEFAULT)) { + for (const key of collection.get(CUSTOM_COMPONENT_DEFAULT).keys()) { + tempSet.add(key); + } + } + if (collection.get(name)) { + for (const key of collection.get(name).keys()) { + tempSet.add(key); + } + } + localStorageLinkMap.set(name, tempSet); + } else { + localStorageLinkMap = collection.get(name); + } + return localStorageLinkMap; +} + +function setDependencies(component: string, asComponentName: string, linkArray: Set, propertyArray: Set, + propArray: Set, builderParamArray: Set, stateArray: Set, + regularArray: Set, storagePropsArray: Set, storageLinksArray: Set, + providesArray: Set, consumesArray: Set, objectLinksArray: Set, + localStorageLinkMap: Map>, localStoragePropMap: Map>, + builderParamData: Set, propData: Set, isDETS: boolean, + structDecorator: structDecoratorResult): void { + if (asComponentName) { + linkCollection.set(asComponentName, linkArray); + storedFileInfo.overallLinkCollection.set(asComponentName, linkArray); + } else if (!asComponentName && component) { + linkCollection.set(component, linkArray); + if (projectConfig.compileMode === 'esmodule' && process.env.compileTool === 'rollup') { + storedFileInfo.overallLinkCollection.set(component, linkArray); + } + } + propertyCollection.set(component, propertyArray); + if (!propCollection.get(component)) { + propCollection.set(component, propArray); + } + if (asComponentName) { + storedFileInfo.overallBuilderParamCollection.set(asComponentName, builderParamArray); + } else if (!asComponentName && component && projectConfig.compileMode === 'esmodule' && + process.env.compileTool === 'rollup') { + storedFileInfo.overallBuilderParamCollection.set(component, builderParamArray); + } + builderParamObjectCollection.set(component, builderParamArray); + componentCollection.customComponents.add(component); + if (isDETS) { + storedFileInfo.getCurrentArkTsFile().compFromDETS.add(component); + } + if (structDecorator.hasRecycle) { + storedFileInfo.getCurrentArkTsFile().recycleComponents.add(component); + } + stateCollection.set(component, stateArray); + regularCollection.set(component, regularArray); + storagePropCollection.set(component, storagePropsArray); + storageLinkCollection.set(component, storageLinksArray); + provideCollection.set(component, providesArray); + consumeCollection.set(component, consumesArray); + if (asComponentName) { + storedFileInfo.overallObjectLinkCollection.set(asComponentName, objectLinksArray); + } else if (!asComponentName && component && projectConfig.compileMode === 'esmodule' && + process.env.compileTool === 'rollup') { + storedFileInfo.overallObjectLinkCollection.set(component, objectLinksArray); + } + objectLinkCollection.set(component, objectLinksArray); + localStorageLinkCollection.set(component, localStorageLinkMap); + localStoragePropCollection.set(component, localStoragePropMap); + if (!builderParamInitialization.get(component)) { + builderParamInitialization.set(component, builderParamData); + } + if (!propInitialization.get(component)) { + propInitialization.set(component, propData); + } +} + +function hasCollection(node: ts.Identifier): boolean { + const name: string = node.escapedText.toString(); + return linkCollection.has(name) || + propCollection.has(name) || + propertyCollection.has(name) || + builderParamObjectCollection.has(name) || + stateCollection.has(name) || + regularCollection.has(name) || + storagePropCollection.has(name) || + storageLinkCollection.has(name) || + provideCollection.has(name) || + consumeCollection.has(name) || + objectLinkCollection.has(name) || + localStorageLinkCollection.has(name) || + localStoragePropCollection.has(name); +} + +function isModule(filePath: string): boolean { + return !/^(\.|\.\.)?\//.test(filePath) || filePath.indexOf(projectConfig.packageDir) > -1; +} + +function isCustomComponent(node: ts.StructDeclaration, structDecorator: structDecoratorResult): boolean { + let isComponent: boolean = false; + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (decorators && decorators.length) { + for (let i = 0; i < decorators.length; ++i) { + const decoratorName: ts.Expression = decorators[i].expression; + if (ts.isIdentifier(decoratorName) || ts.isCallExpression(decoratorName)) { + let name: string = ''; + if (ts.isCallExpression(decoratorName) && ts.isIdentifier(decoratorName.expression)) { + name = decoratorName.expression.escapedText.toString(); + } else if (ts.isIdentifier(decoratorName)) { + name = decoratorName.escapedText.toString(); + } + if (CUSTOM_DECORATOR_NAME.has(name)) { + isComponent = true; + } + if (name === DECORATOR_REUSEABLE) { + structDecorator.hasRecycle = true; + } + } + } + } + return isComponent; +} + +let packageJsonEntry: string = ''; + +function isPackageJsonEntry(filePath: string): boolean { + const packageJsonPath: string = path.join(filePath, projectConfig.packageJson); + if (fs.existsSync(packageJsonPath)) { + let entryTypes: string; + let entryMain: string; + try { + const packageJson: Object = + (projectConfig.packageManagerType === 'npm' ? JSON : JSON5).parse(fs.readFileSync(packageJsonPath).toString()); + entryTypes = packageJson.types; + entryMain = packageJson.main; + } catch (e) { + return false; + } + if (entryExist(filePath, entryTypes)) { + packageJsonEntry = path.resolve(filePath, entryTypes); + return true; + } else if (entryExist(filePath, entryMain)) { + packageJsonEntry = path.resolve(filePath, entryMain); + return true; + } + } + return false; +} + +function entryExist(filePath: string, entry: string): boolean { + return typeof entry === 'string' && fs.existsSync(path.resolve(filePath, entry)) && + fs.statSync(path.resolve(filePath, entry)).isFile(); +} + +function getModuleFilePath(filePath: string): string { + if (filePath && path.extname(filePath) !== EXTNAME_ETS && isModule(filePath)) { + filePath += EXTNAME_ETS; + } + return filePath; +} + +function getFileResolvePath(fileResolvePath: string, pagesDir: string, filePath: string, + projectPath: string): string { + const moduleFilePath: string = getModuleFilePath(filePath); + const defaultModule: string = path.join(projectPath, moduleFilePath); + if (fs.existsSync(defaultModule)) { + return defaultModule; + } + let entryModule: string; + let etsModule: string; + if (new RegExp(`^@(${sdkConfigPrefix})\\.`).test(filePath.trim())) { + for (let i = 0; i < sdkConfigs.length; i++) { + const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(sdkConfigs[i].apiPath, filePath, ['.d.ts', '.d.ets']); + const systemModule: string = resolveModuleInfo.modulePath; + if (fs.existsSync(systemModule)) { + return systemModule; + } + } + } + if (!projectConfig.aceModuleJsonPath) { //??? + entryModule = path.join(projectPath, '../../../../../', moduleFilePath); + etsModule = path.join(projectPath, '../../', moduleFilePath); + } else { + entryModule = path.join(projectPath, '../../../../', moduleFilePath); + etsModule = path.join(projectPath, '../', moduleFilePath); + } + if (fs.existsSync(entryModule)) { + return entryModule; + } + if (fs.existsSync(etsModule)) { + return etsModule; + } + let curPageDir: string = pagesDir; + while (!fs.existsSync(fileResolvePath)) { + if (filePath.indexOf(projectConfig.packageDir) > -1 && /^(\.|\.\.)\//.test(filePath)) { + fileResolvePath = path.join(curPageDir, filePath); + } else { + fileResolvePath = path.join(curPageDir, projectConfig.packageDir, filePath); + } + if (fs.existsSync(fileResolvePath + EXTNAME_ETS)) { + fileResolvePath = fileResolvePath + EXTNAME_ETS; + } else if (isPackageJsonEntry(fileResolvePath)) { + fileResolvePath = packageJsonEntry; + if (fs.statSync(fileResolvePath).isDirectory()) { + if (fs.existsSync(path.join(fileResolvePath, INDEX_ETS))) { + fileResolvePath = path.join(fileResolvePath, INDEX_ETS); + } else if (fs.existsSync(path.join(fileResolvePath, INDEX_TS))) { + fileResolvePath = path.join(fileResolvePath, INDEX_TS); + } + } + } else if (fs.existsSync(path.join(fileResolvePath, INDEX_ETS))) { + fileResolvePath = path.join(fileResolvePath, INDEX_ETS); + } else if (fs.existsSync(path.join(fileResolvePath, INDEX_TS))) { + fileResolvePath = path.join(fileResolvePath, INDEX_TS); + } + if (curPageDir === path.parse(curPageDir).root) { + break; + } + curPageDir = path.dirname(curPageDir); + } + return fileResolvePath; +} + +function getFileFullPath(filePath: string, pagesDir: string): string { + if (filePath && path.extname(filePath) !== EXTNAME_ETS && path.extname(filePath) !== EXTNAME_TS && + !isModule(filePath)) { + const dirIndexEtsPath: string = path.resolve(path.resolve(pagesDir, filePath), INDEX_ETS); + const dirIndexTsPath: string = path.resolve(path.resolve(pagesDir, filePath), INDEX_TS); + if (/^(\.|\.\.)\//.test(filePath) && !fs.existsSync(path.resolve(pagesDir, filePath + EXTNAME_ETS)) && + fs.existsSync(dirIndexEtsPath)) { + filePath = dirIndexEtsPath; + } else if (/^(\.|\.\.)\//.test(filePath) && !fs.existsSync(path.resolve(pagesDir, filePath + EXTNAME_TS)) && + fs.existsSync(dirIndexTsPath)) { + filePath = dirIndexTsPath; + } else { + filePath += getExtensionIfUnfullySpecifiedFilepath(path.resolve(pagesDir, filePath)); + } + } + + let fileResolvePath: string; + if (/^(\.|\.\.)\//.test(filePath) && filePath.indexOf(projectConfig.packageDir) < 0) { + fileResolvePath = path.resolve(pagesDir, filePath); + } else if (/^\//.test(filePath) && filePath.indexOf(projectConfig.packageDir) < 0 || + fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { + fileResolvePath = filePath; + } else { + fileResolvePath = getFileResolvePath(fileResolvePath, pagesDir, filePath, projectConfig.projectPath); + } + + return fileResolvePath; +} + +function validateModuleName(moduleNode: ts.Identifier, log: LogInfo[], sourceFile?: ts.SourceFile, + fileResolvePath?: string): void { + const moduleName: string = moduleNode.escapedText.toString(); + if (INNER_COMPONENT_NAMES.has(moduleName)) { + const error: LogInfo = { + type: LogType.ERROR, + message: `The module name '${moduleName}' can not be the same as the inner component name.`, + pos: moduleNode.getStart(), + code: '10905235' + }; + if (sourceFile && fileResolvePath) { + const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(moduleNode.getStart()); + const line: number = posOfNode.line + 1; + const column: number = posOfNode.character + 1; + Object.assign(error, { + fileName: fileResolvePath, + line: line, + column: column + }); + } + log.push(error); + } +} + +interface PageInfo { + pageFile: string; + setChildOnce: boolean; +} + +export function processImportModule(node: ts.ImportDeclaration, pageFile: string, log: LogInfo[]): void { + let importSymbol: ts.Symbol; + let realSymbol: ts.Symbol; + let originNode: ts.Node; + const pageInfo: PageInfo = { pageFile: pageFile, setChildOnce: false }; + validateModuleSpecifier(node.moduleSpecifier, log); + + // import xxx from 'xxx' + if (node.importClause && node.importClause.name && ts.isIdentifier(node.importClause.name)) { + getDefinedNode(importSymbol, realSymbol, originNode, node.importClause.name, pageInfo); + } + + // import {xxx} from 'xxx' + if (node.importClause && node.importClause.namedBindings && + ts.isNamedImports(node.importClause.namedBindings) && + node.importClause.namedBindings.elements) { + node.importClause.namedBindings.elements.forEach((importSpecifier: ts.ImportSpecifier) => { + if (ts.isImportSpecifier(importSpecifier) && importSpecifier.name && ts.isIdentifier(importSpecifier.name)) { + getDefinedNode(importSymbol, realSymbol, originNode, importSpecifier.name, pageInfo); + } + }); + } + + // import * as xxx from 'xxx' + if (node.importClause && node.importClause.namedBindings && + ts.isNamespaceImport(node.importClause.namedBindings) && node.importClause.namedBindings.name && + ts.isIdentifier(node.importClause.namedBindings.name)) { + storedFileInfo.isAsPageImport = true; + getDefinedNode(importSymbol, realSymbol, originNode, node.importClause.namedBindings.name, pageInfo); + } +} + +function getDefinedNode(importSymbol: ts.Symbol, realSymbol: ts.Symbol, originNode: ts.Node, + usedNode: ts.Identifier, pageInfo: PageInfo): void { + importSymbol = globalProgram.checker.getSymbolAtLocation(usedNode); + if (importSymbol) { + realSymbol = globalProgram.checker.getAliasedSymbol(importSymbol); + } else { + realSymbol = null; + } + if (realSymbol && realSymbol.declarations) { + originNode = realSymbol.declarations[0]; + } else { + originNode = null; + } + if (originNode) { + if (ts.isSourceFile(originNode) && realSymbol.escapedName) { + const escapedName: string = realSymbol.escapedName.toString().replace(/^("|')/, '').replace(/("|')$/, ''); + if (fs.existsSync(escapedName + '.ets') || fs.existsSync(escapedName + '.ts') && + realSymbol.exports && realSymbol.exports instanceof Map) { + getIntegrationNodeInfo(originNode, usedNode, realSymbol.exports, pageInfo); + return; + } + } + processImportNode(originNode, usedNode, false, null, pageInfo); + } +} + +function getIntegrationNodeInfo(originNode: ts.Node, usedNode: ts.Identifier, exportsMap: ts.SymbolTable, + pageInfo: PageInfo): void { + for (const usedSymbol of exportsMap) { + try { + originNode = globalProgram.checker.getAliasedSymbol(usedSymbol[1]).declarations[0]; + } catch (e) { + if (usedSymbol[1] && usedSymbol[1].declarations) { + for (let i = 0; i < usedSymbol[1].declarations.length; i++) { + originNode = usedSymbol[1].declarations[i]; + exportAllManage(originNode, usedNode, pageInfo); + } + } + } + processImportNode(originNode, usedNode, true, usedSymbol[0], pageInfo); + } +} + +// export * from 'xxx'; +function exportAllManage(originNode: ts.Node, usedNode: ts.Identifier, pageInfo: PageInfo): void { + let exportOriginNode: ts.Node; + if (!originNode.exportClause && originNode.moduleSpecifier && ts.isStringLiteral(originNode.moduleSpecifier)) { + const exportSymbol: ts.Symbol = globalProgram.checker.getSymbolAtLocation(originNode.moduleSpecifier); + if (exportSymbol && exportSymbol.declarations) { + exportOriginNode = exportSymbol.declarations[0]; + } else { + exportOriginNode = null; + } + if (exportOriginNode) { + if (ts.isSourceFile(exportOriginNode) && exportSymbol.escapedName) { + const escapedName: string = exportSymbol.escapedName.toString().replace(/^("|')/, '').replace(/("|')$/, ''); + if (fs.existsSync(escapedName + '.ets') || fs.existsSync(escapedName + '.ts') && + exportSymbol.exports && exportSymbol.exports instanceof Map) { + getIntegrationNodeInfo(originNode, usedNode, exportSymbol.exports, pageInfo); + return; + } + } + } + } +} + +function processImportNode(originNode: ts.Node, usedNode: ts.Identifier, importIntegration: boolean, + usedPropName: string, pageInfo: PageInfo): void { + const structDecorator: structDecoratorResult = { hasRecycle: false }; + let name: string; + let asComponentName: string; + if (importIntegration) { + if (storedFileInfo.isAsPageImport) { + asComponentName = usedNode.escapedText.toString() + '.' + usedPropName; + } + name = usedPropName; + } else { + name = usedNode.escapedText.toString(); + } + let needCollection: boolean = true; + const originFile: string = originNode.getSourceFile() ? originNode.getSourceFile().fileName : undefined; + if (ts.isStructDeclaration(originNode) && ts.isIdentifier(originNode.name)) { + parseComponentInImportNode(originNode, name, asComponentName, structDecorator, originFile); + } else if (isObservedClass(originNode)) { + observedClassCollection.add(name); + } else if (ts.isFunctionDeclaration(originNode) && hasDecorator(originNode, COMPONENT_BUILDER_DECORATOR)) { + CUSTOM_BUILDER_METHOD.add(name); + GLOBAL_CUSTOM_BUILDER_METHOD.add(name); + } else if (ts.isEnumDeclaration(originNode) && originNode.name) { + enumCollection.add(name); + } else { + needCollection = false; + } + if (needCollection && pageInfo.pageFile && !pageInfo.setChildOnce && originFile) { + const childFile: string = path.resolve(getRealPath(originFile) || originFile); + storedFileInfo.transformCacheFiles[pageInfo.pageFile].children.push({ + fileName: childFile, + mtimeMs: fs.existsSync(childFile) ? fs.statSync(childFile).mtimeMs : 0 + }); + pageInfo.setChildOnce = true; + } +} + +function getRealPath(filePath: string): string { + try { + const newPath: string = fs.realpathSync.native(filePath); + return newPath; + } catch (err) { + return undefined; + } +} + +function setComponentCollectionInfo(name: string, componentSet: IComponentSet, isDETS: boolean, + structDecorator: structDecoratorResult, asComponentName: string): void { + setDependencies(name, asComponentName, componentSet.links, componentSet.properties, + componentSet.props, componentSet.builderParams, componentSet.states, componentSet.regulars, + componentSet.storageProps, componentSet.storageLinks, componentSet.provides, + componentSet.consumes, componentSet.objectLinks, componentSet.localStorageLink, + componentSet.localStorageProp, componentSet.builderParamData, componentSet.propData, isDETS, + structDecorator); + regularInitialization.set(name, componentSet.regularInit); + stateInitialization.set(name, componentSet.stateInit); + provideInitialization.set(name, componentSet.provideInit); + privateCollection.set(name, componentSet.privateCollection); + regularStaticCollection.set(name, componentSet.regularStaticCollection); + if (asComponentName) { + const asComponentNameStructInfo: StructInfo = + processStructComponentV2.getOrCreateStructInfo(asComponentName); + asComponentNameStructInfo.updatePropsDecoratorsV1.push( + ...componentSet.states, ...componentSet.props, + ...componentSet.provides, ...componentSet.objectLinks + ); + asComponentNameStructInfo.linkDecoratorsV1.push(...componentSet.links); + return; + } + const nameStructInfo: StructInfo = processStructComponentV2.getOrCreateStructInfo(name); + nameStructInfo.updatePropsDecoratorsV1.push( + ...componentSet.states, ...componentSet.props, + ...componentSet.provides, ...componentSet.objectLinks + ); + nameStructInfo.linkDecoratorsV1.push(...componentSet.links); +} + +function parseComponentInImportNode(originNode: ts.StructDeclaration, name: string, + asComponentName: string, structDecorator: structDecoratorResult, originFile: string): void { + componentCollection.customComponents.add(name); + const structInfo: StructInfo = asComponentName ? + processStructComponentV2.getOrCreateStructInfo(asComponentName) : + processStructComponentV2.getOrCreateStructInfo(name); + if (isComponentV2(originNode)) { + parseComponentV2InImportNode(originNode, name, originFile, structInfo); + return; + } + if (isCustomDialogClass(originNode)) { + structInfo.isCustomDialog = true; + componentCollection.customDialogs.add(name); + } + if (isCustomComponent(originNode, structDecorator)) { + structInfo.isComponentV1 = true; + let isDETS: boolean = false; + const componentSet: IComponentSet = getComponentSet(originNode, false); + while (originNode) { + if (ts.isSourceFile(originNode) && /\.d\.ets$/.test(originNode.fileName)) { + isDETS = true; + } + originNode = originNode.parent; + } + setComponentCollectionInfo(name, componentSet, isDETS, structDecorator, asComponentName); + } +} + +function parseComponentV2InImportNode(node: ts.StructDeclaration, name: string, originFile: string, + structInfo: StructInfo): void { + structInfo.isComponentV2 = true; + const isDETS: boolean = originFile && /\.d\.ets$/.test(originFile); + if (isDETS) { + storedFileInfo.getCurrentArkTsFile().compFromDETS.add(name); + } + if (isReusableV2(node)) { + storedFileInfo.getCurrentArkTsFile().reuseComponentsV2.add(name); + } + processStructComponentV2.parseComponentProperty(node, structInfo, null, null); +} + +function isComponentV2(node: ts.StructDeclaration): boolean { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + return decorators.some((item: ts.Decorator) => { + const name: string = item.getText().replace(/\([^\(\)]*\)/, '').replace('@', '').trim(); + return name === 'ComponentV2'; + }); +} + +function isReusableV2(node: ts.StructDeclaration): boolean { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + return decorators.some((item: ts.Decorator) => { + const name: string = item.getText().replace(/\([^\(\)]*\)/, '').replace('@', '').trim(); + return name === DECORATOR_REUSABLE_V2; + }); +} diff --git a/compiler/src/interop/src/process_interop_ui.ts b/compiler/src/interop/src/process_interop_ui.ts new file mode 100644 index 0000000000000000000000000000000000000000..715de7fba5ffc9622337157244811a8813a1a23f --- /dev/null +++ b/compiler/src/interop/src/process_interop_ui.ts @@ -0,0 +1,324 @@ +/* + * 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 ts from 'typescript'; + +import { EXTNAME_D_ETS } from './pre_define'; + +import { + whiteList, + decoratorsWhiteList, +} from './import_whiteList'; + +const fs = require('fs'); +const path = require('path'); + +function getDeclgenFiles(dir: string, filePaths: string[] = []) { + const files = fs.readdirSync(dir); + + files.forEach(file => { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + getDeclgenFiles(filePath, filePaths); + } else if (stat.isFile() && file.endsWith(EXTNAME_D_ETS)) { + filePaths.push(filePath); + } + }); + + return filePaths; +} + +export function isStructDeclaration(node: ts.Node): boolean { + return ts.isStructDeclaration(node); +} + +function defaultCompilerOptions(): ts.CompilerOptions { + return { + target: ts.ScriptTarget.Latest, + module: ts.ModuleKind.CommonJS, + allowJs: true, + checkJs: true, + declaration: true, + emitDeclarationOnly: true, + noEmit: false + }; +} + +function getSourceFiles(program: ts.Program, filePaths: string[]): ts.SourceFile[] { + const sourceFiles: ts.SourceFile[] = []; + + filePaths.forEach(filePath => { + sourceFiles.push(program.getSourceFile(filePath)); + }); + + return sourceFiles; +} + +class HandleUIImports { + private context: ts.TransformationContext; + private typeChecker: ts.TypeChecker; + + private readonly outPath: string; + + private importedInterfaces: Set = new Set(); + private interfacesNeedToImport: Set = new Set(); + private printer = ts.createPrinter(); + private insertPosition = 0; + + private readonly trueSymbolAtLocationCache = new Map(); + + constructor(program: ts.Program, context: ts.TransformationContext, outPath: string) { + this.context = context; + this.typeChecker = program.getTypeChecker(); + this.outPath = outPath; + } + + public createCustomTransformer(sourceFile: ts.SourceFile) { + this.extractImportedNames(sourceFile); + + const statements = sourceFile.statements; + for (let i = 0; i < statements.length; ++i) { + const statement = statements[i]; + if (!ts.isJSDoc(statement) && !(ts.isExpressionStatement(statement) && + ts.isStringLiteral(statement.expression))) { + this.insertPosition = i; + break; + } + } + + return ts.visitNode(sourceFile, this.visitNode.bind(this)) + } + + private visitNode(node: ts.Node): ts.Node | undefined { + // delete constructor + if (node.parent && isStructDeclaration(node.parent) && ts.isConstructorDeclaration(node)) { + return; + } + + // skip to collect origin import from 1.2 + if (ts.isImportDeclaration(node)) { + const moduleSpecifier = node.moduleSpecifier; + if (ts.isStringLiteral(moduleSpecifier)) { + const modulePath = moduleSpecifier.text; + if (['@ohos.arkui.stateManagement', '@ohos.arkui.component'].includes(modulePath)) { + return node; + } + } + } + + this.handleImportBuilder(node); + const result = ts.visitEachChild(node, this.visitNode.bind(this), this.context); + + if (ts.isIdentifier(result) && !this.shouldSkipIdentifier(result)) { + this.interfacesNeedToImport.add(result.text); + } else if (ts.isSourceFile(result)) { + this.AddUIImports(result); + } + + return result; + } + + private handleImportBuilder(node: ts.Node): void { + ts.getAllDecorators(node)?.forEach(element => { + if (element?.getText() === '@Builder') { + this.interfacesNeedToImport.add('Builder'); + return; + } + }); + } + + private AddUIImports(node: ts.SourceFile): void { + const compImportSpecifiers: ts.ImportSpecifier[] = []; + const stateImportSpecifiers: ts.ImportSpecifier[] = []; + + this.interfacesNeedToImport.forEach((interfaceName) => { + if (this.importedInterfaces.has(interfaceName)) { + return; + } + const identifier = ts.factory.createIdentifier(interfaceName); + if (decoratorsWhiteList.includes(interfaceName)) { + stateImportSpecifiers.push(ts.factory.createImportSpecifier(false, undefined, identifier)); + } else { + compImportSpecifiers.push(ts.factory.createImportSpecifier(false, undefined, identifier)); + } + }); + + if (compImportSpecifiers.length + stateImportSpecifiers.length > 0) { + const newStatements = [...node.statements]; + + if (compImportSpecifiers.length) { + const moduleName = '@ohos.arkui.component'; + const compImportDeclaration = ts.factory.createImportDeclaration( + undefined, + ts.factory.createImportClause(false, + undefined, + ts.factory.createNamedImports( + compImportSpecifiers + ) + ), + ts.factory.createStringLiteral(moduleName, true), + undefined + ); + newStatements.splice(this.insertPosition, 0, compImportDeclaration); + } + + if (stateImportSpecifiers.length) { + const moduleName = '@ohos.arkui.stateManagement'; + const stateImportDeclaration = ts.factory.createImportDeclaration( + undefined, + ts.factory.createImportClause(false, + undefined, + ts.factory.createNamedImports( + stateImportSpecifiers + ) + ), + ts.factory.createStringLiteral(moduleName, true), + undefined + ); + newStatements.splice(this.insertPosition, 0, stateImportDeclaration); + } + + const updatedStatements = ts.factory.createNodeArray(newStatements); + const updatedSourceFile = ts.factory.updateSourceFile(node, + updatedStatements, + node.isDeclarationFile, + node.referencedFiles, + node.typeReferenceDirectives, + node.hasNoDefaultLib, + node.libReferenceDirectives + ); + + const updatedCode = this.printer.printFile(updatedSourceFile); + if (this.outPath) { + fs.writeFileSync(this.outPath, updatedCode); + } else { + fs.writeFileSync(updatedSourceFile.fileName, updatedCode); + } + } + } + + private getDeclarationNode(node: ts.Node): ts.Declaration | undefined { + const symbol = this.trueSymbolAtLocation(node); + return HandleUIImports.getDeclaration(symbol); + } + + static getDeclaration(tsSymbol: ts.Symbol | undefined): ts.Declaration | undefined { + if (tsSymbol?.declarations && tsSymbol.declarations.length > 0) { + return tsSymbol.declarations[0]; + } + + return undefined; + } + + private followIfAliased(symbol: ts.Symbol): ts.Symbol { + if ((symbol.getFlags() & ts.SymbolFlags.Alias) !== 0) { + return this.typeChecker.getAliasedSymbol(symbol); + } + + return symbol; + } + + private trueSymbolAtLocation(node: ts.Node): ts.Symbol | undefined { + const cache = this.trueSymbolAtLocationCache; + const val = cache.get(node); + + if (val !== undefined) { + return val !== null ? val : undefined; + } + + let symbol = this.typeChecker.getSymbolAtLocation(node); + + if (symbol === undefined) { + cache.set(node, null); + return undefined; + } + + symbol = this.followIfAliased(symbol); + cache.set(node, symbol); + + return symbol; + } + + private shouldSkipIdentifier(identifier: ts.Identifier): boolean { + const name = identifier.text; + const skippedList = new Set(['Extend', 'Styles']); + + if (skippedList.has(name)) { + return true; + } + + if (!whiteList.has(name)) { + return true; + } + + const symbol = this.typeChecker.getSymbolAtLocation(identifier); + if (symbol) { + const decl = this.getDeclarationNode(identifier); + if (decl?.getSourceFile() === identifier.getSourceFile()) { + return true; + } + } + + if (this.interfacesNeedToImport.has(name)) { + return true; + } + + return false; + } + + private extractImportedNames(sourceFile: ts.SourceFile): void { + for (const statement of sourceFile.statements) { + if (!ts.isImportDeclaration(statement)) { + continue; + } + + const importClause = statement.importClause; + if (!importClause) { + continue; + } + + const namedBindings = importClause.namedBindings; + if (!namedBindings || !ts.isNamedImports(namedBindings)) { + continue; + } + + for (const specifier of namedBindings.elements) { + const importedName = specifier.name.getText(sourceFile); + this.importedInterfaces.add(importedName); + } + } + } +} + +/** + * process interop ui + * + * @param path - declgenV2OutPath + */ +export function processInteropUI(path: string, outPath = ''): void { + const filePaths = getDeclgenFiles(path); + const program = ts.createProgram(filePaths, defaultCompilerOptions()); + const sourceFiles = getSourceFiles(program, filePaths); + + const createTransformer = (ctx: ts.TransformationContext): ts.Transformer => { + return (sourceFile: ts.SourceFile) => { + const handleUIImports = new HandleUIImports(program, ctx, outPath); + return handleUIImports.createCustomTransformer(sourceFile); + } + } + ts.transform(sourceFiles, [createTransformer]); +} diff --git a/compiler/src/interop/src/process_kit_import.ts b/compiler/src/interop/src/process_kit_import.ts new file mode 100644 index 0000000000000000000000000000000000000000..dcdd6326bde529392c3ec9c9fcc5b64dcefae327 --- /dev/null +++ b/compiler/src/interop/src/process_kit_import.ts @@ -0,0 +1,720 @@ +/* + * Copyright (c) 2023 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 ts from 'typescript'; +import fs from 'fs'; +import path from 'path'; + +import { + IFileLog, + LogType, + startTimeStatisticsLocation, + stopTimeStatisticsLocation, + CompilationTimeStatistics +} from './utils'; +import { projectConfig } from '../main'; +import { ModuleSourceFile } from './fast_build/ark_compiler/module/module_source_file'; +import { collectKitModules } from './fast_build/system_api/rollup-plugin-system-api'; +import { hasTsNoCheckOrTsIgnoreFiles, compilingEtsOrTsFiles } from './fast_build/ark_compiler/utils'; +import { compilerOptions } from './ets_checker'; +import { + transformLazyImport, + lazyImportReExportCheck, + reExportNoCheckMode, + LazyImportOptions +} from './process_lazy_import'; +import createAstNodeUtils from './create_ast_node_utils'; +import { MemoryMonitor } from './fast_build/meomry_monitor/rollup-plugin-memory-monitor'; +import { MemoryDefine } from './fast_build/meomry_monitor/memory_define'; +import { + ArkTSErrorDescription, + ErrorCode +} from './fast_build/ark_compiler/error_code'; +import { + LogData, + LogDataFactory +} from './fast_build/ark_compiler/logger'; +import { etsLoaderErrorReferences } from './fast_build/ark_compiler/url_config.json'; + +/* +* basic implementation logic: +* tsc -> transformer +* | -> iterate top-level static import/export declaration +* | -> for each declaration +* | -> collect KitInfo +* | -> generate corresponding ohosImports for each ohos-source +* | -> replace each origin declaration with corresponding ohosImports +*/ + +export const kitTransformLog: IFileLog = new createAstNodeUtils.FileLog(); + +const KIT_PREFIX = '@kit.'; +const KEEPTS = '// @keepTs'; + +/* +* This API is the TSC Transformer for transforming `KitImport` into `OhosImport` +* e.g. +* ``` +* import { ability, ErrorCode } from '@kit.AbilityKit' +* ---> +* import ability from '@ohos.ability.ability' +* import ErrorCode from '@ohos.ability.errorCode' +* ``` +*/ +export function processKitImport(id: string, metaInfo: Object, compilationTime: CompilationTimeStatistics, + shouldReturnOriginalNode: boolean = true, + lazyImportOptions: LazyImportOptions = { autoLazyImport: false, reExportCheckMode: reExportNoCheckMode }): Function { + const { autoLazyImport, reExportCheckMode } = lazyImportOptions; + return (context: ts.TransformationContext) => { + const visitor: ts.Visitor = node => { + // only transform static import/export declaration + if (ts.isImportDeclaration(node) || (ts.isExportDeclaration(node) && node.moduleSpecifier)) { + const moduleRequest: string = (node.moduleSpecifier as ts.StringLiteral).text.replace(/'|"/g, ''); + if (moduleRequest.startsWith(KIT_PREFIX)) { + const kitDefs = getKitDefs(moduleRequest); + if (kitDefs && kitDefs.symbols) { + KitInfo.processKitInfo(moduleRequest, kitDefs.symbols as KitSymbols, node); + const currentKitInfo: KitInfo | undefined = KitInfo.getCurrentKitInfo(); + return currentKitInfo ? [...currentKitInfo.getOhosImportNodes()] : []; + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_KIT_CONFIG_FILE_NOT_FOUND, + ArkTSErrorDescription, + `Kit '${moduleRequest}' has no corresponding config file in ArkTS SDK.`, + '', + [ + "Please make sure the Kit apis are consistent with SDK and there's no local modification on Kit apis.", + `For more details on Kit apis, please refer to ${etsLoaderErrorReferences.harmonyOSReferencesAPI}.` + ] + ); + kitTransformLog.errors.push({ + type: LogType.ERROR, + message: errInfo.toString(), + pos: node.getStart() + }); + } + } + } + return node; + }; + + return (node: ts.SourceFile) => { + startTimeStatisticsLocation(compilationTime ? compilationTime.processKitImportTime : undefined); + const newSourceFileRecordInfo = MemoryMonitor.recordStage(MemoryDefine.NEW_SOURCE_FILE); + compilingEtsOrTsFiles.push(path.normalize(node.fileName)); + + KitInfo.init(node, context, id); + // @ts-ignore + const resolver = context.getEmitResolver(); + + // When compile hap or hsp, it is used to determine whether there is a keepTsNode in the file. + let hasKeepTs: boolean = false; + if (!projectConfig.complieHar) { + hasKeepTs = checkHasKeepTs(node); + } + + if (projectConfig.processTs === true) { + if (ts.hasTsNoCheckOrTsIgnoreFlag(node) && !hasKeepTs) { + hasTsNoCheckOrTsIgnoreFiles.push(path.normalize(node.fileName)); + // process KitImport transforming + const processedNode: ts.SourceFile = + ts.visitEachChild(node, visitor, context); // this node used for [writeFile] + MemoryMonitor.stopRecordStage(newSourceFileRecordInfo); + stopTimeStatisticsLocation(compilationTime ? compilationTime.processKitImportTime : undefined); + // this processNode is used to convert ets/ts to js intermediate products + return processedNode; + } + // process [ConstEnum] + [TypeExportImport] + [KitImport] transforming + // when autoLazyImport is true, some imports are converted to lazy-import + // eg. import { xxx } form "xxx" --> import lazy { xxx } form "xxx" + let processedNode: ts.SourceFile = + ts.visitEachChild(ts.getTypeExportImportAndConstEnumTransformer(context)(node), visitor, context); + processedNode = (autoLazyImport ? transformLazyImport(processedNode, resolver) : processedNode); + lazyImportReExportCheck(processedNode, reExportCheckMode); + ModuleSourceFile.newSourceFile(id, processedNode, metaInfo, projectConfig.singleFileEmit); + MemoryMonitor.stopRecordStage(newSourceFileRecordInfo); + stopTimeStatisticsLocation(compilationTime ? compilationTime.processKitImportTime : undefined); + return shouldReturnOriginalNode ? node : processedNode; // this node not used for [writeFile] + } + // process KitImport transforming + const processedNode: ts.SourceFile = ts.visitEachChild(node, visitor, context); + MemoryMonitor.stopRecordStage(newSourceFileRecordInfo); + stopTimeStatisticsLocation(compilationTime ? compilationTime.processKitImportTime : undefined); + return processedNode; + }; + }; +} + +/* +* Main implementation of Transforming +*/ +const DEFAULT_BINDINGS = 'default'; + +enum FileType { + ETS, + TS +} + +interface KitSymbol { + source: string + bindings: string +} + +declare type KitSymbols = Record; +declare type TSspecifier = ts.ImportSpecifier | ts.ExportSpecifier; +declare type TSModuleDeclaration = ts.ImportDeclaration | ts.ExportDeclaration; + +/* +* class SpecificerInfo represents the corresponding info of each imported identifier which coming from Kit +*/ +class SpecificerInfo { + private localName: string; + private importName: string; + private symbol: KitSymbol; + private renamed: boolean; + + private originElement: TSspecifier | undefined; + private tsImportSendableEnable : boolean = compilerOptions.tsImportSendableEnable; + + constructor(localName: string, importName: string, symbol: KitSymbol, originElement: TSspecifier | undefined) { + this.localName = localName; + this.importName = importName; + this.symbol = symbol; + this.originElement = originElement; + this.renamed = (this.localName !== this.symbol.bindings); + + this.validateImportingETSDeclarationSymbol(); + } + + getSource(): string { + return this.symbol.source; + } + + getLocalName(): string { + return this.localName; + } + + isRenamed(): boolean { + return this.renamed; + } + + getBindings(): string { + return this.symbol.bindings; + } + + isDefaultBinding(): boolean { + return this.symbol.bindings === DEFAULT_BINDINGS; + } + + validateImportingETSDeclarationSymbol() { + if (!this.tsImportSendableEnable && KitInfo.isTSFile() && /.d.ets$/.test(this.symbol.source)) { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_IDENTIFIER_IMPORT_NOT_ALLOWED_IN_TS_FILE, + ArkTSErrorDescription, + `Identifier '${this.importName}' comes from '${this.symbol.source}' ` + + 'which can not be imported in .ts file.', + '', + ["Please remove the import statement or change the file extension to .ets."] + ); + kitTransformLog.errors.push({ + type: LogType.ERROR, + message: errInfo.toString(), + pos: this.getOriginElementNode().getStart() + }); + } + } + + setOriginElementNode(originElement: TSspecifier): void { + this.originElement = originElement; + } + + getOriginElementNode(): TSspecifier { + return this.originElement; + } +} + +export class KitInfo { + private static currentKitInfo: KitInfo = undefined; + private static currentFileType: FileType = FileType.ETS; + private static currentKitName: string = ''; + private static currentSourcefile: string = ''; + private static needSkipType: boolean = true; + private static tsEmitResolver: Object; + + private symbols: KitSymbols; + private kitNode: TSModuleDeclaration; + private kitNodeModifier: readonly ts.Modifier[] | undefined; + private specifiers: Map = new Map(); + + private ohosImportNodes: TSModuleDeclaration[] = []; + + constructor(kitNode: TSModuleDeclaration, symbols: Record) { + this.kitNode = kitNode; + this.symbols = symbols; + + this.kitNodeModifier = ts.canHaveDecorators(this.kitNode) ? ts.getModifiers(this.kitNode) : undefined; + } + + static init(node: ts.SourceFile, context: ts.TransformationContext, moduleId: string): void { + // @ts-ignore + this.tsEmitResolver = context.getEmitResolver(); + this.currentSourcefile = moduleId; + if (/\.ts$/.test(node.fileName)) { + this.setFileType(FileType.TS); + } else { + this.setFileType(FileType.ETS); + } + + if (projectConfig.processTs === true && !ts.hasTsNoCheckOrTsIgnoreFlag(node)) { + this.needSkipType = false; + } else { + this.needSkipType = true; + } + + kitTransformLog.sourceFile = node; + } + + static getCurrentKitName(): string { + return this.currentKitName; + } + + static getCurrentKitInfo(): KitInfo { + return this.currentKitInfo; + } + + static setFileType(fileType: FileType): void { + this.currentFileType = fileType; + } + + static isTSFile(): boolean { + return this.currentFileType === FileType.TS; + } + + static getCurrentSourcefile(): string { + return this.currentSourcefile; + } + + static needSkipTypeSymbolOfNode(node: ts.Node): boolean { + if (!this.needSkipType) { + return false; + } + + // need to skip type symbol + const resolver = this.tsEmitResolver; + let isTypeSymbol: boolean = false; + switch (node.kind) { + case ts.SyntaxKind.ImportDeclaration: + case ts.SyntaxKind.ImportClause: { + const importClause = ts.isImportClause(node) ? node : (node as ts.ImportDeclaration).importClause; + if (importClause) { + isTypeSymbol = importClause.isTypeOnly; + if (importClause.name && !resolver.isReferencedAliasDeclaration(importClause)) { + isTypeSymbol = true; + } + } + break; + } + case ts.SyntaxKind.ImportSpecifier: { + isTypeSymbol = (node as ts.ImportSpecifier).isTypeOnly; + if (!resolver.isReferencedAliasDeclaration(node)) { + isTypeSymbol = true; + } + break; + } + case ts.SyntaxKind.ExportDeclaration: { + isTypeSymbol = (node as ts.ExportDeclaration).isTypeOnly; + break; + } + case ts.SyntaxKind.ExportSpecifier: { + isTypeSymbol = (node as ts.ExportSpecifier).isTypeOnly; + if (!resolver.isValueAliasDeclaration(node)) { + isTypeSymbol = true; + } + break; + } + } + return isTypeSymbol; + } + + static processImportDecl(kitNode: ts.ImportDeclaration, symbols: Record) { + if (!kitNode.importClause) { + // e.g. import "@kit.xxx" + this.currentKitInfo = new EmptyImportKitInfo(kitNode, symbols); + return; + } + + if (kitNode.importClause!.namedBindings) { + const namedBindings: ts.NamedImportBindings = kitNode.importClause.namedBindings; + if (ts.isNamespaceImport(namedBindings)) { + // e.g. import * as ns from "@kit.xxx" + this.currentKitInfo = new NameSpaceKitInfo(kitNode, symbols); + } + if (ts.isNamedImports(namedBindings) && namedBindings.elements.length !== 0) { + // e.g. import { ... } from "@kit.xxx" + this.currentKitInfo = new ImportSpecifierKitInfo(kitNode, symbols); + namedBindings.elements.forEach(element => { this.currentKitInfo.collectSpecifier(element) }); + } + } + + if (kitNode.importClause!.name && !this.needSkipTypeSymbolOfNode(kitNode.importClause)) { + // e.g. import default from "@kit.xxx" + const defaultName: string = kitNode.importClause.name.text; + if (!this.currentKitInfo) { + this.currentKitInfo = new ImportSpecifierKitInfo(kitNode, symbols); + } + this.currentKitInfo.newSpecificerInfo(defaultName, DEFAULT_BINDINGS, undefined); + } + } + + static processExportDecl(kitNode: ts.ExportDeclaration, symbols: Record): void { + if (kitNode.exportClause) { + const namedExportBindings: ts.NamedExportBindings = kitNode.exportClause; + if (ts.isNamespaceExport(namedExportBindings)) { + // e.g. export * as ns from "@kit.xxx" + this.currentKitInfo = new NameSpaceKitInfo(kitNode, symbols); + } else if (ts.isNamedExports(namedExportBindings) && namedExportBindings.elements.length !== 0) { + // e.g. export { ... } from "@kit.xxx" + this.currentKitInfo = new ExportSpecifierKitInfo(kitNode, symbols); + namedExportBindings.elements.forEach(element => { this.currentKitInfo.collectSpecifier(element) }); + } + } else { + this.currentKitInfo = new ExportStarKitInfo(kitNode, symbols); + } + } + + static processKitInfo(kitName: string, symbols: Record, kitNode: TSModuleDeclaration): void { + // clean up the currentKitInfo to prevent the following process getting + // the incorrect symbolTable with the KitInfo from last kit import. + this.currentKitInfo = undefined; + this.currentKitName = kitName; + + // do not handle an empty import + if (ts.isImportDeclaration(kitNode)) { + // case 1: import { ... } from '@kit.xxx' + // case 2: import * as ns from '@kit.xxx' + // case 3: import defalutValue from '@kit.xxx' + // case 4: import '@kit.xxx' + this.processImportDecl(kitNode, symbols); + } + + if (ts.isExportDeclaration(kitNode) && kitNode.moduleSpecifier) { + // case 1: export { ... } from '@kit.xxx' + // case 2: export * from '@kit.xxx' + // case 3: export * as ns from '@kit.xxx' - considering forbidden + this.processExportDecl(kitNode, symbols); + } + // transform into ohos imports or exports + this.currentKitInfo && this.currentKitInfo.transform(); + } + + static cleanUp(): void { + this.currentKitInfo = undefined; + this.tsEmitResolver = undefined; + } + + getSymbols(): KitSymbols { + return this.symbols; + } + + getKitNode(): TSModuleDeclaration { + return this.kitNode; + } + + getKitNodeModifier(): readonly ts.Modifier[] | undefined { + return this.kitNodeModifier; + } + + getSpecifiers(): Map { + return this.specifiers; + } + + getOhosImportNodes(): TSModuleDeclaration[] { + return this.ohosImportNodes; + } + + newSpecificerInfo(localName: string, importName: string, originElement: TSspecifier | undefined): void { + const symbol: KitSymbol | undefined = this.symbols[importName]; + if (symbol) { + const specifier: SpecificerInfo = new SpecificerInfo(localName, importName, symbol, originElement); + if (this.specifiers.has(symbol.source)) { + this.specifiers.get(symbol.source).push(specifier); + } else { + this.specifiers.set(symbol.source, [specifier]); + } + } else { + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_IMPORT_NAME_NOT_EXPORTED_FROM_KIT, + ArkTSErrorDescription, + `'${importName}' is not exported from Kit '${KitInfo.getCurrentKitName()}'.`, + '', + [ + "Please make sure the Kit apis are consistent with SDK and there's no local modification on Kit apis.", + `For more details on Kit apis, please refer to ${etsLoaderErrorReferences.harmonyOSReferencesAPI}.` + ] + ); + kitTransformLog.errors.push({ + type: LogType.ERROR, + message: errInfo.toString(), + pos: originElement ? originElement.getStart() : this.getKitNode().getStart() + }); + } + } + + collectSpecifier(element: TSspecifier): void { + if (KitInfo.needSkipTypeSymbolOfNode(this.getKitNode()) || KitInfo.needSkipTypeSymbolOfNode(element)) { + // skip type symbol + return; + } + + const localName: string = element.name.text; + const importName: string = element.propertyName ? element.propertyName.text : localName; + this.newSpecificerInfo(localName, importName, element); + } + + // @ts-ignore + transform(): void {} //override api +} + +class NameSpaceKitInfo extends KitInfo { + private namespaceName: string; + private localNameTable: string[] = []; + + constructor(kitNode: ts.ImportDeclaration | ts.ExportDeclaration, symbols: Record) { + super(kitNode, symbols); + + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_KIT_NAMESPACE_IMPORT_EXPORT_NOT_SUPPORTED, + ArkTSErrorDescription, + 'Namespace import or export of Kit is not supported currently.', + '', + ['Please namespace import or export of Kit replace it with named import or export instead. ' + + 'For example, import * as ArkTS from "@kit.ArkUI"; -> import { AlertDialog } from "@kit.ArkUI";'] + ); + kitTransformLog.errors.push({ + type: LogType.ERROR, + message: errInfo.toString(), + pos: kitNode.getStart() + }); + } + + transform(): void { + } +} + +class ImportSpecifierKitInfo extends KitInfo { + private namedBindings: ts.ImportSpecifier[] = []; + private specifierDefaultName: ts.Identifier | undefined = undefined; + + constructor(kitNode: ts.ImportDeclaration, symbols: Record) { + super(kitNode, symbols); + } + + private hasNamedBindings(): boolean { + return this.namedBindings.length !== 0; + } + + private clearSpecifierKitInfo(): void { + this.namedBindings = []; + this.specifierDefaultName = undefined; + } + + transform(): void { + const node: ts.ImportDeclaration = this.getKitNode() as ts.ImportDeclaration; + + this.getSpecifiers().forEach((specifiers: SpecificerInfo[], source: string) => { + collectKitModules(KitInfo.getCurrentSourcefile(), KitInfo.getCurrentKitName(), source); + specifiers.forEach((specifier: SpecificerInfo) => { + if (specifier.isDefaultBinding()) { + this.specifierDefaultName = ts.factory.createIdentifier(specifier.getLocalName()); + } else { + this.namedBindings.push( + ts.factory.createImportSpecifier( + specifier.getOriginElementNode() ? + (specifier.getOriginElementNode() as ts.ImportSpecifier).isTypeOnly : node.importClause.isTypeOnly, + specifier.isRenamed() ? ts.factory.createIdentifier(specifier.getBindings()) : undefined, + ts.factory.createIdentifier(specifier.getLocalName()) + ) + ); + } + }); + + let newImportClause: ts.ImportClause = ts.factory.createImportClause( + node.importClause!.isTypeOnly, + this.specifierDefaultName, + this.hasNamedBindings() ? ts.factory.createNamedImports(this.namedBindings) : undefined + ); + // @ts-ignore + newImportClause.isLazy = node.importClause!.isLazy; + this.getOhosImportNodes().push(ts.factory.createImportDeclaration( + this.getKitNodeModifier(), + newImportClause, + ts.factory.createStringLiteral(trimSourceSuffix(source)) + )); + + this.clearSpecifierKitInfo(); + }); + } +} + +class EmptyImportKitInfo extends KitInfo { + constructor(kitNode: ts.ImportDeclaration, symbols: Record) { + super(kitNode, symbols); + + /* + * Side-effect import can not be used by Kit since Kit actually has no spcific implementation. + * In general, a Kit may be imported in a Side-effect import statement by mistake. So we + * illustrate explicitly that Kit can not in Side-effect import to avoid misunderstanding + * of runtime's behavior. + */ + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_EMPTY_IMPORT_NOT_ALLOWED_WITH_KIT, + ArkTSErrorDescription, + `Can not use empty import(side-effect import) statement with Kit ` + + `'${(kitNode.moduleSpecifier as ts.StringLiteral).text.replace(/'|"/g, '')}'.`, + '', + ['Please specify imported symbols explicitly. ' + + 'For example, import "@kit.ArkUI"; -> import { lang } from "@kit.ArkUI";'] + ); + kitTransformLog.errors.push({ + type: LogType.ERROR, + message: errInfo.toString(), + pos: kitNode.getStart() + }); + } + + transform(): void { + } +} + +class ExportSpecifierKitInfo extends KitInfo { + private namedBindings: ts.ExportSpecifier[] = []; + + constructor(kitNode: ts.ExportDeclaration, symbols: Record) { + super(kitNode, symbols); + } + + private hasNamedBindings(): boolean { + return this.namedBindings.length !== 0; + } + + private clearSpecifierKitInfo(): void { + this.namedBindings = []; + } + + transform(): void { + const node: ts.ExportDeclaration = this.getKitNode() as ts.ExportDeclaration; + + this.getSpecifiers().forEach((specifiers: SpecificerInfo[], source: string) => { + specifiers.forEach((specifier: SpecificerInfo) => { + this.namedBindings.push( + ts.factory.createExportSpecifier( + (specifier.getOriginElementNode() as ts.ExportSpecifier).isTypeOnly, + specifier.isRenamed() ? ts.factory.createIdentifier(specifier.getBindings()) : undefined, + ts.factory.createIdentifier(specifier.getLocalName()) + ) + ); + }); + + this.getOhosImportNodes().push(ts.factory.createExportDeclaration( + this.getKitNodeModifier(), + node.isTypeOnly, + this.hasNamedBindings() ? ts.factory.createNamedExports(this.namedBindings) : undefined, + ts.factory.createStringLiteral(trimSourceSuffix(source)), + node.assertClause + )); + + this.clearSpecifierKitInfo(); + }); + } +} + +class ExportStarKitInfo extends KitInfo { + private sourceSet: Set = new Set(); + + constructor(kitNode: ts.ExportDeclaration, symbols: Record) { + super(kitNode, symbols); + + for (const symbol in symbols) { + this.sourceSet.add(symbols[symbol].source); + } + + kitTransformLog.errors.push({ + type: LogType.WARN, + message: `Using 'export *' will load all the sub-module of Kit in runtime.`, + pos: this.getKitNode().getStart() + }); + } + + transform(): void { + const node: ts.ExportDeclaration = this.getKitNode() as ts.ExportDeclaration; + + this.sourceSet.forEach((source: string) => { + this.getOhosImportNodes().push(ts.factory.createExportDeclaration( + this.getKitNodeModifier(), + node.isTypeOnly, + undefined, + ts.factory.createStringLiteral(trimSourceSuffix(source)), + node.assertClause + )); + }); + } +} + +/* +* utils part +*/ +const JSON_SUFFIX = '.json'; +const KIT_CONFIGS = 'kit_configs'; +const KIT_CONFIG_PATH = './build-tools/ets-loader/kit_configs'; + +function getKitDefs(kitModuleRequest: string): Object | undefined { + const kitConfigs: string[] = [path.resolve(__dirname, `../${KIT_CONFIGS}`)]; //??? + if (process.env.externalApiPaths) { + const externalApiPaths = process.env.externalApiPaths.split(path.delimiter); + externalApiPaths.forEach(sdkPath => { + kitConfigs.push(path.resolve(sdkPath, KIT_CONFIG_PATH)); + }); + } + + for (const kitConfig of kitConfigs) { + const kitModuleConfigJson = path.resolve(kitConfig, `./${kitModuleRequest}${JSON_SUFFIX}`); + if (fs.existsSync(kitModuleConfigJson)) { + return JSON.parse(fs.readFileSync(kitModuleConfigJson, 'utf-8')); + } + } + return undefined; +} + +function trimSourceSuffix(source: string): string { + return source.replace(/\.d.[e]?ts$/, ''); +} + +export function checkHasKeepTs(node: ts.SourceFile): boolean { + // Get the first comment in the file and determine whether it is "// @keepTs" + const comments = ts.getTrailingCommentRanges(node.getFullText(), 0) || []; + if (comments.length === 0) { + return false; + } + return node.getFullText().substring(comments[0].pos, comments[0].end).trim() === KEEPTS; +} + +export function resetKitImportLog(): void { + kitTransformLog.cleanUp(); +} + +export function cleanUpKitImportObjects(): void { + KitInfo.cleanUp(); + kitTransformLog.cleanUp(); +} \ No newline at end of file diff --git a/compiler/src/interop/src/process_lazy_import.ts b/compiler/src/interop/src/process_lazy_import.ts new file mode 100644 index 0000000000000000000000000000000000000000..5e87a2e4fbcecbebb873f8ca945ec476c25693bf --- /dev/null +++ b/compiler/src/interop/src/process_lazy_import.ts @@ -0,0 +1,226 @@ +/* + * 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 ts from 'typescript'; + +import { + IFileLog, + LogType +} from './utils'; +import { + LogData, + LogDataFactory +} from './fast_build/ark_compiler/logger'; +import { + ArkTSErrorDescription, + ErrorCode +} from './fast_build/ark_compiler/error_code'; +import creatAstNodeUtils from './create_ast_node_utils'; + +export const reExportCheckLog: IFileLog = new creatAstNodeUtils.FileLog(); +export const reExportNoCheckMode: string = 'noCheck'; +const reExportStrictMode: string = 'strict'; + +export interface LazyImportOptions { + autoLazyImport: boolean; + reExportCheckMode: string; +} + +export function processJsCodeLazyImport(id: string, code: string, + autoLazyImport: boolean, reExportCheckMode: string): string { + let sourceNode: ts.SourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.ES2021, true, ts.ScriptKind.JS); + if (autoLazyImport) { + sourceNode = transformLazyImport(sourceNode); + } + lazyImportReExportCheck(sourceNode, reExportCheckMode); + return autoLazyImport ? ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }).printFile(sourceNode) : code; +} + +export function transformLazyImport(sourceNode: ts.SourceFile, resolver?: Object): ts.SourceFile { + const moduleNodeTransformer: ts.TransformerFactory = context => { + const visitor: ts.Visitor = node => { + if (ts.isImportDeclaration(node)) { + return updateImportDecl(node, resolver); + } + return node; + }; + return node => ts.visitEachChild(node, visitor, context); + }; + + const result: ts.TransformationResult = + ts.transform(sourceNode, [moduleNodeTransformer]); + return result.transformed[0]; +} + +function updateImportDecl(node: ts.ImportDeclaration, resolver: Object): ts.ImportDeclaration { + const importClause: ts.ImportClause | undefined = node.importClause; + // The following cases do not support lazy-import. + // case1: import '...' + // case2: import type { t } from '...' or import type t from '...' + // case3: import lazy { x } from '...' + if (!importClause || importClause.isTypeOnly || importClause.isLazy) { + return node; + } + // case4: import * as ns from '...' + // case5: import y, * as ns from '...' + if (importClause.namedBindings && ts.isNamespaceImport(importClause.namedBindings)) { + return node; + } + const namedBindings: ts.NamedImportBindings = importClause.namedBindings; + let newImportClause: ts.ImportClause; + // The following cases support lazy-import. + // case1: import { x } from '...' --> import lazy { x } from '...' + // case2: import y, { x } from '...' --> import lazy y, { x } from '...' + if (namedBindings && ts.isNamedImports(namedBindings)) { + // The resolver is used to determine whether type symbols need to be processed. + // Only TS/ETS files have type symbols. + if (resolver) { + // eliminate the type symbol + // eg: import { type t, x } from '...' --> import { x } from '...' + const newNameBindings: ts.ImportSpecifier[] = eliminateTypeSymbol(namedBindings, resolver); + newImportClause = ts.factory.createImportClause(false, importClause.name, + ts.factory.createNamedImports(newNameBindings)); + } else { + newImportClause = importClause; + } + } else if (!namedBindings && importClause.name) { + // case3: import y from '...' --> import lazy y from '...' + newImportClause = importClause; + } + // @ts-ignore + newImportClause.isLazy = true; + const modifiers: readonly ts.Modifier[] | undefined = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + return ts.factory.updateImportDeclaration(node, modifiers, newImportClause, node.moduleSpecifier, node.assertClause); +} + +function eliminateTypeSymbol(namedBindings: ts.NamedImportBindings, resolver: Object): ts.ImportSpecifier[] { + const newNameBindings: ts.ImportSpecifier[] = []; + namedBindings.elements.forEach(item => { + const element = item as ts.ImportSpecifier; + if (!element.isTypeOnly && resolver.isReferencedAliasDeclaration(element)) { + // import { x } from './y' --> propertyName is undefined + // import { x as a } from './y' --> propertyName is x + newNameBindings.push( + ts.factory.createImportSpecifier( + false, + element.propertyName ? ts.factory.createIdentifier(element.propertyName.text) : undefined, + ts.factory.createIdentifier(element.name.text) + ) + ); + } + }); + return newNameBindings; +} + +export function resetReExportCheckLog(): void { + reExportCheckLog.cleanUp(); +} + +export function lazyImportReExportCheck(node: ts.SourceFile, reExportCheckMode: string): void { + if (reExportCheckMode === reExportNoCheckMode) { + return; + } + reExportCheckLog.sourceFile = node; + const lazyImportSymbols: Set = new Set(); + const exportSymbols: Map = new Map(); + const result: Map = new Map(); + node.statements.forEach(stmt => { + collectLazyImportSymbols(stmt, lazyImportSymbols, exportSymbols, result); + collectLazyReExportSymbols(stmt, lazyImportSymbols, exportSymbols, result); + }); + for (const [key, statements] of result.entries()) { + for (const statement of statements) { + collectReExportErrors(statement, key, reExportCheckMode); + } + } +} + +function collectLazyImportSymbols(stmt: ts.Statement, lazyImportSymbols: Set, + exportSymbols: Map, result: Map): void { + if (ts.isImportDeclaration(stmt) && stmt.importClause && stmt.importClause.isLazy) { + // For import lazy x from './y', collect 'x' + const importClauseName = stmt.importClause.name; + if (importClauseName) { + lazyImportSymbols.add(importClauseName.text); + result.set(importClauseName.text, exportSymbols.get(importClauseName.text) ?? []); + } + // For import lazy { x } from './y', collect 'x' + const importNamedBindings: ts.NamedImportBindings = stmt.importClause.namedBindings; + if (importNamedBindings && ts.isNamedImports(importNamedBindings) && importNamedBindings.elements.length !== 0) { + importNamedBindings.elements.forEach((element: ts.ImportSpecifier) => { + const nameText = element.name.text; + lazyImportSymbols.add(nameText); + result.set(nameText, exportSymbols.get(nameText) ?? []); + }); + } + } +} + +function collectLazyReExportSymbols(stmt: ts.Statement, lazyImportSymbols: Set, + exportSymbols: Map, result: Map): void { + // export default x + if (ts.isExportAssignment(stmt) && ts.isIdentifier(stmt.expression)) { + const nameText: string = stmt.expression.text; + const targetMap = lazyImportSymbols.has(nameText) ? result : exportSymbols; + if (!targetMap.get(nameText)) { + targetMap.set(nameText, []); + } + targetMap.get(nameText).push(stmt); + } + // export { x } + if (ts.isExportDeclaration(stmt) && !stmt.moduleSpecifier && + ts.isNamedExports(stmt.exportClause) && stmt.exportClause.elements.length !== 0) { + stmt.exportClause.elements.forEach((element: ts.ExportSpecifier) => { + // For example, in 'export { foo as bar }', exportName is 'bar', localName is 'foo' + const exportName: string = element.name.text; + const localName: string = element.propertyName ? element.propertyName.text : exportName; + const targetMap = lazyImportSymbols.has(localName) ? result : exportSymbols; + if (!targetMap.get(localName)) { + targetMap.set(localName, []); + } + targetMap.get(localName).push(stmt); + }); + } +} + +function collectReExportErrors(node: ts.Node, elementText: string, reExportCheckMode: string): void { + let pos: number; + try { + pos = node.getStart(); + } catch { + pos = 0; + } + let type: LogType = LogType.WARN; + if (reExportCheckMode === reExportStrictMode) { + type = LogType.ERROR; + } + // reExportCheckMode explanation: + // - 'noCheck': NoCheck mode. The functionality to block re-exported lazy-import is disabled. + // - 'strict': Strict mode. It intercepts errors and treats them as critical (LogType.ERROR). + // - 'compatible': Compatible mode. It logs warnings (LogType.WARN) but does not intercept or block them. + const errInfo: LogData = LogDataFactory.newInstance( + ErrorCode.ETS2BUNDLE_EXTERNAL_LAZY_IMPORT_RE_EXPORT_ERROR, + ArkTSErrorDescription, + `'${elementText}' of lazy-import is re-export`, + '', + ['Please make sure the namedBindings of lazy-import are not be re-exported.', + 'Please check whether the autoLazyImport switch is opened.'] + ); + reExportCheckLog.errors.push({ + type: type, + message: errInfo.toString(), + pos: pos + }); +} \ No newline at end of file diff --git a/compiler/src/interop/src/process_module_files.ts b/compiler/src/interop/src/process_module_files.ts new file mode 100644 index 0000000000000000000000000000000000000000..bdc7e8e7e34b67983359717ce7c2398abe49d8b7 --- /dev/null +++ b/compiler/src/interop/src/process_module_files.ts @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2023 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 path from 'path'; +import ts from 'typescript'; +import fs from 'fs'; + +import { SourceMapGenerator } from './fast_build/ark_compiler/generate_sourcemap'; +import { + EXTNAME_TS, + EXTNAME_ETS +} from './pre_define'; +import { + genTemporaryPath, + mkdirsSync, + toUnixPath, +} from './utils'; +import { + genSourceMapFileName, + newSourceMaps as webpackNewSourceMaps, + transformModuleSpecifier, + writeObfuscatedSourceCode, + createAndStartEvent, + stopEvent +} from './ark_utils'; +import { processSystemApi } from './validate_ui_syntax'; +import { isDebug } from './fast_build/ark_compiler/utils'; +import { getRelativeSourcePath } from './fast_build/ark_compiler/common/ob_config_resolver'; + +export const SRC_MAIN: string = 'src/main'; + +export async function writeFileSyncByNode(node: ts.SourceFile, projectConfig: Object, metaInfo: Object, moduleId?: string, + parentEvent?: Object, logger?: Object): Promise { + const eventWriteFileSyncByNode = createAndStartEvent(parentEvent, 'write file sync by node'); + const eventGenContentAndSourceMapInfo = createAndStartEvent(eventWriteFileSyncByNode, 'generate content and source map information'); + const mixedInfo: { content: string, sourceMapJson: ts.RawSourceMap } = genContentAndSourceMapInfo(node, moduleId, projectConfig, metaInfo); + const sourceMapGenerator = SourceMapGenerator.getInstance(); + stopEvent(eventGenContentAndSourceMapInfo); + + /** + * In the following situation: + * A typescript source file whose name is 'Test.ts', which is used via `import xxx for 'test'` in another source file. + + * The value of "node.fileName" consists of "test.ts", which does not correspond with the source file's actual name and would lead to a compilation error. + * The value of moduleId is same as the actual file name, so it would be used here for locating the target source file. + + * Note: current realization is related to the moduleId mechanism in the rollup framework, which is needed to be reconsidered to improve the code robustness. + * In the current realization, when moduleId mechanism is changed, there would be a compilation error. + */ + let filePath: string = moduleId ? moduleId : node.fileName; + let temporaryFile: string = genTemporaryPath(filePath, projectConfig.projectPath, process.env.cachePath, + projectConfig, metaInfo); + if (temporaryFile.length === 0) { + return; + } + if (temporaryFile.endsWith(EXTNAME_ETS)) { + temporaryFile = temporaryFile.replace(/\.ets$/, EXTNAME_TS); + } + let relativeFilePath = getRelativeSourcePath(filePath, projectConfig.projectRootPath, metaInfo?.belongProjectPath); + let sourceMaps: Object; + if (process.env.compileTool === 'rollup') { + const key = sourceMapGenerator.isNewSourceMaps() ? moduleId! : relativeFilePath; + sourceMapGenerator.fillSourceMapPackageInfo(moduleId!, mixedInfo.sourceMapJson); + sourceMapGenerator.updateSourceMap(key, mixedInfo.sourceMapJson); + sourceMaps = sourceMapGenerator.getSourceMaps(); + } else { + webpackNewSourceMaps[relativeFilePath] = mixedInfo.sourceMapJson; + sourceMaps = webpackNewSourceMaps; + } + if (!isDebug(projectConfig)) { + const eventWriteObfuscatedSourceCode = createAndStartEvent(eventWriteFileSyncByNode, 'write obfuscated source code'); + await writeObfuscatedSourceCode({ + content: mixedInfo.content, + buildFilePath: temporaryFile, + relativeSourceFilePath: relativeFilePath, + originSourceFilePath: node.fileName, + rollupModuleId: moduleId ? moduleId : undefined + }, logger, projectConfig, sourceMaps); + stopEvent(eventWriteObfuscatedSourceCode); + return; + } + mkdirsSync(path.dirname(temporaryFile)); + fs.writeFileSync(temporaryFile, mixedInfo.content); + stopEvent(eventWriteFileSyncByNode); +} + +function genContentAndSourceMapInfo(node: ts.SourceFile, moduleId: string | undefined, projectConfig: Object, metaInfo: Object): Object { + const printer: ts.Printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const options: ts.CompilerOptions = { + sourceMap: true + }; + const mapOpions: Object = { + sourceMap: true, + inlineSourceMap: false, + inlineSources: false, + sourceRoot: '', + mapRoot: '', + extendedDiagnostics: false + }; + const host: ts.CompilerHost = ts.createCompilerHost(options); + const fileName: string = moduleId ? moduleId : node.fileName; + // @ts-ignore + const sourceMapGenerator: ts.SourceMapGenerator = ts.createSourceMapGenerator( + host, + // @ts-ignore + ts.getBaseFileName(fileName), + '', + '', + mapOpions + ); + // @ts-ignore + const writer: ts.EmitTextWriter = ts.createTextWriter( + // @ts-ignore + ts.getNewLineCharacter({ newLine: ts.NewLineKind.LineFeed, removeComments: false })); + printer.writeFile(node, writer, sourceMapGenerator); + const sourceMapJson: ts.RawSourceMap = sourceMapGenerator.toJSON(); + sourceMapJson.sources = [ + toUnixPath(fileName).startsWith(toUnixPath(projectConfig.projectRootPath)) ? + toUnixPath(fileName).replace(toUnixPath(projectConfig.projectRootPath) + '/', '') : + toUnixPath(fileName).replace(toUnixPath(metaInfo.belongProjectPath) + '/', '') + ]; + let content: string = writer.getText(); + if (process.env.compileTool !== 'rollup') { + content = transformModuleSpecifier(fileName, processSystemApi(content, true), projectConfig); + } + + return { + content: content, + sourceMapJson: sourceMapJson + }; +} diff --git a/compiler/src/interop/src/process_module_package.ts b/compiler/src/interop/src/process_module_package.ts new file mode 100644 index 0000000000000000000000000000000000000000..9c1738ccfa911f6f8f0e4e53b2a2873d570217bb --- /dev/null +++ b/compiler/src/interop/src/process_module_package.ts @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ts from 'typescript'; +import path from 'path'; + +import { projectConfig } from '../main'; +import { + resourceFileName, + isDynamic +} from './process_ui_syntax'; +import { + PAGE_PATH, + INTEGRATED_HSP, + TRUE, + FALSE, + RESOURCE_NAME_BUNDLE, + RESOURCE_NAME_MODULE +} from './pre_define'; +import constantDefine from './constant_define'; + +export function routerOrNavPathWrite(context: ts.TransformationContext, keyName: string, projectPath: string, + projectRootPath: string = ''): ts.PropertyAssignment { + return context.factory.createPropertyAssignment( + context.factory.createIdentifier(keyName), + context.factory.createStringLiteral( + projectConfig.compileHar ? keyName === PAGE_PATH ? byteCodeHarPagePath(projectRootPath) : '' : + pathMessage(projectPath)) + ); +} + +function byteCodeHarPagePath(projectRootPath: string): string { + return projectConfig.byteCodeHar ? pathMessage(projectRootPath) : constantDefine.HAR_DEFAULT_PAGE_PATH; +} + +function pathMessage(projectPathName: string): string { + return path.relative(projectPathName || '', resourceFileName).replace(/\\/g, '/').replace(/\.ets$/, ''); +} + +export function integratedHspType(): string { + return projectConfig.integratedHsp ? TRUE : projectConfig.compileHar ? constantDefine.HAR_DEFAULT_INTEGRATED_HSP_TYPE : FALSE; +} + +export function routerModuleType(context: ts.TransformationContext): ts.PropertyAssignment { + return context.factory.createPropertyAssignment( + context.factory.createIdentifier(constantDefine.MODULE_TYPE), + context.factory.createStringLiteral(moduleType()) + ); +} + +function moduleType(): string { + if (projectConfig.compileHar) { + return projectConfig.byteCodeHar ? constantDefine.BYTE_CODE_HAR : constantDefine.CLOSED_SOURCE_HAR; + } else if (projectConfig.compileShared) { + return projectConfig.integratedHsp ? INTEGRATED_HSP : constantDefine.SHARED_HSP; + } + return constantDefine.FOLLOW_WITH_HAP; +} + +export function routerBundleOrModule(context: ts.TransformationContext, type: string): ts.PropertyAssignment { + const typeKey: string = type === RESOURCE_NAME_BUNDLE ? RESOURCE_NAME_BUNDLE : RESOURCE_NAME_MODULE; + if (isDynamic()) { + return context.factory.createPropertyAssignment( + context.factory.createIdentifier(typeKey), + context.factory.createIdentifier(type === RESOURCE_NAME_BUNDLE ? (projectConfig.allowEmptyBundleName ? '' : '__BUNDLE_NAME__') : '__MODULE_NAME__') + ); + } + return context.factory.createPropertyAssignment( + context.factory.createIdentifier(typeKey), + context.factory.createStringLiteral(type === RESOURCE_NAME_BUNDLE ? (projectConfig.allowEmptyBundleName ? '' : (projectConfig.bundleName || '')) : + (projectConfig.moduleName || '')) + ); +} diff --git a/compiler/src/interop/src/process_sendable.ts b/compiler/src/interop/src/process_sendable.ts new file mode 100644 index 0000000000000000000000000000000000000000..5a6f58827eaf10a0167edce1329d7ca947b6e3c7 --- /dev/null +++ b/compiler/src/interop/src/process_sendable.ts @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ts from 'typescript'; +import { SUPER_ARGS, COMPONENT_SENDABLE_DECORATOR } from './pre_define'; + +function transformOptionalMemberForSendable(node: ts.PropertyDeclaration): ts.PropertyDeclaration { + let updatedTypeNode: ts.TypeNode = node.type; + + if (ts.isUnionTypeNode(updatedTypeNode)) { + if (!updatedTypeNode.types.find(type => type.kind === ts.SyntaxKind.UndefinedKeyword)) { + updatedTypeNode = ts.factory.createUnionTypeNode([ + ...updatedTypeNode.types, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword) + ]); + } + } else { + updatedTypeNode = ts.factory.createUnionTypeNode([ + updatedTypeNode, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword) + ]); + } + + return ts.factory.createPropertyDeclaration( + node.modifiers, + node.name, + undefined, + updatedTypeNode, + node.initializer ? node.initializer : ts.factory.createIdentifier('undefined') + ); +} + +function removeSendableDecorator(modifiers: ts.NodeArray): ts.NodeArray { + return ts.factory.createNodeArray( + modifiers.filter(decorator => { + const originalDecortor: string = decorator.getText().replace(/\(.*\)$/, '').trim(); + return originalDecortor !== COMPONENT_SENDABLE_DECORATOR; + }) + ); +} + +function updateSendableConstructor(constructor: ts.ConstructorDeclaration): ts.ConstructorDeclaration { + // Skip the overloaded signature of the constructor + if (constructor.body === undefined) { + return constructor; + } + const statementArray: ts.Statement[] = [ + ts.factory.createExpressionStatement(ts.factory.createStringLiteral('use sendable')), + ...constructor.body.statements + ]; + + return ts.factory.updateConstructorDeclaration( + constructor, + constructor.modifiers, + constructor.parameters, + ts.factory.updateBlock(constructor.body, statementArray) + ); +} + +function addConstructorForSendableClass( + members: ts.NodeArray, + needSuper: boolean): ts.NodeArray { + const params: ts.ParameterDeclaration[] = []; + const constructorStatements: ts.Statement[] = [ + ts.factory.createExpressionStatement(ts.factory.createStringLiteral('use sendable')) + ]; + if (needSuper) { + constructorStatements.push( + ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createSuper(), undefined, [ts.factory.createSpreadElement(ts.factory.createIdentifier(SUPER_ARGS))]) + ) + ); + params.push( + ts.factory.createParameterDeclaration( + undefined, + ts.factory.createToken(ts.SyntaxKind.DotDotDotToken), + ts.factory.createIdentifier(SUPER_ARGS), + undefined, + undefined, + undefined) + ); + } + const constructor: ts.ConstructorDeclaration = ts.factory.createConstructorDeclaration( + undefined, + params, + ts.factory.createBlock(constructorStatements, true) + ); + + return ts.factory.createNodeArray([constructor, ...(members || [])]); +} + +export function processSendableClass(node: ts.ClassDeclaration): ts.ClassDeclaration { + let hasConstructor = false; + let updatedMembers: ts.NodeArray = node.members; + let updatedModifiers: ts.NodeArray = removeSendableDecorator(node.modifiers); + let needSuper: boolean = + node.heritageClauses?.some(clause => clause.token === ts.SyntaxKind.ExtendsKeyword) || false; + + for (const member of node.members) { + if (ts.isPropertyDeclaration(member) && member.questionToken) { + const propertyDecl: ts.PropertyDeclaration = member as ts.PropertyDeclaration; + const updatedPropertyDecl: ts.PropertyDeclaration = transformOptionalMemberForSendable(member); + updatedMembers = ts.factory.createNodeArray( + updatedMembers.map(member => (member === propertyDecl ? updatedPropertyDecl : member)) + ); + } + if (ts.isConstructorDeclaration(member)) { + hasConstructor = true; + const constructor: ts.ConstructorDeclaration = member as ts.ConstructorDeclaration; + updatedMembers = ts.factory.createNodeArray( + updatedMembers.map(member => (member === constructor ? updateSendableConstructor(constructor) : member)) + ); + } + } + + if (!hasConstructor) { + updatedMembers = addConstructorForSendableClass(updatedMembers, needSuper); + } + + node = ts.factory.updateClassDeclaration(node, updatedModifiers, node.name, node.typeParameters, + node.heritageClauses, updatedMembers); + + return node; +} + +export function processSendableFunction(node: ts.FunctionDeclaration): ts.FunctionDeclaration { + if (node.body) { + const statementArray: ts.Statement[] = + [ts.factory.createExpressionStatement(ts.factory.createStringLiteral('use sendable')), + ...node.body.statements]; + return ts.factory.updateFunctionDeclaration(node, ts.getModifiers(node), node.asteriskToken, node.name, + node.typeParameters, node.parameters, node.type, ts.factory.updateBlock(node.body, statementArray)); + } + return ts.factory.createFunctionDeclaration(undefined, node.asteriskToken, node.name, + node.typeParameters, node.parameters, node.type, node.body); +} + +export function processSendableType(node: ts.TypeAliasDeclaration): ts.TypeAliasDeclaration { + return ts.factory.createTypeAliasDeclaration(undefined, node.name, node.typeParameters, node.type); +} diff --git a/compiler/src/interop/src/process_source_file.ts b/compiler/src/interop/src/process_source_file.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c42d170656e7999fc2805f3a4768308b7f81daa --- /dev/null +++ b/compiler/src/interop/src/process_source_file.ts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 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 { writeFileSyncByString } from './ark_utils'; +import { projectConfig } from '../main'; +import { + ESMODULE, + ARK +} from './pre_define'; +import { logger } from './compile_info'; + +module.exports = function processSourcefile(source: string): string { + if (projectConfig.compileMode === ESMODULE && process.env.compilerType && + process.env.compilerType === ARK) { + writeFileSyncByString(this.resourcePath, source, projectConfig, logger); + } + return source; +}; diff --git a/compiler/src/interop/src/process_struct_componentV2.ts b/compiler/src/interop/src/process_struct_componentV2.ts new file mode 100644 index 0000000000000000000000000000000000000000..88b64b613293034f283f8a79c091b50c28963d6a --- /dev/null +++ b/compiler/src/interop/src/process_struct_componentV2.ts @@ -0,0 +1,745 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ts from 'typescript'; + +import { + LogInfo, + LogType, + addLog, + removeDecorator +} from './utils'; +import { + COMPONENT_CONSTRUCTOR_PARENT, + COMPONENT_CONSTRUCTOR_PARAMS, + COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU, + ELMTID, + COMPONENT_PARAMS_LAMBDA_FUNCTION, + CUSTOM_COMPONENT_EXTRAINFO, + COMPONENT_CONSTRUCTOR_UNDEFINED, + REUSABLE_V2_INNER_DECORATOR, + REFLECT, + DEFINE_PROPERTY, + BASE_CLASS, + PROTOTYPE, + IS_REUSABLE_, + GET_ATTRIBUTE, +} from './pre_define'; +import constantDefine from './constant_define'; +import createAstNodeUtils from './create_ast_node_utils'; +import { + updateHeritageClauses, + processComponentMethod, + BuildCount, + addRerenderFunc, + validateBuildMethodCount, + getEntryNameFunction, + FreezeParamType, + decoratorAssignParams +} from './process_component_class'; +import { isReuseInV2 } from './process_custom_component'; +import { judgeBuilderParamAssignedByBuilder } from './process_component_member'; +import { + componentCollection, + builderParamObjectCollection +} from './validate_ui_syntax'; +import logMessageCollection from './log_message_collection'; +import { globalProgram } from '../main'; + +export class ParamDecoratorInfo { + initializer: ts.Expression; + hasRequire: boolean = false; +} + +export class StructInfo { + isComponentV1: boolean = false; + isComponentV2: boolean = false; + isCustomDialog: boolean = false; + isReusable: boolean = false; + isReusableV2: boolean = false; + structName: string = ''; + updatePropsDecoratorsV1: string[] = []; + linkDecoratorsV1: string[] = []; + paramDecoratorMap: Map = new Map(); + eventDecoratorMap: Map = new Map(); + localDecoratorSet: Set = new Set(); + providerDecoratorSet: Set = new Set(); + consumerDecoratorSet: Set = new Set(); + builderParamDecoratorSet: Set = new Set(); + regularSet: Set = new Set(); + propertiesMap: Map = new Map(); + staticPropertySet: Set = new Set(); + computedDecoratorSet: Set = new Set(); + monitorDecoratorSet: Set = new Set(); +} + +const structMapInEts: Map = new Map(); + +function getOrCreateStructInfo(key: string): StructInfo { + let structInfo: StructInfo = structMapInEts.get(key); + if (!structInfo) { + structInfo = new StructInfo(); + structInfo.structName = key; + structMapInEts.set(key, structInfo); + } + return structInfo; +} + +/** + * import * as a from './common' + * a.struct() + */ +function getAliasStructInfo(node: ts.CallExpression): StructInfo { + let aliasStructInfo: StructInfo; + if (node.expression && structMapInEts.has(node.expression.getText())) { + aliasStructInfo = structMapInEts.get(node.expression.getText()); + } + return aliasStructInfo; +} + +function processStructComponentV2(node: ts.StructDeclaration, log: LogInfo[], + context: ts.TransformationContext, StateManagementV2: { hasReusableV2: boolean }): ts.ClassDeclaration { + const isReusableV2: boolean = node.name && ts.isIdentifier(node.name) && isReuseInV2(node.name.getText()); + if (isReusableV2) { + StateManagementV2.hasReusableV2 = true; + } + return ts.factory.createClassDeclaration(isReusableV2 ? + ts.concatenateDecoratorsAndModifiers( + [ts.factory.createDecorator(ts.factory.createIdentifier(REUSABLE_V2_INNER_DECORATOR))], + ts.getModifiers(node) + ) : + ts.getModifiers(node), + node.name, + node.typeParameters, + updateHeritageClauses(node, log, true), + processStructMembersV2(node, context, log) + ); +} + +function createReusableV2ReflectFunction(): ts.FunctionDeclaration { + const reflectStatement: ts.ExpressionStatement = ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(REFLECT), ts.factory.createIdentifier(DEFINE_PROPERTY) + ), undefined, + [ + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(BASE_CLASS), ts.factory.createIdentifier(PROTOTYPE) + ), + ts.factory.createStringLiteral(IS_REUSABLE_), + ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(GET_ATTRIBUTE), + ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createTrue() + ) + )], + false + ) + ] + ) + ); + const parameter: ts.ParameterDeclaration = ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createIdentifier(BASE_CLASS), + undefined, + undefined, + undefined + ); + return ts.factory.createFunctionDeclaration( + undefined, + undefined, + ts.factory.createIdentifier(REUSABLE_V2_INNER_DECORATOR), + undefined, + [parameter], + undefined, + ts.factory.createBlock([reflectStatement]) + ); +} + +function processStructMembersV2(node: ts.StructDeclaration, context: ts.TransformationContext, + log: LogInfo[]): ts.ClassElement[] { + const structName: string = node.name.getText(); + const newMembers: ts.ClassElement[] = []; + const buildCount: BuildCount = { count: 0 }; + const structInfo: StructInfo = getOrCreateStructInfo(structName); + const addStatementsInConstructor: ts.Statement[] = []; + const addStatementsInResetOnReuse: ts.Statement[] = []; + const paramStatementsInStateVarsMethod: ts.Statement[] = []; + const structDecorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + const freezeParam: FreezeParamType = { componentFreezeParam: undefined }; + decoratorAssignParams(structDecorators, context, freezeParam); + traverseStructInfo(structInfo, addStatementsInConstructor, paramStatementsInStateVarsMethod, addStatementsInResetOnReuse); + node.members.forEach((member: ts.ClassElement) => { + if (ts.isGetAccessor(member) && member.modifiers?.some(isComputedDecorator) && member.name && + ts.isIdentifier(member.name)) { + const symbol: ts.Symbol = globalProgram.checker?.getSymbolAtLocation(member.name); + validateComputedGetter(symbol, log); + } + if (ts.isConstructorDeclaration(member)) { + processStructConstructorV2(node.members, newMembers, addStatementsInConstructor, freezeParam); + createResetStateVarsOnReuse(structInfo, newMembers, addStatementsInResetOnReuse); + return; + } else if (ts.isPropertyDeclaration(member)) { + newMembers.push(processComponentProperty(member, structInfo, log)); + return; + } else if (ts.isMethodDeclaration(member) && member.name) { + const newMethodNode: ts.MethodDeclaration = processComponentMethod(member, context, log, buildCount); + if (newMethodNode) { + newMembers.push(newMethodNode); + } + return; + } + newMembers.push(member); + }); + validateBuildMethodCount(buildCount, node.name, log); + updateStateVarsMethodNode(paramStatementsInStateVarsMethod, newMembers); + newMembers.push(addRerenderFunc([])); + if (componentCollection.entryComponent === structName) { + newMembers.push(getEntryNameFunction(componentCollection.entryComponent)); + } + return newMembers; +} + +function isComputedDecorator(decorator: ts.Decorator): boolean { + return ts.isDecorator(decorator) && ts.isIdentifier(decorator.expression) && + decorator.expression.escapedText.toString() === constantDefine.COMPUTED; +} + +function validateComputedGetter(symbol: ts.Symbol, log: LogInfo[]): void { + if (symbol && symbol.declarations) { + symbol.declarations.forEach((declaration: ts.Declaration) => { + logMessageCollection.checkComputedGetter(symbol, declaration, log); + }); + } +} + +function traverseStructInfo(structInfo: StructInfo, + addStatementsInConstructor: ts.Statement[], paramStatementsInStateVarsMethod: ts.Statement[], + addStatementsInResetOnReuse: ts.Statement[]): void { + const needInitFromParams: string[] = [...structInfo.builderParamDecoratorSet, + ...structInfo.eventDecoratorMap.keys()]; + for (const property of structInfo.propertiesMap) { + if (!structInfo.staticPropertySet.has(property[0])) { + setPropertyStatement(structInfo, addStatementsInConstructor, property[0], property[1], + needInitFromParams, addStatementsInResetOnReuse); + } + } + for (const param of structInfo.paramDecoratorMap) { + if (!structInfo.staticPropertySet.has(param[0])) { + paramStatementsInStateVarsMethod.push(updateParamNode(param[0])); + } + } +} + +function setPropertyStatement(structInfo: StructInfo, addStatementsInConstructor: ts.Statement[], + propName: string, initializer: ts.Expression, needInitFromParams: string[], + addStatementsInResetOnReuse: ts.Statement[]): void { + if (needInitFromParams.includes(propName)) { + if (structInfo.eventDecoratorMap.has(propName)) { + const eventDeclaration: ts.PropertyDeclaration = structInfo.eventDecoratorMap.get(propName); + addStatementsInConstructor.push( + createPropertyAssignNode(propName, initializer || getDefaultValueForEvent(eventDeclaration, false), true)); + addStatementsInResetOnReuse.push( + createPropertyAssignNode(propName, initializer || getDefaultValueForEvent(eventDeclaration, true), true)); + } else { + const builderParamExpression: ts.ExpressionStatement = createPropertyAssignNode(propName, initializer, true); + addStatementsInConstructor.push(builderParamExpression); + addStatementsInResetOnReuse.push(builderParamExpression); + } + } else if (structInfo.paramDecoratorMap.has(propName)) { + const paramProperty: ParamDecoratorInfo = structInfo.paramDecoratorMap.get(propName); + addStatementsInConstructor.push(createInitOrUpdateParam(propName, paramProperty.initializer, true)); + addStatementsInResetOnReuse.push(createInitOrUpdateParam(propName, paramProperty.initializer, false)); + } else if (structInfo.consumerDecoratorSet.has(propName)) { + addStatementsInConstructor.push(createPropertyAssignNode(propName, initializer, false)); + addStatementsInResetOnReuse.push(createResetNode(propName, constantDefine.RESET_CONSUMER, initializer)); + } else if (structInfo.regularSet.has(propName)) { + addStatementsInConstructor.push(createPropertyAssignNode(propName, initializer, false)); + } else if (structInfo.computedDecoratorSet.has(propName)) { + addStatementsInResetOnReuse.push(createResetNode(propName, constantDefine.RESET_COMPUTED)); + } else { + const propertyAssignNode: ts.ExpressionStatement = createPropertyAssignNode(propName, initializer, false); + addStatementsInConstructor.push(propertyAssignNode); + addStatementsInResetOnReuse.push(propertyAssignNode); + } +} + +function getDefaultValueForEvent(node: ts.PropertyDeclaration, isResetOnReuse: boolean = false): ts.Expression { + let param: ts.NodeArray; + if (node.type && ts.isFunctionTypeNode(node.type) && node.type.parameters && node.type.parameters.length) { + param = node.type.parameters; + } + return ts.factory.createArrowFunction(undefined, undefined, isResetOnReuse && param ? param : [], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock([], false)); +} + +function createPropertyAssignNode(propName: string, initializer: ts.Expression, + initFromParams: boolean): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(propName) + ), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + setInitValue(propName, initializer, initFromParams) + )); +} + +function createResetNode(propName: string, type: string, initializer: ts.Expression = null): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(type) + ), + undefined, + type === constantDefine.RESET_CONSUMER ? + [ + ts.factory.createStringLiteral(propName), + initializer ? initializer : ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) + ] : [ + ts.factory.createStringLiteral(propName) + ] + )); +} + +function processComponentProperty(member: ts.PropertyDeclaration, structInfo: StructInfo, + log: LogInfo[]): ts.PropertyDeclaration { + const propName: string = member.name.getText(); + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(member); + let initializer: ts.Expression; + if (!structInfo.regularSet.has(propName) && !member.type) { + checkV2ComponentMemberType(member.name, propName, log); + } + if (structInfo.staticPropertySet.has(propName)) { + initializer = member.initializer; + } + if (structInfo.paramDecoratorMap.has(propName)) { + return processParamProperty(member, decorators, initializer); + } + if (structInfo.builderParamDecoratorSet.has(propName)) { + return processBuilderParamProperty(member, log, decorators, initializer); + } + return ts.factory.updatePropertyDeclaration(member, + ts.concatenateDecoratorsAndModifiers(decorators, ts.getModifiers(member)), + member.name, member.questionToken, member.type, initializer); +} + +function checkV2ComponentMemberType(node: ts.Node, propName: string, log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The property '${propName}' must specify a type.`, + pos: node.getStart(), + code: '10905328' + }); +} + +function processParamProperty(member: ts.PropertyDeclaration, + decorators: readonly ts.Decorator[], initializer: ts.Expression): ts.PropertyDeclaration { + const newDecorators: readonly ts.Decorator[] = removeDecorator(decorators, constantDefine.REQUIRE); + return ts.factory.updatePropertyDeclaration(member, + ts.concatenateDecoratorsAndModifiers(newDecorators, ts.getModifiers(member)), + member.name, member.questionToken, member.type, initializer); +} + +function processBuilderParamProperty(member: ts.PropertyDeclaration, log: LogInfo[], + decorators: readonly ts.Decorator[], initializer: ts.Expression): ts.PropertyDeclaration { + if (judgeBuilderParamAssignedByBuilder(member)) { + log.push({ + type: LogType.ERROR, + message: 'BuilderParam property can only initialized by Builder function.', + pos: member.getStart(), + code: '10905107' + }); + } + const newDecorators: readonly ts.Decorator[] = removeDecorator(decorators, constantDefine.BUILDER_PARAM); + return ts.factory.updatePropertyDeclaration(member, + ts.concatenateDecoratorsAndModifiers(newDecorators, ts.getModifiers(member)), + member.name, member.questionToken, member.type, initializer); +} + +function setInitValue(propName: string, initializer: ts.Expression, + initFromParams: boolean): ts.Expression { + let initNode: ts.Expression = initializer || + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED); + if (initFromParams) { + initNode = createInitNode(propName, initNode); + } + return initNode; +} + +function createInitNode(propName: string, defaultValue: ts.Expression): ts.Expression { + return ts.factory.createConditionalExpression( + ts.factory.createBinaryExpression( + ts.factory.createStringLiteral(propName), + ts.factory.createToken(ts.SyntaxKind.InKeyword), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS) + ), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS), + ts.factory.createIdentifier(propName) + ), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + defaultValue + ); +} + +function parseComponentProperty(node: ts.StructDeclaration, structInfo: StructInfo, log: LogInfo[], + sourceFileNode: ts.SourceFile): void { + node.members.forEach((member: ts.ClassElement) => { + if (ts.isPropertyDeclaration(member)) { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(member); + const modifiers: readonly ts.Modifier[] = ts.getModifiers(member); + structInfo.propertiesMap.set(member.name.getText(), member.initializer); + parsePropertyDecorator(member, decorators, structInfo, log, sourceFileNode); + parsePropertyModifiers(member.name.getText(), structInfo, modifiers); + } else if (ts.isGetAccessor(member)) { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(member); + parseGetAccessor(member, decorators, structInfo); + } else if (ts.isMethodDeclaration(member)) { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(member); + parseMethodDeclaration(member, decorators, structInfo); + } + }); +} + +function parseGetAccessor(member: ts.GetAccessorDeclaration, decorators: readonly ts.Decorator[], + structInfo: StructInfo): void { + if (decorators.length && decorators.some((value: ts.Decorator) => getDecoratorName(value) === constantDefine.COMPUTED_DECORATOR)) { + structInfo.propertiesMap.set(member.name.getText(), undefined); + structInfo.computedDecoratorSet.add(member.name.getText()); + } +} + +function parseMethodDeclaration(member: ts.MethodDeclaration, decorators: readonly ts.Decorator[], + structInfo: StructInfo): void { + if (decorators.length && decorators.some((value: ts.Decorator) => getDecoratorName(value) === constantDefine.MONITOR_DECORATOR)) { + structInfo.monitorDecoratorSet.add(member.name.getText()); + } +} + +function getDecoratorName(decorator: ts.Decorator): string { + return decorator.getText().replace(/\([^\(\)]*\)/, '').trim(); +} + +function parsePropertyModifiers(propName: string, structInfo: StructInfo, + modifiers: readonly ts.Modifier[]): void { + if (modifiers && modifiers.length) { + const isStatic: boolean = modifiers.some((item: ts.Modifier) => { + return item.kind === ts.SyntaxKind.StaticKeyword; + }); + if (isStatic) { + structInfo.staticPropertySet.add(propName); + } + } +} + +class PropertyDecorator { + hasParam: boolean = false; + hasRequire: boolean = false; + hasOnce: boolean = false; + hasEvent: boolean = false; +} + +const decoratorsFunc: Record = { + 'Param': parseParamDecorator, + 'Event': parseEventDecorator, + 'Require': parseRequireDecorator, + 'Once': parseOnceDecorator, + 'Local': parseLocalDecorator, + 'BuilderParam': parseBuilderParamDecorator, + 'Provider': parseProviderDecorator, + 'Consumer': parseConsumerDecorator +}; + +function parsePropertyDecorator(member: ts.PropertyDeclaration, decorators: readonly ts.Decorator[], + structInfo: StructInfo, log: LogInfo[], sourceFileNode: ts.SourceFile): void { + const propertyDecorator: PropertyDecorator = new PropertyDecorator(); + let isRegular: boolean = true; + for (let i = 0; i < decorators.length; i++) { + const originalName: string = getDecoratorName(decorators[i]); + const name: string = originalName.replace('@', '').trim(); + if (decoratorsFunc[name]) { + decoratorsFunc[name](propertyDecorator, member, structInfo); + } + if (constantDefine.COMPONENT_MEMBER_DECORATOR_V2.includes(originalName) || + originalName === constantDefine.DECORATOR_BUILDER_PARAM) { + isRegular = false; + } + } + if (isRegular) { + structInfo.regularSet.add(member.name.getText()); + } + checkPropertyDecorator(propertyDecorator, member, log, sourceFileNode, structInfo); +} + +function checkPropertyDecorator(propertyDecorator: PropertyDecorator, + member: ts.PropertyDeclaration, log: LogInfo[], sourceFileNode: ts.SourceFile, + structInfo: StructInfo): void { + if (log && sourceFileNode) { + checkParamDecorator(propertyDecorator, member, log, sourceFileNode, structInfo); + } +} + +function parseParamDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration, + structInfo: StructInfo): void { + propertyDecorator.hasParam = true; + let paramDecoratorInfo: ParamDecoratorInfo = structInfo.paramDecoratorMap.get(member.name.getText()); + if (!paramDecoratorInfo) { + paramDecoratorInfo = new ParamDecoratorInfo(); + } + paramDecoratorInfo.initializer = member.initializer; + structInfo.paramDecoratorMap.set(member.name.getText(), paramDecoratorInfo); +} + +function parseEventDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration, + structInfo: StructInfo): void { + propertyDecorator.hasEvent = true; + structInfo.eventDecoratorMap.set(member.name.getText(), member); +} + +function parseRequireDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration, + structInfo: StructInfo): void { + propertyDecorator.hasRequire = true; + let paramDecoratorInfo: ParamDecoratorInfo = structInfo.paramDecoratorMap.get(member.name.getText()); + if (!paramDecoratorInfo) { + paramDecoratorInfo = new ParamDecoratorInfo(); + } + paramDecoratorInfo.hasRequire = true; + structInfo.paramDecoratorMap.set(member.name.getText(), paramDecoratorInfo); +} + +function parseOnceDecorator(propertyDecorator: PropertyDecorator): void { + propertyDecorator.hasOnce = true; +} + +function parseLocalDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration, + structInfo: StructInfo): void { + structInfo.localDecoratorSet.add(member.name.getText()); +} + +function parseProviderDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration, + structInfo: StructInfo): void { + structInfo.providerDecoratorSet.add(member.name.getText()); +} + +function parseConsumerDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration, + structInfo: StructInfo): void { + structInfo.consumerDecoratorSet.add(member.name.getText()); +} + +function parseBuilderParamDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration, + structInfo: StructInfo): void { + let builderParamSet: Set = builderParamObjectCollection.get(structInfo.structName); + if (!builderParamSet) { + builderParamSet = new Set(); + } + builderParamSet.add(member.name.getText()); + builderParamObjectCollection.set(structInfo.structName, builderParamSet); + structInfo.builderParamDecoratorSet.add(member.name.getText()); +} + +function checkHasBuilderParamDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration, + sourceFileNode: ts.SourceFile, structInfo: StructInfo): boolean { + let checkResult: boolean = false; + if (StructInfo) { + checkResult = structInfo.builderParamDecoratorSet.has(member.name.getText()); + } + return checkResult; +} + +function checkParamDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration, log: LogInfo[], + sourceFileNode: ts.SourceFile, structInfo: StructInfo): void { + if (propertyDecorator.hasParam && !member.initializer && !propertyDecorator.hasRequire) { + const message: string = 'When a variable decorated with @Param is not assigned a default value, ' + + 'it must also be decorated with @Require.'; + addLog(LogType.ERROR, message, member.getStart(), log, sourceFileNode, { code: '10905327' }); + } + if (propertyDecorator.hasOnce && !propertyDecorator.hasParam) { + const message: string = 'When a variable decorated with @Once, it must also be decorated with @Param.'; + addLog(LogType.ERROR, message, member.getStart(), log, sourceFileNode, { code: '10905326' }); + } + if (propertyDecorator.hasRequire && !propertyDecorator.hasParam && !checkHasBuilderParamDecorator(propertyDecorator, + member, sourceFileNode, structInfo)) { + const message: string = 'In a struct decorated with @ComponentV2, @Require can only be used with @Param.'; + addLog(LogType.ERROR, message, member.getStart(), log, sourceFileNode, { code: '10905325' }); + } +} + +function processStructConstructorV2(members: ts.NodeArray, newMembers: ts.ClassElement[], + paramStatements: ts.Statement[], freezeParam: FreezeParamType): void { + const freezeParamNode: ts.Expression = freezeParam.componentFreezeParam ? + freezeParam.componentFreezeParam : undefined; + const constructorIndex: number = members.findIndex((item: ts.ClassElement) => { + return ts.isConstructorDeclaration(item); + }); + if (constructorIndex !== -1) { + const constructorNode: ts.ConstructorDeclaration = members[constructorIndex] as ts.ConstructorDeclaration; + newMembers.splice(constructorIndex, 0, ts.factory.updateConstructorDeclaration(constructorNode, ts.getModifiers(constructorNode), + createConstructorParams(), updateConstructorBody(constructorNode.body, paramStatements, freezeParamNode))); + } +} + +function createConstructorParams(): ts.ParameterDeclaration[] { + const paramNames: string[] = [COMPONENT_CONSTRUCTOR_PARENT, COMPONENT_CONSTRUCTOR_PARAMS, + COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU, ELMTID, COMPONENT_PARAMS_LAMBDA_FUNCTION, + CUSTOM_COMPONENT_EXTRAINFO]; + return paramNames.map((name: string) => { + return createAstNodeUtils.createParameterDeclaration(name); + }); +} + +function updateConstructorBody(node: ts.Block, paramStatements: ts.Statement[], + freezeParamNode: ts.Expression): ts.Block { + const body: ts.Statement[] = [createSuperV2()]; + if (node.statements) { + body.push(...node.statements); + } + body.push(...paramStatements, createAstNodeUtils.createFinalizeConstruction(freezeParamNode)); + return ts.factory.createBlock(body, true); +} + +function createResetStateVarsOnReuse(structInfo: StructInfo, newMembers: ts.ClassElement[], + addStatementsInResetOnReuse: ts.Statement[]): void { + if (structInfo.monitorDecoratorSet.size) { + addStatementsInResetOnReuse.push(generateResetMonitor()); + } + const resetOnReuseNode: ts.MethodDeclaration = ts.factory.createMethodDeclaration( + [ts.factory.createToken(ts.SyntaxKind.PublicKeyword)], + undefined, + ts.factory.createIdentifier(constantDefine.RESET_STATE_VARS_METHOD), + undefined, + undefined, + [ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS), + undefined, + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier(constantDefine.OBJECT_TYPE), + undefined + ), + undefined + )], + ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword), + ts.factory.createBlock( + addStatementsInResetOnReuse, + true + ) + ); + newMembers.push(resetOnReuseNode); +} + +function generateResetMonitor(): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(constantDefine.RESET_MONITORS_ON_REUSE) + ), + undefined, + [] + )); +} + +function createSuperV2(): ts.Statement { + const paramNames: string[] = [COMPONENT_CONSTRUCTOR_PARENT, ELMTID, CUSTOM_COMPONENT_EXTRAINFO]; + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createSuper(), undefined, paramNames.map((name: string) => { + return ts.factory.createIdentifier(name); + }))); +} + +function createInitOrUpdateParam(propName: string, initializer: ts.Expression, isInit: boolean): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + ts.factory.createIdentifier(isInit ? constantDefine.INIT_PARAM : constantDefine.RESET_PARAM)), + undefined, + [ + ts.factory.createStringLiteral(propName), + ts.factory.createConditionalExpression(ts.factory.createParenthesizedExpression( + ts.factory.createBinaryExpression(ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS), + ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + createParamBinaryNode(propName) + )), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS), ts.factory.createIdentifier(propName) + ), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + initializer || ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) + ) + ])); +} + +function updateParamNode(propName: string): ts.IfStatement { + return ts.factory.createIfStatement(createParamBinaryNode(propName), + ts.factory.createBlock([ + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + ts.factory.createIdentifier(constantDefine.UPDATE_PARAM)), + undefined, + [ + ts.factory.createStringLiteral(propName), + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS), ts.factory.createIdentifier(propName) + ) + ]))], true)); +} + +function createParamBinaryNode(propName: string): ts.BinaryExpression { + return ts.factory.createBinaryExpression( + ts.factory.createStringLiteral(propName), + ts.factory.createToken(ts.SyntaxKind.InKeyword), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS) + ); +} + +function updateStateVarsMethodNode(paramStatements: ts.Statement[], newMembers: ts.ClassElement[]): void { + if (paramStatements.length) { + newMembers.push(ts.factory.createMethodDeclaration([ts.factory.createToken(ts.SyntaxKind.PublicKeyword)], + undefined, ts.factory.createIdentifier(constantDefine.UPDATE_STATE_VARS), undefined, undefined, + [createAstNodeUtils.createParameterDeclaration(COMPONENT_CONSTRUCTOR_PARAMS)], undefined, + ts.factory.createBlock([emptyJudgeForParamsNode(), ...paramStatements], true))); + } +} + +function emptyJudgeForParamsNode(): ts.IfStatement { + return ts.factory.createIfStatement(ts.factory.createBinaryExpression( + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS), + ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) + ), ts.factory.createBlock([ts.factory.createReturnStatement(undefined)], true), undefined); +} + +function resetStructMapInEts(): void { + structMapInEts.clear(); +} + +export default { + getOrCreateStructInfo: getOrCreateStructInfo, + processStructComponentV2: processStructComponentV2, + parseComponentProperty: parseComponentProperty, + resetStructMapInEts: resetStructMapInEts, + getAliasStructInfo: getAliasStructInfo, + createReusableV2ReflectFunction: createReusableV2ReflectFunction +}; diff --git a/compiler/src/interop/src/process_system_module.ts b/compiler/src/interop/src/process_system_module.ts new file mode 100644 index 0000000000000000000000000000000000000000..4588bccfc87504a545ffbf145969d2d681bd8b92 --- /dev/null +++ b/compiler/src/interop/src/process_system_module.ts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 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 { + processSystemApi, + collectImportNames +} from './validate_ui_syntax'; +import { systemModules } from '../main'; + +module.exports = function processSystemModule(source: string): string { + const REG_IMPORT: RegExp = + /(import|export)\s+(.+)\s+from\s+['"](\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"](\S+)['"]\s*\)/g; + source.replace(REG_IMPORT, (item, item1, item2, item3, item4, item5) => { + const moduleRequest: string = item3 || item5; + if (/^@(system|ohos)\./i.test(moduleRequest.trim())) { + if (!systemModules.includes(moduleRequest.trim() + '.d.ts')) { + const message: string = + `Cannot find module '${moduleRequest}' or its corresponding type declarations.`; + this.emitError(`BUILDERROR File: ${this.resourcePath}\n ${message}`); + } + } + return item; + }); + source = processSystemApi(source, false, this.resourcePath, true); + collectImportNames(source, this.resourcePath); + return source; +}; diff --git a/compiler/src/interop/src/process_ui_syntax.ts b/compiler/src/interop/src/process_ui_syntax.ts new file mode 100644 index 0000000000000000000000000000000000000000..03f0093a6ecb29dc55c954279b3c54d553120403 --- /dev/null +++ b/compiler/src/interop/src/process_ui_syntax.ts @@ -0,0 +1,2008 @@ +/* + * Copyright (c) 2021 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 ts from 'typescript'; +import path from 'path'; +import fs from 'fs'; + +import { + PAGE_ENTRY_FUNCTION_NAME, + PREVIEW_COMPONENT_FUNCTION_NAME, + STORE_PREVIEW_COMPONENTS, + GET_PREVIEW_FLAG_FUNCTION_NAME, + COMPONENT_CONSTRUCTOR_UNDEFINED, + BUILD_ON, + COMPONENT_BUILDER_DECORATOR, + COMPONENT_CONCURRENT_DECORATOR, + COMPONENT_SENDABLE_DECORATOR, + COMPONENT_EXTEND_DECORATOR, + COMPONENT_STYLES_DECORATOR, + RESOURCE, + RESOURCE_TYPE, + WORKER_OBJECT, + RESOURCE_NAME_ID, + RESOURCE_NAME_TYPE, + RESOURCE_NAME_PARAMS, + RESOURCE_RAWFILE, + RESOURCE_NAME_BUNDLE, + RESOURCE_NAME_MODULE, + ATTRIBUTE_ANIMATETO_SET, + GLOBAL_CONTEXT, + INSTANCE, + SET_CONTROLLER_CTR_TYPE, + SET_CONTROLLER_METHOD, + JS_DIALOG, + CUSTOM_DIALOG_CONTROLLER_BUILDER, + ESMODULE, + EXTNAME_ETS, + GENERATE_ID, + _GENERATE_ID, + VIEWSTACKPROCESSOR, + STARTGETACCESSRECORDINGFOR, + ALLOCATENEWELMETIDFORNEXTCOMPONENT, + STOPGETACCESSRECORDING, + CARD_ENTRY_FUNCTION_NAME, + CARD_LOG_TYPE_COMPONENTS, + CARD_LOG_TYPE_DECORATORS, + CARD_LOG_TYPE_IMPORT, + COMPONENT_ANIMATABLE_EXTEND_DECORATOR, + CHECK_EXTEND_DECORATORS, + ELMTID, + ROUTENAME_NODE, + STORAGE_NODE, + STORAGE, + REGISTER_NAMED_ROUTE, + ROUTE_NAME, + PAGE_PATH, + ISINITIALRENDER, + CREATE_ANIMATABLE_PROPERTY, + UPDATE_ANIMATABLE_PROPERTY, + MY_IDS, + VIEW_STACK_PROCESSOR, + GET_AND_PUSH_FRAME_NODE, + COMPONENT_CONSTRUCTOR_PARENT, + WRAPBUILDER_FUNCTION, + FINISH_UPDATE_FUNC, + INTEGRATED_HSP, + FUNCTION, + PAGE_FULL_PATH, + LENGTH, + PUV2_VIEW_BASE, + CONTEXT_STACK +} from './pre_define'; +import { + componentInfo, + LogInfo, + LogType, + hasDecorator, + IFileLog, + getPossibleBuilderTypeParameter, + storedFileInfo, + ExtendResult, + startTimeStatisticsLocation, + stopTimeStatisticsLocation, + CompilationTimeStatistics, + getStoredFileInfo, + ProcessFileInfo, + RouterInfo, + EntryOptionValue, + judgeUseSharedStorageForExpresion, + createGetSharedForVariable, + createGetShared +} from './utils'; +import { writeFileSyncByNode } from './process_module_files'; +import { + componentCollection, + localStorageLinkCollection, + localStoragePropCollection +} from './validate_ui_syntax'; +import { + processComponentClass, + createParentParameter, + processBuildMember, + checkFinalizeConstruction, + checkContextStack +} from './process_component_class'; +import processImport, { + processImportModule +} from './process_import'; +import { + processComponentBlock, + bindComponentAttr, + getName, + createViewStackProcessorStatement, + parseGlobalBuilderParams, + BuilderParamsResult +} from './process_component_build'; +import { + BUILDIN_STYLE_NAMES, + CUSTOM_BUILDER_METHOD, + EXTEND_ATTRIBUTE, + INNER_STYLE_FUNCTION, + GLOBAL_STYLE_FUNCTION, + INTERFACE_NODE_SET, + ID_ATTRS, + GLOBAL_CUSTOM_BUILDER_METHOD +} from './component_map'; +import { + resources, + projectConfig, + partialUpdateConfig +} from '../main'; +import { + createCustomComponentNewExpression, + createViewCreate +} from './process_component_member'; +import { + assignComponentParams, + assignmentFunction +} from './process_custom_component'; +import { processDecorator } from './fast_build/ark_compiler/process_decorator'; +import { hasArkDecorator } from './fast_build/ark_compiler/utils'; +import { + checkTypeReference, + validateModuleSpecifier +} from './fast_build/system_api/api_check_utils'; +import constantDefine from './constant_define'; +import processStructComponentV2 from './process_struct_componentV2'; +import createAstNodeUtils from './create_ast_node_utils'; +import { + processSendableClass, + processSendableFunction, + processSendableType +} from './process_sendable'; +import { + routerOrNavPathWrite, + integratedHspType, + routerModuleType, + routerBundleOrModule +} from './process_module_package'; + +export let transformLog: IFileLog = new createAstNodeUtils.FileLog(); +export let contextGlobal: ts.TransformationContext; +export let resourceFileName: string = ''; +export const builderTypeParameter: { params: string[] } = { params: [] }; + +export function processUISyntax(program: ts.Program, ut = false, + compilationTime: CompilationTimeStatistics = null, filePath: string = ''): Function { + let entryNodeKey: ts.Expression; + return (context: ts.TransformationContext) => { + contextGlobal = context; + let pagesDir: string; + let pageFile: string; + let hasUseResource: boolean = false; + let hasStruct: boolean = false; + let StateManagementV2: { hasReusableV2: boolean } = { hasReusableV2: false }; + return (node: ts.SourceFile) => { + startTimeStatisticsLocation(compilationTime ? compilationTime.processUISyntaxTime : undefined); + pagesDir = path.resolve(path.dirname(node.fileName)); + resourceFileName = path.resolve(node.fileName); + pageFile = path.resolve(filePath !== '' ? filePath : node.fileName); + if (process.env.compiler === BUILD_ON || process.env.compileTool === 'rollup') { + storedFileInfo.transformCacheFiles[pageFile] = { + mtimeMs: fs.existsSync(pageFile) ? fs.statSync(pageFile).mtimeMs : 0, + children: [] + }; + transformLog.sourceFile = node; + preprocessIdAttrs(node.fileName); + if (!ut && (process.env.compileMode !== 'moduleJson' && + path.resolve(node.fileName) === path.resolve(projectConfig.projectPath, 'app.ets') || + /\.ts$/.test(node.fileName))) { + node = ts.visitEachChild(node, processResourceNode, context); + node = ts.factory.updateSourceFile(node, + insertImportModuleNode(Array.from(node.statements), hasUseResource)); + if (projectConfig.compileMode === ESMODULE && projectConfig.processTs === true) { + if (process.env.compileTool !== 'rollup') { + const processedNode: ts.SourceFile = ts.getTypeExportImportAndConstEnumTransformer(context)(node); + writeFileSyncByNode(processedNode, projectConfig, undefined); + } + } + const visitEachChildNode: ts.SourceFile = ts.visitEachChild(node, visitor, context); + stopTimeStatisticsLocation(compilationTime ? compilationTime.processUISyntaxTime : undefined); + return visitEachChildNode; + } + const id: number = ++componentInfo.id; + node = ts.visitEachChild(node, processAllNodes, context); + if(context.getCompilerOptions().etsAnnotationsEnable){ + node = ts.getAnnotationTransformer()(context)(node); + } + node = createEntryNode(node, context, entryNodeKey, id); + GLOBAL_STYLE_FUNCTION.forEach((block, styleName) => { + BUILDIN_STYLE_NAMES.delete(styleName); + }); + GLOBAL_STYLE_FUNCTION.clear(); + const statements: ts.Statement[] = Array.from(node.statements); + if (!partialUpdateConfig.partialUpdateMode) { + generateId(statements, node); + } + INTERFACE_NODE_SET.forEach(item => { + statements.unshift(item); + }) + if (StateManagementV2.hasReusableV2) { + statements.unshift(processStructComponentV2.createReusableV2ReflectFunction()); + } + if (partialUpdateConfig.partialUpdateMode && hasStruct) { + if (storedFileInfo.hasLocalBuilderInFile) { + statements.unshift(checkContextStack()); + } + statements.unshift(checkFinalizeConstruction()); + } + createNavigationInit(resourceFileName, statements); + insertImportModuleNode(statements, hasUseResource); + node = ts.factory.updateSourceFile(node, statements); + INTERFACE_NODE_SET.clear(); + if (projectConfig.compileMode === ESMODULE && projectConfig.processTs === true) { + if (process.env.compileTool !== 'rollup') { + const processedNode: ts.SourceFile = ts.getTypeExportImportAndConstEnumTransformer(context)(node); + writeFileSyncByNode(processedNode, projectConfig, undefined); + } + } + const visitEachChildNode: ts.SourceFile = ts.visitEachChild(node, visitor, context); + stopTimeStatisticsLocation(compilationTime ? compilationTime.processUISyntaxTime : undefined); + return visitEachChildNode; + } else { + stopTimeStatisticsLocation(compilationTime ? compilationTime.processUISyntaxTime : undefined); + return node; + } + }; + + function entryKeyNode(node: ts.Node): ts.Expression { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (node && decorators && decorators.length) { + decorators.forEach(item => { + if (item.expression && ts.isCallExpression(item.expression) && ts.isIdentifier(item.expression.expression) && + item.expression.expression.escapedText.toString() === 'Entry' && item.expression.arguments && + item.expression.arguments.length && ts.isIdentifier(item.expression.arguments[0])) { + entryNodeKey = item.expression.arguments[0]; + } + }); + } + return entryNodeKey; + } + + function isESObjectNode(node: ts.Node): boolean { + if (node.kind === ts.SyntaxKind.TypeReference) { + const n: TypeReferenceNode = node as TypeReferenceNode; + if (n.typeName?.kind === ts.SyntaxKind.Identifier && (n.typeName as ts.Identifier).escapedText === 'ESObject') { + return true; + } + } + return false; + } + + function processAllNodes(node: ts.Node): ts.Node { + if (projectConfig.compileMode === 'esmodule' && process.env.compileTool === 'rollup' && + ts.isImportDeclaration(node)) { + startTimeStatisticsLocation(compilationTime ? compilationTime.processImportTime : undefined); + processImportModule(node, pageFile, transformLog.errors); + stopTimeStatisticsLocation(compilationTime ? compilationTime.processImportTime : undefined); + } else if ((projectConfig.compileMode !== 'esmodule' || process.env.compileTool !== 'rollup') && + (ts.isImportDeclaration(node) || ts.isImportEqualsDeclaration(node) || + ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier))) { + processImport(node, pagesDir, transformLog.errors); + } + if (ts.isStructDeclaration(node)) { + hasStruct = true; + componentCollection.currentClassName = node.name.getText(); + componentCollection.entryComponent === componentCollection.currentClassName && entryKeyNode(node); + startTimeStatisticsLocation(compilationTime ? compilationTime.processComponentClassTime : undefined); + node = processStructComponentV2.getOrCreateStructInfo(componentCollection.currentClassName).isComponentV2 ? + processStructComponentV2.processStructComponentV2(node, transformLog.errors, context, StateManagementV2) : + processComponentClass(node, context, transformLog.errors, program); + stopTimeStatisticsLocation(compilationTime ? compilationTime.processComponentClassTime : undefined); + componentCollection.currentClassName = null; + INNER_STYLE_FUNCTION.forEach((block, styleName) => { + BUILDIN_STYLE_NAMES.delete(styleName); + }); + INNER_STYLE_FUNCTION.clear(); + } else if (ts.isFunctionDeclaration(node)) { + if (hasDecorator(node, COMPONENT_EXTEND_DECORATOR, null, transformLog.errors)) { + node = processExtend(node, transformLog.errors, COMPONENT_EXTEND_DECORATOR); + // @ts-ignore + if (node && node.illegalDecorators) { + // @ts-ignore + node.illegalDecorators = undefined; + } + } else if (hasDecorator(node, COMPONENT_BUILDER_DECORATOR) && node.name && node.body && + ts.isBlock(node.body)) { + storedFileInfo.processBuilder = true; + storedFileInfo.processGlobalBuilder = true; + CUSTOM_BUILDER_METHOD.add(node.name.getText()); + builderTypeParameter.params = getPossibleBuilderTypeParameter(node.parameters); + const parameters: ts.NodeArray = + ts.factory.createNodeArray(Array.from(node.parameters)); + parameters.push(createParentParameter()); + if (projectConfig.optLazyForEach) { + parameters.push(initializeMYIDS()); + } + storedFileInfo.builderLikeCollection = CUSTOM_BUILDER_METHOD; + const builderParamsResult: BuilderParamsResult = { firstParam: null }; + parseGlobalBuilderParams(node.parameters, builderParamsResult); + const componentBlock: ts.Block = processComponentBlock(node.body, false, transformLog.errors, false, true, + node.name.getText(), undefined, true, builderParamsResult, true); + node = ts.factory.updateFunctionDeclaration(node, ts.getModifiers(node), + node.asteriskToken, node.name, node.typeParameters, parameters, node.type, + componentBlock); + builderParamsResult.firstParam = null; + // @ts-ignore + if (node && node.illegalDecorators) { + // @ts-ignore + node.illegalDecorators = undefined; + } + builderTypeParameter.params = []; + node = processBuildMember(node, context, transformLog.errors, true); + storedFileInfo.processBuilder = false; + storedFileInfo.processGlobalBuilder = false; + } else if (hasDecorator(node, COMPONENT_STYLES_DECORATOR)) { + if (node.parameters.length === 0) { + node = undefined; + } else { + transformLog.errors.push({ + type: LogType.ERROR, + message: `@Styles can't have parameters.`, + pos: node.getStart(), + code: '10905110' + }); + } + } else if (hasDecorator(node, COMPONENT_CONCURRENT_DECORATOR)) { + // ark compiler's feature + node = processConcurrent(node); + if (node && node.illegalDecorators) { + // @ts-ignore + node.illegalDecorators = undefined; + } + } else if (hasArkDecorator(node, COMPONENT_SENDABLE_DECORATOR)) { + // ark compiler's feature + node = processSendableFunction(node); + if (node && node.illegalDecorators) { + node.illegalDecorators = undefined; + } + } else if (hasDecorator(node, COMPONENT_ANIMATABLE_EXTEND_DECORATOR, null, transformLog.errors)) { + node = processExtend(node, transformLog.errors, COMPONENT_ANIMATABLE_EXTEND_DECORATOR); + // @ts-ignore + if (node && node.illegalDecorators) { + // @ts-ignore + node.illegalDecorators = undefined; + } + } + } else if (isResource(node)) { + hasUseResource = true; + node = processResourceData(node as ts.CallExpression, filePath); + } else if (isWorker(node)) { + node = processWorker(node as ts.NewExpression); + } else if (isAnimateToOrImmediately(node)) { + node = processAnimateToOrImmediately(node as ts.CallExpression); + } else if (isCustomDialogController(node)) { + node = createCustomDialogController(node.parent, node, transformLog.errors); + } else if (isESObjectNode(node)) { + node = ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword); + } else if (ts.isDecorator(node)) { + // This processing is for mock instead of ui transformation + node = processDecorator(node); + } else if (isWrapBuilderFunction(node)) { + if (node.arguments && node.arguments[0] && (!ts.isIdentifier(node.arguments[0]) || + ts.isIdentifier(node.arguments[0]) && + !CUSTOM_BUILDER_METHOD.has(node.arguments[0].escapedText.toString()))) { + transformLog.errors.push({ + type: LogType.ERROR, + message: `wrapBuilder's parameter should be @Builder function.`, + pos: node.getStart(), + code: '10905109' + }); + } + } else if (ts.isClassDeclaration(node)) { + if (hasDecorator(node, COMPONENT_SENDABLE_DECORATOR)) { + if (projectConfig.compileHar && !projectConfig.useTsHar) { + let warnMessage: string = 'If you use @Sendable in js har, an exception will occur during runtime.\n' + + 'Please use @Sendable in ts har. You can configure {"name": "UseTsHar", ' + + '"value": "true"} in the "metadata" in the module.json5 file in har.'; + transformLog.errors.push({ + type: LogType.WARN, + message: warnMessage, + pos: node.getStart() + }); + } + node = processSendableClass(node); + } + } else if (ts.isTypeAliasDeclaration(node) && hasArkDecorator(node, COMPONENT_SENDABLE_DECORATOR)) { + node = processSendableType(node); + if (node && node.illegalDecorators) { + node.illegalDecorators = undefined; + } + } + return ts.visitEachChild(node, processAllNodes, context); + } + + function processResourceNode(node: ts.Node): ts.Node { + if (ts.isImportDeclaration(node)) { + validateModuleSpecifier(node.moduleSpecifier, transformLog.errors); + } else if (isResource(node)) { + hasUseResource = true; + node = processResourceData(node as ts.CallExpression, filePath); + } else if (ts.isTypeReferenceNode(node)) { + checkTypeReference(node, transformLog); + } + return ts.visitEachChild(node, processResourceNode, context); + } + + function isWrapBuilderFunction(node: ts.Node): boolean { + if (ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && + node.expression.escapedText.toString() === WRAPBUILDER_FUNCTION) { + return true; + } + return false; + } + + function visitor(node: ts.Node): ts.VisitResult { + return ts.visitEachChild(node, visitor, context); + } + }; +} + +export function globalBuilderParamAssignment(): ts.VariableStatement { + const contextStackCondition: ts.PropertyAccessExpression = ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(PUV2_VIEW_BASE), + ts.factory.createIdentifier(CONTEXT_STACK) + ); + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT), + undefined, + undefined, + ts.factory.createConditionalExpression( + ts.factory.createBinaryExpression( + contextStackCondition, + ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + ts.factory.createPropertyAccessExpression( + contextStackCondition, + ts.factory.createIdentifier(LENGTH) + ) + ), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createElementAccessExpression( + contextStackCondition, + ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + contextStackCondition, + ts.factory.createIdentifier(LENGTH) + ), + ts.factory.createToken(ts.SyntaxKind.MinusToken), + ts.factory.createNumericLiteral('1') + ) + ), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + ts.factory.createNull() + ) + )], + ts.NodeFlags.Const + )); +} + +function createNavigationInit(fileName: string, statements: ts.Statement[]): void { + const newStoredFileInfo: ProcessFileInfo = getStoredFileInfo(); + if (newStoredFileInfo.routerInfo.has(fileName)) { + const routerInfoArr: Array = newStoredFileInfo.routerInfo.get(fileName); + const builderStatements: ts.Statement[] = []; + routerInfoArr.forEach((item) => { + if (GLOBAL_CUSTOM_BUILDER_METHOD.has(item.buildFunction)) { + builderStatements.push(createNavigationRegister(item)); + } else { + transformLog.errors.push({ + type: LogType.ERROR, + message: `The buildFunction '${item.buildFunction}' configured in the routerMap json file does not exist.`, + code: '10904336' + }); + } + }); + statements.push(ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createParenthesizedExpression(ts.factory.createFunctionExpression( + undefined, undefined, undefined, undefined, [], undefined, + ts.factory.createBlock( + [ts.factory.createIfStatement(ts.factory.createBinaryExpression( + ts.factory.createTypeOfExpression(ts.factory.createIdentifier(constantDefine.NAVIGATION_BUILDER_REGISTER)), + ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createStringLiteral(FUNCTION)), + ts.factory.createBlock(builderStatements, true), undefined)], true) + )), undefined, [] + ))); + } +} + +function createNavigationRegister(routerInfo: RouterInfo): ts.Statement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createIdentifier(constantDefine.NAVIGATION_BUILDER_REGISTER), + undefined, + [ + ts.factory.createStringLiteral(routerInfo.name), + ts.factory.createCallExpression( + ts.factory.createIdentifier(WRAPBUILDER_FUNCTION), + undefined, + [ts.factory.createIdentifier(routerInfo.buildFunction)] + ) + ] + )); +} + +export function initializeMYIDS(): ts.ParameterDeclaration { + return ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createIdentifier(MY_IDS), + undefined, + undefined, + ts.factory.createArrayLiteralExpression( + [], + false + ) + ); +} + +function generateId(statements: ts.Statement[], node: ts.SourceFile): void { + statements.unshift( + ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(_GENERATE_ID), + undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), + ts.factory.createNumericLiteral('0') + )], + ts.NodeFlags.Let + ) + ), + ts.factory.createFunctionDeclaration( + undefined, + undefined, + ts.factory.createIdentifier(GENERATE_ID), + undefined, + [], + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ts.factory.createBlock( + [ts.factory.createReturnStatement(ts.factory.createBinaryExpression( + ts.factory.createStringLiteral(path.basename(node.fileName, EXTNAME_ETS) + '_'), + ts.factory.createToken(ts.SyntaxKind.PlusToken), ts.factory.createPrefixUnaryExpression( + ts.SyntaxKind.PlusPlusToken, + ts.factory.createIdentifier(_GENERATE_ID) + )))], + true + ) + ) + ); +} + +function preprocessIdAttrs(fileName: string): void { + for (const [id, idInfo] of ID_ATTRS) { + if (fileName === idInfo.get('path')) { + ID_ATTRS.delete(id); + } + } +} + +function isCustomDialogController(node: ts.Expression) { + const tempParent: ts.Node = node.parent; + // @ts-ignore + if (!node.parent && node.original) { + // @ts-ignore + node.parent = node.original.parent; + } + if (ts.isNewExpression(node) && node.expression && ts.isIdentifier(node.expression) && + node.expression.escapedText.toString() === SET_CONTROLLER_CTR_TYPE) { + return true; + } else { + // @ts-ignore + node.parent = tempParent; + return false; + } +} + +function createCustomDialogController(parent: ts.Expression, node: ts.NewExpression, + log: LogInfo[]): ts.NewExpression { + if (node.arguments && node.arguments.length === 1 && + ts.isObjectLiteralExpression(node.arguments[0]) && node.arguments[0].properties) { + const newproperties: ts.ObjectLiteralElementLike[] = node.arguments[0].properties.map((item) => { + const componentName: string = isCustomDialogControllerPropertyAssignment(item, log); + if (componentName !== null) { + item = processCustomDialogControllerPropertyAssignment(parent, + item as ts.PropertyAssignment, componentName); + } + return item; + }); + return ts.factory.createNewExpression(node.expression, node.typeArguments, + [ts.factory.createObjectLiteralExpression(newproperties, true), ts.factory.createThis()]); + } else { + return node; + } +} + +function isCustomDialogControllerPropertyAssignment(node: ts.ObjectLiteralElementLike, + log: LogInfo[]): string { + if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && + node.name.getText() === CUSTOM_DIALOG_CONTROLLER_BUILDER) { + if (node.initializer) { + const componentName: string = getName(node.initializer); + if (componentCollection.customDialogs.has(componentName)) { + return componentName; + } + } else { + validateCustomDialogControllerBuilderInit(node, log); + } + } + return null; +} + +function validateCustomDialogControllerBuilderInit(node: ts.ObjectLiteralElementLike, + log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: 'The builder should be initialized with a @CustomDialog Component.', + pos: node.getStart(), + code: '10905335' + }); +} + +function processCustomDialogControllerPropertyAssignment(parent: ts.Expression, + node: ts.PropertyAssignment, componentName: string): ts.PropertyAssignment { + if (ts.isCallExpression(node.initializer)) { + return ts.factory.updatePropertyAssignment(node, node.name, + processCustomDialogControllerBuilder(parent, node.initializer, componentName)); + } + return undefined; +} + +function processCustomDialogControllerBuilder(parent: ts.Expression, + node: ts.CallExpression, componentName: string): ts.ArrowFunction { + const newExp: ts.Expression = createCustomComponentNewExpression(node, componentName, false, false, true); + const jsDialog: ts.Identifier = ts.factory.createIdentifier(JS_DIALOG); + return createCustomComponentBuilderArrowFunction(node, parent, jsDialog, newExp); +} + +function createCustomComponentBuilderArrowFunction(node: ts.CallExpression, parent: ts.Expression, + jsDialog: ts.Identifier, newExp: ts.Expression): ts.ArrowFunction { + let mountNodde: ts.PropertyAccessExpression; + if (ts.isBinaryExpression(parent)) { + mountNodde = parent.left; + } else if (ts.isVariableDeclaration(parent) || ts.isPropertyDeclaration(parent)) { + mountNodde = ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + parent.name as ts.Identifier); + } + return ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [ + ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration(jsDialog, undefined, undefined, newExp)], + ts.NodeFlags.Let + ) + ), + ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + jsDialog, + ts.factory.createIdentifier(SET_CONTROLLER_METHOD) + ), + undefined, + mountNodde ? [mountNodde] : undefined + ) + ), + ts.factory.createExpressionStatement(createViewCreate(jsDialog)), + partialUpdateConfig.partialUpdateMode ? assignComponentParams(node) : undefined, + partialUpdateConfig.partialUpdateMode ? assignmentFunction(jsDialog.escapedText.toString()) : undefined + ], + true + ) + ); +} + +export function isResource(node: ts.Node): boolean { + return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && + (node.expression.escapedText.toString() === RESOURCE || + node.expression.escapedText.toString() === RESOURCE_RAWFILE) && node.arguments.length > 0; +} + +export function isAnimateToOrImmediately(node: ts.Node): boolean { + return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && + ATTRIBUTE_ANIMATETO_SET.has(node.expression.escapedText.toString()); +} + +export function processResourceData(node: ts.CallExpression, filePath: string, + previewLog: {isAcceleratePreview: boolean, log: LogInfo[]} = {isAcceleratePreview: false, log: []}): ts.Node { + if (ts.isStringLiteral(node.arguments[0])) { + const resourceData: string[] = (node.arguments[0] as ts.StringLiteral).text.trim().split('.'); + const isResourceModule: boolean = resourceData.length && /^\[.*\]$/g.test(resourceData[0]); + if (node.expression.getText() === RESOURCE_RAWFILE) { + isResourcefile(node, previewLog, isResourceModule); + if (resourceData && resourceData[0] && isResourceModule) { + return createResourceParam(-1, RESOURCE_TYPE.rawfile, [node.arguments[0]], resourceData[0], true); + } else { + return createResourceParam(0, RESOURCE_TYPE.rawfile, [node.arguments[0]], '', false); + } + } else { + return getResourceDataNode(node, previewLog, resourceData, isResourceModule, filePath); + } + } else if (node.expression.getText() === RESOURCE && node.arguments && node.arguments.length) { + resourcePreviewMessage(previewLog); + return createResourceParamWithVariable(node, -1, -1); + } else if (node.expression.getText() === RESOURCE_RAWFILE && node.arguments && node.arguments.length) { + resourcePreviewMessage(previewLog); + return createResourceParamWithVariable(node, -1, RESOURCE_TYPE.rawfile); + } + return node; +} + +function resourcePreviewMessage(previewLog: {isAcceleratePreview: boolean, log: LogInfo[]}): void { + if (previewLog.isAcceleratePreview) { + previewLog.log.push({ + type: LogType.ERROR, + message: 'not support AcceleratePreview', + code: '10906401' + }); + } +} + +function getResourceDataNode(node: ts.CallExpression, + previewLog: {isAcceleratePreview: boolean, log: LogInfo[]}, resourceData: string[], isResourceModule: boolean, filePath: string): ts.Node { + let resourceValue: number; + if (preCheckResourceData(resourceData, resources, node.arguments[0].getStart(), previewLog, isResourceModule, filePath)) { + let resourceType: number = RESOURCE_TYPE[resourceData[1]]; + if (resourceType === undefined && !previewLog.isAcceleratePreview) { + transformLog.errors.push({ + type: LogType.ERROR, + message: `The resource type ${resourceData[1]} is not supported.`, + pos: node.getStart(), + code: '10906334' + }); + return node; + } + if (isResourceModule) { + resourceValue = -1; + resourceType = -1; + } else { + resourceValue = resources[resourceData[0]][resourceData[1]][resourceData[2]]; + } + return createResourceParam(resourceValue, resourceType, + projectConfig.compileHar || isResourceModule ? Array.from(node.arguments) : Array.from(node.arguments).slice(1), + resourceData.length && resourceData[0], isResourceModule); + } + return node; +} + +function isResourcefile(node: ts.CallExpression, previewLog: {isAcceleratePreview: boolean, log: LogInfo[]}, isResourceModule: boolean): void { + if (!isResourceModule && process.env.rawFileResource && !storedFileInfo.resourcesArr.has(node.arguments[0].text) && + !previewLog.isAcceleratePreview && process.env.compileMode === 'moduleJson') { + transformLog.errors.push({ + type: LogType.ERROR, + message: `No such '${node.arguments[0].text}' resource in current module.`, + pos: node.getStart(), + code: '10904333' + }); + } +} + +function addBundleAndModuleParam(propertyArray: Array, resourceModuleName: string, isResourceModule: boolean): void { + if (projectConfig.compileHar) { + projectConfig.bundleName = '__harDefaultBundleName__'; + projectConfig.moduleName = '__harDefaultModuleName__'; + } + const isDynamicBundleOrModule: boolean = isDynamic(); + const moduleNameNode: ts.Expression = createResourceModuleNode(resourceModuleName, isResourceModule, isDynamicBundleOrModule); + if (projectConfig.bundleName || projectConfig.bundleName === '') { + propertyArray.push(ts.factory.createPropertyAssignment( + ts.factory.createStringLiteral(RESOURCE_NAME_BUNDLE), + (projectConfig.resetBundleName || projectConfig.allowEmptyBundleName) ? ts.factory.createStringLiteral('') : + createBundleOrModuleNode(isDynamicBundleOrModule, 'bundleName') + )); + } + if (projectConfig.moduleName || projectConfig.moduleName === '' || moduleNameNode) { + propertyArray.push(ts.factory.createPropertyAssignment( + ts.factory.createStringLiteral(RESOURCE_NAME_MODULE), + isResourceModule ? moduleNameNode : createBundleOrModuleNode(isDynamicBundleOrModule, 'moduleName') + )); + } +} + +function createResourceModuleNode(resourceModuleName: string, isResourceModule: boolean, + isDynamicBundleOrModule: boolean): ts.Expression { + if (isResourceModule) { + if (resourceModuleName) { + const moduleName: string = resourceModuleName.replace(/^\[|\]$/g, ''); + return ts.factory.createStringLiteral(moduleName); + } + if (isDynamicBundleOrModule) { + return ts.factory.createIdentifier('__MODULE_NAME__'); + } + return projectConfig.moduleName ? ts.factory.createStringLiteral(projectConfig.moduleName) : undefined; + } + return undefined; +} + +function createBundleOrModuleNode(isDynamicBundleOrModule: boolean, type: string): ts.Expression { + if (isDynamicBundleOrModule) { + return ts.factory.createIdentifier(type === 'bundleName' ? '__BUNDLE_NAME__' : '__MODULE_NAME__'); + } + return ts.factory.createStringLiteral(type === 'bundleName' ? projectConfig.bundleName : + projectConfig.moduleName); +} + +function createResourceParamWithVariable(node: ts.CallExpression, resourceValue: number, resourceType: number): ts.ObjectLiteralExpression { + const propertyArray: Array = [ + ts.factory.createPropertyAssignment( + ts.factory.createStringLiteral(RESOURCE_NAME_ID), + ts.factory.createNumericLiteral(resourceValue) + ), + ts.factory.createPropertyAssignment( + ts.factory.createStringLiteral(RESOURCE_NAME_TYPE), + ts.factory.createNumericLiteral(resourceType) + ), + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(RESOURCE_NAME_PARAMS), + ts.factory.createArrayLiteralExpression(Array.from(node.arguments), false) + ) + ]; + + addBundleAndModuleParam(propertyArray, '', true); + + const resourceParams: ts.ObjectLiteralExpression = ts.factory.createObjectLiteralExpression( + propertyArray, false); + return resourceParams; +} + +function createResourceParam(resourceValue: number, resourceType: number, argsArr: ts.Expression[], + resourceModuleName: string, isResourceModule: boolean): + ts.ObjectLiteralExpression { + if (projectConfig.compileHar) { + resourceValue = -1; + } + + const propertyArray: Array = [ + ts.factory.createPropertyAssignment( + ts.factory.createStringLiteral(RESOURCE_NAME_ID), + ts.factory.createNumericLiteral(resourceValue) + ), + ts.factory.createPropertyAssignment( + ts.factory.createStringLiteral(RESOURCE_NAME_TYPE), + ts.factory.createNumericLiteral(resourceType) + ), + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(RESOURCE_NAME_PARAMS), + ts.factory.createArrayLiteralExpression(argsArr, false) + ) + ]; + + addBundleAndModuleParam(propertyArray, resourceModuleName, isResourceModule); + + const resourceParams: ts.ObjectLiteralExpression = ts.factory.createObjectLiteralExpression( + propertyArray, false); + return resourceParams; +} + +function preCheckResourceData(resourceData: string[], resources: object, pos: number, + previewLog: {isAcceleratePreview: boolean, log: LogInfo[]}, isResourceModule: boolean, filePath: string): boolean { + if (previewLog.isAcceleratePreview) { + return validateResourceData(resourceData, resources, pos, previewLog.log, true, isResourceModule, filePath); + } else { + return validateResourceData(resourceData, resources, pos, transformLog.errors, false, isResourceModule, filePath); + } +} + +function validateResourceData(resourceData: string[], resources: object, pos: number, log: LogInfo[], isAcceleratePreview: boolean, + isResourceModule: boolean, filePath: string): boolean { + if (resourceData.length !== 3) { + log.push({ + type: LogType.ERROR, + message: 'The input parameter is not supported.', + pos, + code: '10905332' + }); + } else { + if (!isAcceleratePreview && process.env.compileTool === 'rollup' && process.env.compileMode === 'moduleJson') { + storedFileInfo.collectResourceInFile(resourceData[1] + '_' + resourceData[2], path.resolve(filePath)); + } + if (isResourceModule) { + if (/^\[.*\]$/.test(resourceData[0]) && projectConfig.hspResourcesMap) { + const resourceDataFirst: string = resourceData[0].replace(/^\[/, '').replace(/\]$/, '').trim(); + return resourceCheck(resourceData, resources, pos, log, true, resourceDataFirst, false); + } else { + return resourceCheck(resourceData, resources, pos, log, false, resourceData[0], true); + } + } else { + return resourceCheck(resourceData, resources, pos, log, false, resourceData[0], false); + } + } + return false; +} + +function resourceCheck(resourceData: string[], resources: object, pos: number, log: LogInfo[], isHspResourceModule: boolean, + resourceDataFirst: string, faOrNoHspResourcesMap: boolean): boolean { + const logType: LogType = isHspResourceModule ? LogType.WARN : LogType.ERROR; + if (!faOrNoHspResourcesMap && !resources[resourceDataFirst]) { + log.push({ + type: logType, + message: `Unknown resource source '${resourceDataFirst}'.`, + pos, + code: '10903331' + }); + } else if (!faOrNoHspResourcesMap && !resources[resourceDataFirst][resourceData[1]]) { + log.push({ + type: logType, + message: `Unknown resource type '${resourceData[1]}'.`, + pos, + code: '10903330' + }); + } else if (!faOrNoHspResourcesMap && !resources[resourceDataFirst][resourceData[1]][resourceData[2]]) { + log.push({ + type: logType, + message: `Unknown resource name '${resourceData[2]}'.`, + pos, + code: '10903329' + }); + } else { + return true; + } + return false; +} + +function isWorker(node: ts.Node): boolean { + return ts.isNewExpression(node) && ts.isPropertyAccessExpression(node.expression) && + ts.isIdentifier(node.expression.name) && + node.expression.name.escapedText.toString() === WORKER_OBJECT; +} + +function processWorker(node: ts.NewExpression): ts.Node { + if (node.arguments.length && ts.isStringLiteral(node.arguments[0])) { + const args: ts.Expression[] = Array.from(node.arguments); + // @ts-ignore + const workerPath: string = node.arguments[0].text; + const stringNode: ts.StringLiteral = ts.factory.createStringLiteral( + workerPath.replace(/\.ts$/, '.js')); + args.splice(0, 1, stringNode); + return ts.factory.updateNewExpression(node, node.expression, node.typeArguments, args); + } + return node; +} + +export function processAnimateToOrImmediately(node: ts.CallExpression): ts.CallExpression { + return ts.factory.updateCallExpression(node, ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(GLOBAL_CONTEXT), + ts.factory.createIdentifier(node.expression.escapedText.toString())), + node.typeArguments, node.arguments); +} + +function processExtend(node: ts.FunctionDeclaration, log: LogInfo[], + decoratorName: string): ts.FunctionDeclaration { + const componentName: string = isExtendFunction(node, { decoratorName: '', componentName: '' }, true); + if (componentName && node.body && node.body.statements.length) { + const statementArray: ts.Statement[] = []; + let bodynode: ts.Block; + if (decoratorName === COMPONENT_EXTEND_DECORATOR) { + const attrSet: ts.CallExpression = node.body.statements[0].expression; + if (isOriginalExtend(node.body)) { + const changeCompName: ts.ExpressionStatement = ts.factory.createExpressionStatement(processExtendBody(attrSet)); + bindComponentAttr(changeCompName as ts.ExpressionStatement, + ts.factory.createIdentifier(componentName), statementArray, log); + } else { + bodynode = ts.visitEachChild(node.body, traverseExtendExpression, contextGlobal); + } + let extendFunctionName: string; + if (node.name.getText().startsWith('__' + componentName + '__')) { + extendFunctionName = node.name.getText(); + } else { + extendFunctionName = '__' + componentName + '__' + node.name.getText(); + collectExtend(EXTEND_ATTRIBUTE, componentName, node.name.escapedText.toString()); + } + return ts.factory.updateFunctionDeclaration(node, ts.getModifiers(node), node.asteriskToken, + ts.factory.createIdentifier(extendFunctionName), node.typeParameters, + node.parameters, ts.factory.createToken(ts.SyntaxKind.VoidKeyword), isOriginalExtend(node.body) ? + ts.factory.updateBlock(node.body, statementArray) : bodynode); + } + if (decoratorName === COMPONENT_ANIMATABLE_EXTEND_DECORATOR) { + bindComponentAttr(node.body.statements[0], + ts.factory.createIdentifier(componentName), statementArray, log); + return ts.factory.updateFunctionDeclaration(node, ts.getModifiers(node), node.asteriskToken, + node.name, node.typeParameters, + [...node.parameters, ...createAnimatableParameterNode()], ts.factory.createToken(ts.SyntaxKind.VoidKeyword), + ts.factory.updateBlock(node.body, createAnimatableBody(componentName, node.name, + node.parameters, statementArray))); + } + } + function traverseExtendExpression(node: ts.Node): ts.Node { + if (ts.isExpressionStatement(node) && isDollarNode(node, componentName)) { + const changeCompName: ts.ExpressionStatement = + ts.factory.createExpressionStatement(processExtendBody(node.expression, componentName)); + const statementArray: ts.Statement[] = []; + bindComponentAttr(changeCompName, ts.factory.createIdentifier(componentName), statementArray, []); + return ts.factory.createBlock(statementArray, true); + } + return ts.visitEachChild(node, traverseExtendExpression, contextGlobal); + } + return undefined; +} + +function createAnimatableParameterNode(): ts.ParameterDeclaration[] { + return [ + ts.factory.createParameterDeclaration( + undefined, undefined, ts.factory.createIdentifier(ELMTID)), + ts.factory.createParameterDeclaration( + undefined, undefined, ts.factory.createIdentifier(ISINITIALRENDER)), + ts.factory.createParameterDeclaration( + undefined, undefined, ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT)) + ]; +} + +function createAnimatableBody(componentName: string, funcName: ts.Identifier, + parameters: ts.NodeArray, attrArray: ts.Statement[]): ts.Statement[] { + const paramNode: ts.Identifier[] = []; + parameters.forEach((item: ts.ParameterDeclaration) => { + if (item.name && ts.isIdentifier(item.name)) { + paramNode.push(item.name); + } + }); + return [ + ts.factory.createIfStatement( + ts.factory.createIdentifier(ISINITIALRENDER), + ts.factory.createBlock([ + createAnimatableProperty(componentName, funcName, parameters, paramNode, attrArray), + ...attrArray + ], true), + ts.factory.createBlock([ + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(componentName), + ts.factory.createIdentifier(UPDATE_ANIMATABLE_PROPERTY) + ), undefined, + [ts.factory.createStringLiteral(funcName.escapedText.toString()), ...paramNode] + )) + ]) + ) + ]; +} + +function createAnimatableProperty(componentName: string, funcName: ts.Identifier, + parameters: ts.NodeArray, + paramNode: ts.Identifier[], attrArray: ts.Statement[]) { + const componentIdentifier: ts.Identifier = ts.factory.createIdentifier(componentName); + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + componentIdentifier, + ts.factory.createIdentifier(CREATE_ANIMATABLE_PROPERTY)), + undefined, [ + ts.factory.createStringLiteral(funcName.escapedText.toString()), + ...paramNode, + ts.factory.createArrowFunction(undefined, undefined, parameters, undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock([ + createViewStackProcessorStatement(STARTGETACCESSRECORDINGFOR, ELMTID), + createAnimatableFrameNode(componentName), + ...attrArray, + createViewStackProcessorStatement(STOPGETACCESSRECORDING), + createAnimatableUpdateFunc() + ], true)) + ] + ) + ); +} + +function createAnimatableFrameNode(componentName: string): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(VIEW_STACK_PROCESSOR), + ts.factory.createIdentifier(GET_AND_PUSH_FRAME_NODE) + ), undefined, + [ + ts.factory.createStringLiteral(componentName), + ts.factory.createIdentifier(ELMTID) + ] + )); +} + +function createAnimatableUpdateFunc(): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT), + ts.factory.createIdentifier(FINISH_UPDATE_FUNC) + ), undefined, [ts.factory.createIdentifier(ELMTID)] + )); +} + +function processConcurrent(node: ts.FunctionDeclaration): ts.FunctionDeclaration { + if (node.body) { + const statementArray: ts.Statement[] = + [ts.factory.createExpressionStatement(ts.factory.createStringLiteral('use concurrent')), + ...node.body.statements]; + return ts.factory.updateFunctionDeclaration(node, ts.getModifiers(node), node.asteriskToken, node.name, + node.typeParameters, node.parameters, node.type, ts.factory.updateBlock(node.body, statementArray)); + } + return node; +} + +export function isOriginalExtend(node: ts.Block): boolean { + let innerNode: ts.Node = node.statements[0]; + if (node.statements.length === 1 && ts.isExpressionStatement(innerNode)) { + while (innerNode.expression) { + innerNode = innerNode.expression; + } + if (ts.isIdentifier(innerNode) && innerNode.pos && innerNode.end && innerNode.pos === innerNode.end && + innerNode.escapedText.toString().match(/Instance$/)) { + return true; + } + } + return false; +} + +function isDollarNode(node: ts.ExpressionStatement, componentName: string): boolean { + let innerNode: ts.Node = node; + while (innerNode.expression) { + innerNode = innerNode.expression; + } + let changedIdentifier: string = '$'; + if (process.env.compileTool === 'rollup' && storedFileInfo.reUseProgram) { + changedIdentifier = `${componentName}Instance`; + } + if (ts.isIdentifier(innerNode) && innerNode.getText() === changedIdentifier) { + return true; + } else { + return false; + } +} + +function processExtendBody(node: ts.Node, componentName?: string): ts.Expression { + switch (node.kind) { + case ts.SyntaxKind.CallExpression: + return ts.factory.createCallExpression(processExtendBody(node.expression, componentName), + undefined, node.arguments); + case ts.SyntaxKind.PropertyAccessExpression: + return ts.factory.createPropertyAccessExpression( + processExtendBody(node.expression, componentName), node.name); + case ts.SyntaxKind.Identifier: + if (!componentName) { + return ts.factory.createIdentifier(node.escapedText.toString().replace(INSTANCE, '')); + } else { + return ts.factory.createIdentifier(componentName); + } + } + return undefined; +} + +export function collectExtend(collectionSet: Map>, component: string, attribute: string): void { + if (collectionSet.has(component)) { + collectionSet.get(component).add(attribute); + } else { + collectionSet.set(component, new Set([attribute])); + } +} + +export function isExtendFunction(node: ts.FunctionDeclaration, extendResult: ExtendResult, + checkArguments: boolean = false): string { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (decorators && decorators.length) { + for (let i = 0, len = decorators.length; i < len; i++) { + if (ts.isCallExpression(decorators[i].expression)) { + parseExtendNode(decorators[i].expression as ts.CallExpression, extendResult, checkArguments); + if (CHECK_EXTEND_DECORATORS.includes(extendResult.decoratorName) && extendResult.componentName) { + return extendResult.componentName; + } + } + } + } + return null; +} + +function parseExtendNode(node: ts.CallExpression, extendResult: ExtendResult, checkArguments: boolean): void { + if (ts.isIdentifier(node.expression)) { + extendResult.decoratorName = node.expression.escapedText.toString(); + if (checkArguments && CHECK_EXTEND_DECORATORS.includes(extendResult.decoratorName) && + node.arguments && node.arguments.length !== 1) { + transformLog.errors.push({ + type: LogType.ERROR, + message: `@${extendResult.decoratorName} should have one and only one parameter`, + pos: node.getStart(), + code: '10905108' + }); + } + } + if (node.arguments.length && ts.isIdentifier(node.arguments[0])) { + extendResult.componentName = node.arguments[0].escapedText.toString(); + } +} + +function createEntryNode(node: ts.SourceFile, context: ts.TransformationContext, + entryNodeKey: ts.Expression, id: number): ts.SourceFile { + let cardRelativePath: string; + if (projectConfig && projectConfig.cardObj) { + cardRelativePath = projectConfig.cardObj[resourceFileName]; + } + if (componentCollection.previewComponent.length === 0 || !projectConfig.isPreview) { + if (componentCollection.entryComponent) { + if (!partialUpdateConfig.partialUpdateMode) { + const entryNode: ts.ExpressionStatement = + createEntryFunction(componentCollection.entryComponent, context, + cardRelativePath, entryNodeKey, id) as ts.ExpressionStatement; + return context.factory.updateSourceFile(node, [...node.statements, entryNode]); + } else { + const entryNodes: ts.ExpressionStatement[] = + createEntryFunction(componentCollection.entryComponent, context, + cardRelativePath, entryNodeKey, id) as ts.ExpressionStatement[]; + return entryNodes ? + context.factory.updateSourceFile(node, [...node.statements, ...entryNodes]) : + context.factory.updateSourceFile(node, [...node.statements]); + } + } else { + return node; + } + } else { + const statementsArray: ts.Statement = + createPreviewComponentFunction(componentCollection.entryComponent, context, cardRelativePath, entryNodeKey, id); + return context.factory.updateSourceFile(node, [...node.statements, statementsArray]); + } +} + +function createEntryFunction(name: string, context: ts.TransformationContext, cardRelativePath: string, + entryNodeKey: ts.Expression, id: number): ts.ExpressionStatement | (ts.Statement | ts.Block)[] { + const newArray: ts.Expression[] = [ + context.factory.createStringLiteral(id.toString()), + context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED), + context.factory.createObjectLiteralExpression([], false) + ]; + const [localStorageName, entryOptionNode]: [string, ts.Expression] = addStorageParam(name); + if (localStorageName && entryNodeKey) { + newArray.push(entryNodeKey); + } + const newExpressionParams: any[] = [ + context.factory.createNewExpression( + context.factory.createIdentifier(name), undefined, newArray)]; + addCardStringliteral(newExpressionParams, context, cardRelativePath); + if (!partialUpdateConfig.partialUpdateMode) { + const newExpressionStatement: ts.ExpressionStatement = + context.factory.createExpressionStatement(context.factory.createCallExpression( + context.factory.createIdentifier(cardRelativePath ? CARD_ENTRY_FUNCTION_NAME : + PAGE_ENTRY_FUNCTION_NAME), undefined, newExpressionParams)); + return newExpressionStatement; + } else { + if (cardRelativePath) { + if (entryOptionNode && ts.isObjectLiteralExpression(entryOptionNode)) { + transformLog.errors.push({ + type: LogType.ERROR, + message: `@Entry doesn't support {} parameter in card`, + pos: componentCollection.entryComponentPos, + code: '10906218' + }); + } + return [ + createStartGetAccessRecording(context), + createLoadDocument(context, name, cardRelativePath, localStorageName, entryNodeKey), + createStopGetAccessRecording(context) + ]; + } else { + return createLoadPageConditionalJudgMent(context, name, cardRelativePath, localStorageName, entryOptionNode); + } + } +} + +function createLoadPageConditionalJudgMent(context: ts.TransformationContext, name: string, + cardRelativePath: string, localStorageName: string, entryOptionNode: ts.Expression, + argsArr: ts.Expression[] = undefined, isComponentPreview: boolean = false): (ts.Statement | ts.Block)[] { + let isObject: boolean = false; + const entryOptionValue: EntryOptionValue = new EntryOptionValue(); + if (!entryOptionNode) { + let originArray: ts.ExpressionStatement[]; + if (projectConfig.minAPIVersion > 10) { + if (componentCollection.entryComponent === name && componentCollection.localSharedStorage) { + return [judgeRouteNameAndStorageForIdentifier(context, name, + cardRelativePath, isObject, componentCollection.localSharedStorage, undefined, undefined, argsArr)]; + } + const newArray: ts.Expression[] = [ + context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED), + context.factory.createObjectLiteralExpression([], false) + ]; + const newExpressionParams: any[] = [ + context.factory.createNewExpression(context.factory.createIdentifier(name), undefined, newArray)]; + originArray = [ + createRegisterNamedRoute(context, newExpressionParams, false, undefined, false) + ]; + } else { + originArray = [ + createStartGetAccessRecording(context), + createLoadDocument(context, name, cardRelativePath, localStorageName, entryOptionNode), + createStopGetAccessRecording(context) + ]; + } + return originArray; + } + if (ts.isObjectLiteralExpression(entryOptionNode)) { + isObject = true; + if (entryOptionNode.properties) { + entryOptionNode.properties.forEach((property) => { + if (ts.isPropertyAssignment(property) && property.name && ts.isIdentifier(property.name)) { + entryOptionValue[property.name.escapedText.toString()] = property.initializer; + } + }); + if (entryOptionValue.useSharedStorage) { + entryOptionValue.storage = createGetShared(entryOptionValue); + } + } + } else { + isObject = false; + } + return generateLoadDocumentEntrance(isObject, entryOptionValue.routeName, entryOptionValue.storage, + isComponentPreview, context, name, cardRelativePath, entryOptionNode, argsArr); +} + +function generateLoadDocumentEntrance(isObject: boolean, routeNameNode: ts.Expression, + storageNode: ts.Expression, isComponentPreview: boolean, context: ts.TransformationContext, + name: string, cardRelativePath: string, entryOptionNode: ts.Expression, + argsArr: ts.Expression[]): (ts.ExpressionStatement | ts.Block | ts.IfStatement)[] { + if (isObject) { + if (routeNameNode && !storageNode) { + return isComponentPreview ? [ + ...assignRouteNameAndStorage(routeNameNode), + ...createLoadDocumentWithRoute(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, true, false, false, argsArr) + ] : [ts.factory.createBlock([ + ...assignRouteNameAndStorage(routeNameNode), + ...createLoadDocumentWithRoute(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, true, false, false, argsArr) + ])]; + } else if (!routeNameNode && !storageNode) { + return isComponentPreview ? [ + ...assignRouteNameAndStorage(routeNameNode), + ...createLoadDocumentWithRoute(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, false, false, true, argsArr) + ] : [ts.factory.createBlock([ + ...assignRouteNameAndStorage(routeNameNode), + ...createLoadDocumentWithRoute(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, false, false, true, argsArr) + ])]; + } else if (!routeNameNode && storageNode) { + return isComponentPreview ? [ + ...assignRouteNameAndStorage(routeNameNode), + ...createLoadDocumentWithRoute(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, false, true, true, argsArr) + ] : [ts.factory.createBlock([ + ...assignRouteNameAndStorage(routeNameNode), + ...createLoadDocumentWithRoute(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, false, true, true, argsArr) + ])]; + } else { + return isComponentPreview ? [ + ...assignRouteNameAndStorage(routeNameNode), + judgeRouteNameAndStorage(context, name, cardRelativePath, isObject, entryOptionNode, routeNameNode, storageNode, argsArr) + ] : [ts.factory.createBlock([ + ...assignRouteNameAndStorage(routeNameNode), + judgeRouteNameAndStorage(context, name, cardRelativePath, isObject, entryOptionNode, routeNameNode, storageNode, argsArr) + ])]; + } + } else { + return [ + judgeRouteNameAndStorage(context, name, cardRelativePath, isObject, entryOptionNode, routeNameNode, storageNode, argsArr)]; + } +} + +function judgeRouteNameAndStorage(context: ts.TransformationContext, name: string, + cardRelativePath: string, isObject: boolean, entryOptionNode: ts.Expression, routeNameNode: ts.Expression, + storageNode: ts.Expression, argsArr: ts.Expression[] = undefined): ts.IfStatement { + return isObject ? judgeRouteNameAndStorageForObj(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, argsArr) : judgeRouteNameAndStorageForIdentifier(context, name, + cardRelativePath, isObject, entryOptionNode, routeNameNode, storageNode, argsArr); +} + +function judgeRouteNameAndStorageForObj(context: ts.TransformationContext, name: string, + cardRelativePath: string, isObject: boolean, entryOptionNode: ts.Expression, routeNameNode: ts.Expression, + storageNode: ts.Expression, argsArr: ts.Expression[] = undefined): ts.IfStatement { + return ts.factory.createIfStatement( + judgeRouteAndStorageForObject(true), + ts.factory.createBlock( + [ + ...createLoadDocumentWithRoute(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, true, true, false, argsArr) + ], + true + ), ts.factory.createBlock( + [ + ...createLoadDocumentWithRoute(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, false, false, true, argsArr) + ], + true + )); +} + +function judgeRouteNameAndStorageForIdentifier(context: ts.TransformationContext, name: string, + cardRelativePath: string, isObject: boolean, entryOptionNode: ts.Expression, routeNameNode: ts.Expression, + storageNode: ts.Expression, argsArr: ts.Expression[] = undefined): ts.IfStatement { + return ts.factory.createIfStatement( + judgeRouteAndStorageForIdentifier(entryOptionNode as ts.Identifier, true, true), + ts.factory.createBlock( + [ + ...createLoadDocumentWithRoute(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, true, true, false, argsArr) + ], + true + ), + ts.factory.createIfStatement( + judgeRouteAndStorageForIdentifier(entryOptionNode as ts.Identifier, true, false), + ts.factory.createBlock( + [ + ...createLoadDocumentWithRoute(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, true, false, false, argsArr) + ], + true + ), + ts.factory.createIfStatement( + judgeRouteAndStorageForIdentifier(entryOptionNode as ts.Identifier, false, true), + ts.factory.createBlock( + [ + ...createLoadDocumentWithRoute(context, name, cardRelativePath, isObject, entryOptionNode, + routeNameNode, storageNode, false, true, true, argsArr) + ], + true + ), + ts.factory.createIfStatement( + judgeUseSharedStorageForExpresion(entryOptionNode), + ts.factory.createBlock( + [...createSharedStorageWithRoute(context, name, cardRelativePath, entryOptionNode, true, argsArr)], true), + ts.factory.createBlock( + [...createLoadDocumentWithRoute(context, name, cardRelativePath, false, entryOptionNode, + null, null, false, false, true, argsArr) + ], true) + ) + ) + ) + ); +} + +function judgeRouteAndStorageForObject(hasRouteName: boolean): ts.BinaryExpression { + return ts.factory.createBinaryExpression( + ts.factory.createIdentifier(ROUTENAME_NODE), + ts.factory.createToken(hasRouteName ? ts.SyntaxKind.ExclamationEqualsToken : ts.SyntaxKind.EqualsEqualsToken), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) + ); +} + +function judgeRouteAndStorageForIdentifier(entryOptionNode: ts.Identifier, hasRouteName: boolean, + hasStorage: boolean): ts.BinaryExpression { + return ts.factory.createBinaryExpression( + ts.factory.createBinaryExpression( + entryOptionNode, + ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + entryOptionNode, + ts.factory.createIdentifier(ROUTE_NAME) + ), + ts.factory.createToken(hasRouteName ? ts.SyntaxKind.ExclamationEqualsToken : ts.SyntaxKind.EqualsEqualsToken), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) + ) + ), + ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + entryOptionNode, + ts.factory.createIdentifier(STORAGE) + ), + ts.factory.createToken(hasStorage ? ts.SyntaxKind.ExclamationEqualsToken : ts.SyntaxKind.EqualsEqualsToken), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) + ) + ); +} + +function assignRouteNameAndStorage(routeNameNode): ts.ExpressionStatement[] { + const assignOperation: ts.VariableStatement[] = []; + if (routeNameNode) { + assignOperation.push(ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(ROUTENAME_NODE), + undefined, + undefined, + routeNameNode + )], + ts.NodeFlags.Let + ) + )); + } + return assignOperation; +} + +function createLoadDocumentWithRoute(context: ts.TransformationContext, name: string, + cardRelativePath: string, isObject: boolean, entryOptionNode: ts.Expression, + routeNameNode: ts.Node, storageNode: ts.Node, hasRouteName: boolean, hasStorage: boolean, + shouldCreateAccsessRecording: boolean, argsArr: ts.Expression[]): ts.ExpressionStatement[] { + const newArray: ts.Expression[] = [ + context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED), + context.factory.createObjectLiteralExpression([], false) + ]; + if (entryOptionNode) { + if (!isObject) { + if (routeNameNode === null && storageNode === null) { + newArray.push(entryOptionNode); + } else { + newArray.push(createGetSharedForVariable(entryOptionNode)); + } + } else if (storageNode) { + newArray.push(storageNode as ts.Expression); + } + } + return loadDocumentWithRoute(context, name, newArray, argsArr, hasRouteName, shouldCreateAccsessRecording, + isObject, entryOptionNode, cardRelativePath); +} + +function loadDocumentWithRoute(context: ts.TransformationContext, name: string, newArray: ts.Expression[], + argsArr: ts.Expression[], hasRouteName: boolean, shouldCreateAccsessRecording: boolean, + isObject: boolean, entryOptionNode: ts.Expression, cardRelativePath: string): ts.ExpressionStatement[] { + const newExpressionParams = [context.factory.createNewExpression( + context.factory.createIdentifier(name), undefined, newArray)]; + if (argsArr) { + argsArr = []; + componentCollection.previewComponent.forEach((componentName: string) => { + const newExpression: ts.Expression = context.factory.createNewExpression( + context.factory.createIdentifier(componentName), undefined, + componentName === name ? newArray : newArray.slice(0, newArray.length - 1) + ); + argsArr.push(context.factory.createStringLiteral(componentName), newExpression); + }); + } + if (hasRouteName) { + return [ + shouldCreateAccsessRecording ? createStartGetAccessRecording(context) : undefined, + createRegisterNamedRoute(context, newExpressionParams, isObject, entryOptionNode, hasRouteName), + shouldCreateAccsessRecording ? createStopGetAccessRecording(context) : undefined]; + } else { + if (projectConfig.minAPIVersion > 10) { + return [createRegisterNamedRoute(context, newExpressionParams, isObject, entryOptionNode, hasRouteName)]; + } else { + return [shouldCreateAccsessRecording ? createStartGetAccessRecording(context) : undefined, + context.factory.createExpressionStatement(context.factory.createCallExpression( + context.factory.createIdentifier(cardRelativePath ? CARD_ENTRY_FUNCTION_NAME : + PAGE_ENTRY_FUNCTION_NAME), undefined, newExpressionParams)), + shouldCreateAccsessRecording ? createStopGetAccessRecording(context) : undefined]; + } + } +} + +function createRegisterNamedRoute(context: ts.TransformationContext, newExpressionParams: ts.NewExpression[], + isObject: boolean, entryOptionNode: ts.Expression, hasRouteName: boolean): ts.ExpressionStatement { + return context.factory.createExpressionStatement(context.factory.createCallExpression( + context.factory.createIdentifier(REGISTER_NAMED_ROUTE), + undefined, + [ + context.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + context.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + newExpressionParams[0] + ), + hasRouteName ? isObject ? ts.factory.createIdentifier(ROUTENAME_NODE) : context.factory.createPropertyAccessExpression( + entryOptionNode, + context.factory.createIdentifier(ROUTE_NAME) + ) : ts.factory.createStringLiteral(''), + context.factory.createObjectLiteralExpression( + [ + routerBundleOrModule(context, RESOURCE_NAME_BUNDLE), + routerBundleOrModule(context, RESOURCE_NAME_MODULE), + routerOrNavPathWrite(context, PAGE_PATH, projectConfig.projectPath, projectConfig.projectRootPath), + routerOrNavPathWrite(context, PAGE_FULL_PATH, projectConfig.projectRootPath), + context.factory.createPropertyAssignment( + context.factory.createIdentifier(INTEGRATED_HSP), + context.factory.createStringLiteral(integratedHspType()) + ), + routerModuleType(context) + ], + false + ) + ] + )); +} + +export function createStartGetAccessRecording(context: ts.TransformationContext): ts.ExpressionStatement { + return context.factory.createExpressionStatement( + context.factory.createCallExpression( + context.factory.createPropertyAccessExpression( + context.factory.createIdentifier(VIEWSTACKPROCESSOR), + context.factory.createIdentifier(STARTGETACCESSRECORDINGFOR) + ), + undefined, + [context.factory.createCallExpression( + context.factory.createPropertyAccessExpression( + context.factory.createIdentifier(VIEWSTACKPROCESSOR), + context.factory.createIdentifier(ALLOCATENEWELMETIDFORNEXTCOMPONENT) + ), + undefined, + [] + )] + ) + ); +} + +function createLoadDocument(context: ts.TransformationContext, name: string, + cardRelativePath: string, localStorageName: string, entryNodeKey: ts.Expression): ts.ExpressionStatement { + const newArray: ts.Expression[] = [ + context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED), + context.factory.createObjectLiteralExpression([], false) + ]; + if (localStorageName && entryNodeKey) { + newArray.push(entryNodeKey); + } + const newExpressionParams: any[] = [ + context.factory.createNewExpression( + context.factory.createIdentifier(name), + undefined, newArray)]; + addCardStringliteral(newExpressionParams, context, cardRelativePath); + return context.factory.createExpressionStatement( + context.factory.createCallExpression( + context.factory.createIdentifier(cardRelativePath ? CARD_ENTRY_FUNCTION_NAME : + PAGE_ENTRY_FUNCTION_NAME), undefined, newExpressionParams) + ); +} + +function createStopGetAccessRecording(context: ts.TransformationContext): ts.ExpressionStatement { + return context.factory.createExpressionStatement( + context.factory.createCallExpression( + context.factory.createPropertyAccessExpression( + context.factory.createIdentifier(VIEWSTACKPROCESSOR), + context.factory.createIdentifier(STOPGETACCESSRECORDING) + ), + undefined, + [] + ) + ); +} + +function addStorageParam(name: string): [string, ts.Expression] { + let localStorageName: string; + let localStorageNode: ts.Identifier | ts.ObjectLiteralExpression; + const localStorageNum: number = (localStorageLinkCollection.get(name) || new Set()).size + + (localStoragePropCollection.get(name) || new Set()).size; + if (componentCollection.entryComponent === name) { + if (componentCollection.localStorageNode) { + localStorageNode = componentCollection.localStorageNode; + } + if (componentCollection.localStorageName) { + localStorageName = componentCollection.localStorageName; + } + if (!hasStorage() && localStorageNum) { + transformLog.errors.push({ + type: LogType.WARN, + message: `@Entry should have a parameter, like '@Entry (storage)'.`, + pos: componentCollection.entryComponentPos + }); + } + } + return [localStorageName, localStorageNode]; +} + +function hasStorage(): boolean { + if (componentCollection.localStorageName || componentCollection.localStorageNode || + componentCollection.localSharedStorage) { + return true; + } + return false; +} + +function createPreviewComponentFunction(name: string, context: ts.TransformationContext, + cardRelativePath: string, entryNodeKey: ts.Expression, id: number): ts.Statement { + const newArray: ts.Expression[] = partialUpdateConfig.partialUpdateMode ? + [ + context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED), + context.factory.createObjectLiteralExpression([], false) + ] : + [ + context.factory.createStringLiteral(id.toString()), + context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED), + context.factory.createObjectLiteralExpression([], false) + ]; + const [localStorageName, entryOptionNode]: [string, ts.Expression] = addStorageParam(name); + if (entryOptionNode) { + newArray.push(entryOptionNode); + } + const argsArr: ts.Expression[] = []; + componentCollection.previewComponent.forEach(componentName => { + const newExpression: ts.Expression = context.factory.createNewExpression( + context.factory.createIdentifier(componentName), + undefined, + newArray + ); + argsArr.push(context.factory.createStringLiteral(componentName)); + argsArr.push(newExpression); + }); + const newExpressionParams: any[] = name ? [context.factory.createNewExpression( + context.factory.createIdentifier(name), undefined, newArray)] : []; + addCardStringliteral(newExpressionParams, context, cardRelativePath); + const ifStatement: ts.Statement = context.factory.createIfStatement( + context.factory.createCallExpression( + context.factory.createIdentifier(GET_PREVIEW_FLAG_FUNCTION_NAME), + undefined, + [] + ), + context.factory.createBlock( + [...storePreviewComponents(name, entryOptionNode, argsArr), + context.factory.createExpressionStatement(context.factory.createCallExpression( + context.factory.createIdentifier(PREVIEW_COMPONENT_FUNCTION_NAME), + undefined, + [] + ))], + true + ), + context.factory.createBlock( + createPreviewElseBlock(name, context, cardRelativePath, localStorageName, entryOptionNode, + newExpressionParams, argsArr), + true + ) + ); + return ifStatement; +} + +function storePreviewComponents(name: string, entryOptionNode: ts.Expression, argsArr: ts.Expression[]): + (ts.ExpressionStatement | ts.VariableStatement | ts.IfStatement)[] { + let isObject: boolean = false; + let storageNode: ts.Expression; + if (!entryOptionNode) { + return [ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createIdentifier(STORE_PREVIEW_COMPONENTS), + undefined, + [ + ts.factory.createNumericLiteral(componentCollection.previewComponent.length), + ...argsArr + ] + ))]; + } + if (ts.isObjectLiteralExpression(entryOptionNode)) { + isObject = true; + if (entryOptionNode.properties) { + entryOptionNode.properties.forEach((property) => { + if (ts.isPropertyAssignment(property) && property.name && ts.isIdentifier(property.name) && + property.name.escapedText.toString() === STORAGE) { + storageNode = property.initializer; + } + }); + } + } else { + isObject = false; + } + const newArray: ts.Expression[] = [ + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED), + ts.factory.createObjectLiteralExpression([], false) + ]; + const newArgsArr: ts.Expression[] = []; + if (isObject) { + return processObjectStorage(storageNode, newArray, name, newArgsArr); + } else { + return [ts.factory.createIfStatement( + ts.factory.createBinaryExpression( + entryOptionNode, + ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + entryOptionNode, + ts.factory.createIdentifier(STORAGE) + ), + ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsToken), + ts.factory.createIdentifier('undefined') + ) + ), + ts.factory.createBlock( + [returnStorePreview(entryOptionNode, true, name)], + true + ), + ts.factory.createBlock( + [returnStorePreview(entryOptionNode, false, name)], + true + ) + )]; + } +} + +function processObjectStorage(storageNode: ts.Expression, newArray: ts.Expression[], name: string, + newArgsArr: ts.Expression[]): (ts.ExpressionStatement | ts.VariableStatement)[] { + if (storageNode) { + newArray.push(ts.factory.createIdentifier(STORAGE_NODE)); + componentCollection.previewComponent.forEach(componentName => { + const newExpression: ts.Expression = ts.factory.createNewExpression( + ts.factory.createIdentifier(componentName), + undefined, + componentName === name ? newArray : newArray.slice(0, newArray.length - 1) + ); + newArgsArr.push(ts.factory.createStringLiteral(componentName)); + newArgsArr.push(newExpression); + }); + return [ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(STORAGE_NODE), + undefined, + undefined, + storageNode + )], + ts.NodeFlags.Let + ) + ), ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createIdentifier(STORE_PREVIEW_COMPONENTS), + undefined, + [ + ts.factory.createNumericLiteral(componentCollection.previewComponent.length), + ...newArgsArr + ] + ))]; + } else { + componentCollection.previewComponent.forEach(componentName => { + const newExpression: ts.Expression = ts.factory.createNewExpression( + ts.factory.createIdentifier(componentName), + undefined, + newArray + ); + newArgsArr.push(ts.factory.createStringLiteral(componentName)); + newArgsArr.push(newExpression); + }); + return [ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createIdentifier(STORE_PREVIEW_COMPONENTS), + undefined, + [ + ts.factory.createNumericLiteral(componentCollection.previewComponent.length), + ...newArgsArr + ] + ))]; + } +} + +function returnStorePreview(entryOptionNode: ts.Expression, hasStorage: boolean, name: string): ts.ExpressionStatement { + const newArray: ts.Expression[] = [ + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED), + ts.factory.createObjectLiteralExpression([], false) + ]; + const newArgsArr: ts.Expression[] = []; + newArray.push(hasStorage ? ts.factory.createPropertyAccessExpression( + entryOptionNode, + ts.factory.createIdentifier(STORAGE) + ) : entryOptionNode); + componentCollection.previewComponent.forEach(componentName => { + const newExpression: ts.Expression = ts.factory.createNewExpression( + ts.factory.createIdentifier(componentName), + undefined, + componentName === name ? newArray : newArray.slice(0, newArray.length - 1) + ); + newArgsArr.push(ts.factory.createStringLiteral(componentName)); + newArgsArr.push(newExpression); + }); + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createIdentifier(STORE_PREVIEW_COMPONENTS), + undefined, + [ + ts.factory.createNumericLiteral(componentCollection.previewComponent.length), + ...newArgsArr + ] + )); +} + +function createPreviewElseBlock(name: string, context: ts.TransformationContext, cardRelativePath: string, + localStorageName: string, entryOptionNode: ts.Expression, newExpressionParams: ts.Expression[], + argsArr: ts.Expression[]): (ts.ExpressionStatement | ts.IfStatement | ts.Block | ts.Statement)[] { + if (name) { + if (!partialUpdateConfig.partialUpdateMode) { + return [context.factory.createExpressionStatement(context.factory.createCallExpression( + context.factory.createIdentifier(STORE_PREVIEW_COMPONENTS), + undefined, + [ + context.factory.createNumericLiteral(componentCollection.previewComponent.length), + ...argsArr + ] + )), + context.factory.createExpressionStatement(context.factory.createCallExpression( + context.factory.createIdentifier(cardRelativePath ? CARD_ENTRY_FUNCTION_NAME : + PAGE_ENTRY_FUNCTION_NAME), undefined, newExpressionParams + ))]; + } else { + if (cardRelativePath) { + if (entryOptionNode && ts.isObjectLiteralExpression(entryOptionNode)) { + transformLog.errors.push({ + type: LogType.ERROR, + message: `@Entry doesn't support {} parameter in card`, + pos: componentCollection.entryComponentPos, + code: '10906217' + }); + } + return [ + name ? createStartGetAccessRecording(context) : undefined, + name ? context.factory.createExpressionStatement(context.factory.createCallExpression( + context.factory.createIdentifier(cardRelativePath ? CARD_ENTRY_FUNCTION_NAME : + PAGE_ENTRY_FUNCTION_NAME), undefined, newExpressionParams + )) : undefined, + name ? createStopGetAccessRecording(context) : undefined + ]; + } + return createLoadPageConditionalJudgMent(context, name, cardRelativePath, localStorageName, + entryOptionNode, argsArr, true); + } + } + return undefined; +} + +export function resetLog(): void { + transformLog.errors = []; +} + +function addCardStringliteral(newExpressionParams: any[], context: ts.TransformationContext, + cardRelativePath: string): void { + const bundleName = projectConfig.allowEmptyBundleName ? '' : projectConfig.bundleName; + if (cardRelativePath) { + newExpressionParams.push(context.factory.createStringLiteral( + bundleName + '/' + projectConfig.moduleName + '/' + cardRelativePath)); + } +} + +export function validatorCard(log: LogInfo[], type: number, pos: number, + name: string = ''): void { + if (projectConfig && projectConfig.cardObj && resourceFileName && + projectConfig.cardObj[resourceFileName]) { + const logInfo: LogInfo = { + type: LogType.ERROR, + message: '', + pos: pos + }; + switch (type) { + case CARD_LOG_TYPE_COMPONENTS: + logInfo.message = `Card page cannot use the component ${name}.`; + logInfo.code = '10905216'; + break; + case CARD_LOG_TYPE_DECORATORS: + logInfo.message = `Card page cannot use ${name}`; + logInfo.code = '10905215'; + break; + case CARD_LOG_TYPE_IMPORT: + logInfo.message = `Card page cannot use import.`; + logInfo.code = '10905214'; + break; + } + log.push(logInfo); + } +} + +export function resetProcessUiSyntax(): void { + transformLog = new createAstNodeUtils.FileLog(); + contextGlobal = undefined; +} + +function createSharedStorageWithRoute(context: ts.TransformationContext, name: string, cardRelativePath: string, + entryOptionNode: ts.Expression, shouldCreateAccsessRecording: boolean, argsArr: ts.Expression[]): ts.Statement[] { + const newArray: ts.Expression[] = [ + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED), + ts.factory.createObjectLiteralExpression([], false), + createGetSharedForVariable(entryOptionNode, false) + ]; + return loadDocumentWithRoute(context, name, newArray, argsArr, false, shouldCreateAccsessRecording, + false, entryOptionNode, cardRelativePath); +} + +function insertImportModuleNode(statements: ts.Statement[], hasUseResource: boolean): ts.Statement[] { + if (isDynamic() && (hasUseResource || componentCollection.entryComponent)) { + statements.unshift(createAstNodeUtils.createImportNodeForModuleInfo()); + } + return statements; +} + +// Do you want to start dynamic bundleName or moduleName. +export function isDynamic(): boolean { + const isByteCodeHar: boolean = projectConfig.compileHar && projectConfig.byteCodeHar; + const uiTransformOptimization: boolean = !!projectConfig.uiTransformOptimization; + return uiTransformOptimization ? uiTransformOptimization : isByteCodeHar; +} diff --git a/compiler/src/interop/src/process_visual.ts b/compiler/src/interop/src/process_visual.ts new file mode 100644 index 0000000000000000000000000000000000000000..80cdcb147c2903db7df407611e4f6ccb96a639eb --- /dev/null +++ b/compiler/src/interop/src/process_visual.ts @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2023 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 ts from 'typescript'; +import fs from 'fs'; +import path from 'path'; +import { SourceMapGenerator } from 'source-map'; + +import { + validateUISyntax, + componentCollection, + ReplaceResult, + sourceReplace +} from './validate_ui_syntax'; +import { + LogType, + LogInfo, + mkDir, + emitLogInfo +} from './utils'; +import { + MODULE_ETS_PATH, + MODULE_VISUAL_PATH, + SUPERVISUAL, + SUPERVISUAL_SOURCEMAP_EXT +} from './pre_define'; + +import { projectConfig } from '../main.js'; +import { genETS } from '../../../codegen/codegen_ets.js'; + +const visualMap: Map = new Map(); +const slotMap: Map = new Map(); + +const red: string = '\u001b[31m'; +const reset: string = '\u001b[39m'; + +const compilerOptions = ts.readConfigFile( + path.resolve(__dirname, '../../../tsconfig.json'), + ts.sys.readFile +).config.compilerOptions; +compilerOptions.sourceMap = false; + +export function visualTransform(code: string, id: string, logger: any) { + const log: LogInfo[] = []; + const content: string | null = getParsedContent(code, path.normalize(id), log); + if (!content) { + return code; + } + if (log.length) { + emitLogInfo(logger, log, true, id); + } + generateSourceMapForNewAndOriEtsFile(path.normalize(id), code); + return content; +} + +export function parseVisual(resourcePath: string, resourceQuery: string, content: string, + log: LogInfo[], source: string): string { + let code: string | null = getParsedContent(content, resourcePath, log); + if (!code) { + return content; + } + const result: ReplaceResult = sourceReplace(code, resourcePath); + code = result.content; + log.concat(result.log); + const resultLog: LogInfo[] = validateUISyntax(source, code, resourcePath, resourceQuery); + log.concat(resultLog); + if (!log.length) { + generateSourceMapForNewAndOriEtsFile(resourcePath, source); + } + return code; +} + +function parseStatement(statement: ts.Statement, content: string, log: LogInfo[], + visualContent: any): string { + if (statement.kind === ts.SyntaxKind.StructDeclaration && statement.name) { + if (statement.members) { + statement.members.forEach(member => { + if (member.kind && member.kind === ts.SyntaxKind.MethodDeclaration) { + content = parseMember(statement, member, content, log, visualContent); + } + }); + } + } + return content; +} + +function parseMember(statement: ts.Statement, member: ts.MethodDeclaration, content: string, + log: LogInfo[], visualContent: any): string { + let newContent: string = content; + if (member.name && member.name.getText() === 'build') { + const buildBody: string = member.getText(); + if (buildBody.replace(/\ +/g, '').replace(/[\r\n]/g, '') === 'build(){}') { + newContent = insertVisualCode(statement, member, visualContent, newContent); + } else { + log.push({ + type: LogType.ERROR, + message: `when the corresponding visual file exists,` + + ` the build function of the entry component must be empty.`, + pos: member.pos + }); + } + } + return newContent; +} + +function insertVisualCode(statement: ts.Statement, member: ts.MethodDeclaration, + visualContent: any, content: string): string { + let newContent: string = content; + newContent = insertImport(visualContent, newContent); + newContent = insertVarAndFunc(member, visualContent, newContent, content); + newContent = insertBuild(member, visualContent, newContent, content); + newContent = insertAboutToAppear(statement, member, visualContent, newContent, content); + return newContent; +} + +function insertImport(visualContent: any, content: string): string { + if (!visualContent.etsImport) { + return content; + } + const mediaQueryImport: string = visualContent.etsImport + '\n'; + const newContent: string = mediaQueryImport + content; + slotMap.set(0, mediaQueryImport.length); + visualMap.set(0, mediaQueryImport.split('\n').length - 1); + return newContent; +} + +function insertVarAndFunc(build: ts.MethodDeclaration, visualContent: any, + content: string, oriContent: string): string { + const visualVarAndFunc: string = (visualContent.etsVariable ? visualContent.etsVariable : '') + + (visualContent.etsFunction ? visualContent.etsFunction : ''); + return visualVarAndFunc ? insertVisualCodeBeforePos(build, '\n' + visualVarAndFunc, content, + oriContent) : content; +} + +function insertBuild(build: ts.MethodDeclaration, visualContent: any, content: string, + oriContent: string): string { + return visualContent.build ? insertVisualCodeAfterPos(build.body, + '\n' + visualContent.build + '\n', content, oriContent) : content; +} + +function insertAboutToAppear(statement: ts.Statement, build: ts.MethodDeclaration, + visualContent: any, content: string, oriContent: string): string { + if (!visualContent.aboutToAppear) { + return content; + } + for (const member of statement.members) { + const hasAboutToAppear: boolean = member.kind && member.kind === ts.SyntaxKind.MethodDeclaration && + member.name && member.name.getText() === 'aboutToAppear'; + if (hasAboutToAppear) { + return insertVisualCodeAfterPos(member.body, '\n' + visualContent.aboutToAppear, content, + oriContent); + } + } + + const aboutToAppearFunc: string = '\n aboutToAppear() {\n' + visualContent.aboutToAppear + + ' }\n'; + return insertVisualCodeBeforePos(build, aboutToAppearFunc, content, oriContent); +} + +function insertVisualCodeAfterPos(member: ts.Block, visualContent: string, content: string, + oriContent: string): string { + const contentBeforePos: string = oriContent.substring(0, member.getStart() + 1); + const originEtsFileLineNumber: number = contentBeforePos.split('\n').length; + const visualLines: number = visualContent.split('\n').length - 1; + const insertedLineNumbers: number = visualMap.get(originEtsFileLineNumber); + visualMap.set(originEtsFileLineNumber, insertedLineNumbers ? insertedLineNumbers + visualLines : + visualLines); + + let newPos: number = member.getStart() + 1; + for (const [key, value] of slotMap) { + if (member.getStart() >= key) { + newPos += value; + } + } + + const newContent: string = content.substring(0, newPos) + visualContent + + content.substring(newPos); + slotMap.set(member.getStart(), visualContent.length); + return newContent; +} + +function insertVisualCodeBeforePos(member: ts.MethodDeclaration, visualContent: string, + content: string, oriContent: string): string { + const contentBeforePos: string = oriContent.substring(0, member.pos); + const originEtsFileLineNumber: number = contentBeforePos.split('\n').length; + const visualLines: number = visualContent.split('\n').length - 1; + const insertedLineNumbers: number = visualMap.get(originEtsFileLineNumber); + visualMap.set(originEtsFileLineNumber, insertedLineNumbers ? insertedLineNumbers + visualLines : + visualLines); + let newPos: number = member.pos; + for (const [key, value] of slotMap) { + if (member.pos >= key) { + newPos += value; + } + } + const newContent: string = content.substring(0, newPos) + visualContent + + content.substring(newPos); + slotMap.set(member.pos, visualContent.length); + return newContent; +} + +function generateSourceMapForNewAndOriEtsFile(resourcePath: string, content: string) { + if (!process.env.cachePath) { + return; + } + const sourcemap: SourceMapGenerator = new SourceMapGenerator({ + file: resourcePath + }); + const lines: Array = content.split('\n'); + const originEtsFileLines: number = lines.length; + for (let l: number = 1; l <= originEtsFileLines; l++) { + let newEtsFileLineNumber: number = l; + for (const [originEtsFileLineNumber, visualLines] of visualMap) { + if (l > originEtsFileLineNumber) { + newEtsFileLineNumber += visualLines; + } + } + sourcemap.addMapping({ + generated: { + line: newEtsFileLineNumber, + column: 0 + }, + source: resourcePath, + original: { + line: l, + column: 0 + } + }); + } + const visualMapName: string = path.parse(resourcePath).name + SUPERVISUAL_SOURCEMAP_EXT; + const visualDirPath: string = path.parse(resourcePath).dir; + const etsDirPath: string = path.parse(projectConfig.projectPath).dir; + let visualMapDirPath: string = path.resolve(process.env.cachePath, SUPERVISUAL + + visualDirPath.replace(etsDirPath, '')); + if (!visualDirPath.includes(etsDirPath)) { + const projectRootPath = getProjectRootPath(); + visualMapDirPath = path.resolve(process.env.cachePath, SUPERVISUAL + + visualDirPath.replace(projectRootPath, '')); + } + if (!(fs.existsSync(visualMapDirPath) && fs.statSync(visualMapDirPath).isDirectory())) { + mkDir(visualMapDirPath); + } + fs.writeFile(path.resolve(visualMapDirPath, visualMapName), sourcemap.toString(), (err) => { + if (err) { + console.error(red, 'ERROR: Failed to write visual.js.map', reset); + } + }); +} + +function getProjectRootPath(): string { + let projectRootPath = projectConfig.projectRootPath; + if (!projectRootPath) { + if (!projectConfig.aceModuleJsonPath) { //??? + projectRootPath = path.resolve(projectConfig.projectPath, '../../../../../'); + } else { + projectRootPath = path.resolve(projectConfig.projectPath, '../../../../'); + } + } + return projectRootPath; +} + +export function findVisualFile(filePath: string): string { + if (!/\.ets$/.test(filePath)) { + return ''; + } + let etsDirPath: string = path.parse(projectConfig.projectPath).dir; + let visualDirPath: string = path.parse(projectConfig.aceSuperVisualPath).dir; + let resolvePath = filePath.replace(projectConfig.projectPath, projectConfig.aceSuperVisualPath) + .replace(etsDirPath, visualDirPath).replace(/\.ets$/, '.visual'); + if (fs.existsSync(resolvePath)) { + return resolvePath; + } + try { + const projectRootPath = getProjectRootPath(); + let moduleName = ''; + const relativePath = filePath.replace(projectRootPath, ''); + const moduleNames = relativePath.split(path.sep); + for (let i = 0; i < moduleNames.length; ++i) { + if (moduleNames[i] === 'src') { + if (i >= moduleNames.length - 2) { + break; + } + const modulePath = path.join(moduleNames[i], moduleNames[i + 1], moduleNames[i + 2]); + if (modulePath === MODULE_ETS_PATH) { + break; + } + } + moduleName = path.join(moduleName, moduleNames[i]); + } + etsDirPath = path.join(projectRootPath, moduleName, MODULE_ETS_PATH); + visualDirPath = path.join(projectRootPath, moduleName, MODULE_VISUAL_PATH); + resolvePath = filePath.replace(etsDirPath, visualDirPath).replace(/\.ets$/, '.visual'); + return resolvePath; + } catch (e) { + // avoid projectConfig attributes has undefined value + return ''; + } +} + +function getVisualContent(visualPath: string, log: LogInfo[]): any { + const parseContent: any = genETS(fs.readFileSync(visualPath, 'utf-8')); + if (parseContent && parseContent.errorType && parseContent.errorType !== '') { + log.push({ + type: LogType.ERROR, + message: parseContent.errorMessage + }); + } + return parseContent ? parseContent.ets : null; +} + +function getParsedContent(code: string, id: string, log: LogInfo[]): string | null { + if (!projectConfig.aceSuperVisualPath || + !(componentCollection.entryComponent || componentCollection.customComponents)) { + return null; + } + const visualPath: string = findVisualFile(id); + if (!visualPath || !fs.existsSync(visualPath)) { + return null; + } + const visualContent: any = getVisualContent(visualPath, log); + if (!visualContent) { + return null; + } + clearVisualSlotMap(); + const sourceFile: ts.SourceFile = ts.createSourceFile( + id, + code, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.ETS, + compilerOptions + ); + let content: string = code; + if (sourceFile.statements) { + sourceFile.statements.forEach(statement => { + content = parseStatement(statement, content, log, visualContent); + }); + } + return content; +} + +function clearVisualSlotMap(): void { + visualMap.clear(); + slotMap.clear(); +} \ No newline at end of file diff --git a/compiler/src/interop/src/resolve_ohm_url.ts b/compiler/src/interop/src/resolve_ohm_url.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1ab4836c647f41e7cc7df665b94c18fe485a61a --- /dev/null +++ b/compiler/src/interop/src/resolve_ohm_url.ts @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023 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 { logger } from './compile_info'; +const { projectConfig } = require('../main'); + +const red: string = '\u001b[31m'; +const reset: string = '\u001b[39m'; + +const REG_OHM_URL: RegExp = /^@bundle:(\S+)\/(\S+)\/(ets|js)\/(\S+)$/; + +export class OHMResolverPlugin { + private source: any; + private target: any; + + constructor(source = 'resolve', target = 'resolve') { + this.source = source; + this.target = target; + } + + apply(resolver) { + const target: any = resolver.ensureHook(this.target); + resolver.getHook(this.source).tapAsync('OHMResolverPlugin', (request, resolveContext, callback) => { + if (isOhmUrl(request.request)) { + const resolvedSourceFile: string = resolveSourceFile(request.request); + const obj = Object.assign({}, request, { + request: resolvedSourceFile + }); + return resolver.doResolve(target, obj, null, resolveContext, callback); + } + callback(); + return undefined; + }); + } +} + +export function isOhmUrl(moduleRequest: string): boolean { + return !!/^@(\S+):/.test(moduleRequest); +} + +function addExtension(file: string, srcPath: string): string { + let extension: string = '.d.ts'; + if (fs.existsSync(file + '.ets') && fs.statSync(file + '.ets').isFile()) { + extension = '.ets'; + } + if (fs.existsSync(file + '.ts') && fs.statSync(file + '.ts').isFile()) { + if (extension !== '.d.ts') { + logger.error(red, `ArkTS:ERROR Failed to compile with files with same name ${srcPath} in the same directory`, reset); + } + extension = '.ts'; + } + if (fs.existsSync(file + '.js') && fs.statSync(file + '.js').isFile()) { + if (extension !== '.d.ts') { + logger.error(red, `ArkTS:ERROR Failed to compile with files with same name ${srcPath} in the same directory`, reset); + } + extension = '.js'; + } + return file + extension; +} + +export function resolveSourceFile(ohmUrl: string): string { + if (!projectConfig.aceBuildJson) { + logger.error(red, `ArkTS:ERROR Failed to resolve OhmUrl because of aceBuildJson not existing `, reset); + return ohmUrl; + } + + const result: RegExpMatchArray = ohmUrl.match(REG_OHM_URL); + const moduleName: string = result[2]; + const srcKind: string = result[3]; + + const modulePath: string = projectConfig.modulePathMap[moduleName]; + const srcRoot: string = projectConfig.isOhosTest ? 'src/ohosTest' : 'src/main'; + let file: string = path.join(modulePath, srcRoot, srcKind, result[4]); + file = addExtension(file, result[4]); + + if (!fs.existsSync(file) || !fs.statSync(file).isFile()) { + if (projectConfig.isOhosTest) { + file = path.join(modulePath, 'src/main', srcKind, result[4]); + file = addExtension(file, result[4]); + + if (fs.existsSync(file) && fs.statSync(file).isFile()) { + return file; + } + } + + logger.error(red, `ArkTS:ERROR Failed to resolve existed file by this ohm url ${ohmUrl} `, reset); + } + + return file; +} diff --git a/compiler/src/interop/src/result_process.ts b/compiler/src/interop/src/result_process.ts new file mode 100644 index 0000000000000000000000000000000000000000..b37a77ac08e33a07e9c57dafeadc80792c78ea70 --- /dev/null +++ b/compiler/src/interop/src/result_process.ts @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 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 { BUILD_OFF, ESMODULE, JSBUNDLE } from './pre_define'; +import { + resetLog, + transformLog +} from './process_ui_syntax'; +import { + propertyCollection, + linkCollection, + processSystemApi +} from './validate_ui_syntax'; +import { + emitLogInfo, + componentInfo, + generateSourceFilesInHar, + getTransformLog +} from './utils'; +import { generateSourceFilesToTemporary } from './ark_utils'; +import { resetComponentCollection } from './validate_ui_syntax'; +import { abilityConfig, projectConfig } from '../main'; +import { logger } from './compile_info'; +import processStructComponentV2 from './process_struct_componentV2'; + +module.exports = function resultProcess(source: string, map: any): void { + process.env.compiler = BUILD_OFF; + source = processSystemApi(source, true, this.resourcePath); + if (/\.(ets|ts)$/.test(this.resourcePath)) { + componentInfo.id = 0; + propertyCollection.clear(); + linkCollection.clear(); + resetComponentCollection(); + processStructComponentV2.resetStructMapInEts(); + if (transformLog && transformLog.errors.length) { + emitLogInfo(this, getTransformLog(transformLog)); + resetLog(); + } + } + if (projectConfig.compileMode === JSBUNDLE && + [abilityConfig.abilityEntryFile].concat(abilityConfig.projectAbilityPath).concat(abilityConfig.testRunnerFile).includes(this.resourcePath)) { + source = source.replace(/exports\.default/, 'globalThis.exports.default'); + } + + if (projectConfig.compileMode === ESMODULE && projectConfig.processTs === false && + process.env.compilerType && process.env.compilerType === 'ark' && !projectConfig.compileHar) { + generateSourceFilesToTemporary(this.resourcePath, source, map, projectConfig, logger); + } + + if (projectConfig.compileHar) { + generateSourceFilesInHar(this.resourcePath, source, '.js', projectConfig); + } + + this.callback(null, source, map); +}; diff --git a/compiler/src/interop/src/utils.ts b/compiler/src/interop/src/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..0819fb76b92f0b43df382a1e46f9990c5ff6d055 --- /dev/null +++ b/compiler/src/interop/src/utils.ts @@ -0,0 +1,1333 @@ +/* + * Copyright (c) 2021 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 path from 'path'; +import ts from 'typescript'; +import fs from 'fs'; +import os from 'os'; +import uglifyJS from 'uglify-js'; + +import { + partialUpdateConfig, + projectConfig, + globalProgram +} from '../main'; +import { createHash } from 'crypto'; +import type { Hash } from 'crypto'; +import { + AUXILIARY, + EXTNAME_ETS, + EXTNAME_JS, + MAIN, + FAIL, + TEMPORARY, + ESMODULE, + $$, + EXTEND_DECORATORS, + COMPONENT_EXTEND_DECORATOR, + COMPONENT_ANIMATABLE_EXTEND_DECORATOR, + COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU, + GET_SHARED, + COMPONENT_CONSTRUCTOR_UNDEFINED, + USE_SHARED_STORAGE, + STORAGE +} from './pre_define'; +import { + ERROR_DESCRIPTION, + HvigorLogInfo, + HvigorErrorInfo +} from './hvigor_error_code/hvigor_error_info'; + +export enum LogType { + ERROR = 'ERROR', + WARN = 'WARN', + NOTE = 'NOTE' +} +export const TEMPORARYS: string = 'temporarys'; +export const BUILD: string = 'build'; +export const SRC_MAIN: string = 'src/main'; + +const red: string = '\u001b[31m'; +const reset: string = '\u001b[39m'; + +const WINDOWS: string = 'Windows_NT'; +const LINUX: string = 'Linux'; +const MAC: string = 'Darwin'; +const HARMONYOS: string = 'HarmonyOS'; + +export interface LogInfo extends HvigorLogInfo { + type: LogType, + message: string, + pos?: number, + line?: number, + column?: number, + fileName?: string +} + +export const repeatLog: Map = new Map(); + +export interface IFileLog { + sourceFile: ts.SourceFile | undefined; + errors: LogInfo[]; + cleanUp(): void +} + +export function buildErrorInfoFromLogInfo(info: LogInfo, fileName: string): HvigorErrorInfo { + const positionMessage: string = info.line && info.column ? `${fileName}:${info.line}:${info.column}` : fileName; + return new HvigorErrorInfo() + .setCode(info.code) + .setDescription(info.description ?? ERROR_DESCRIPTION) + .setCause(info.cause ?? info.message) + .setPosition(`File: ${positionMessage}`) + .setSolutions(info.solutions ?? []); +} + +export function isHvigorLogInfo(logInfo: LogInfo): boolean { + // Hvigor logger requires code as mendatory attribute. + // ArkUI warnings or info are not using Hvigor logger to emit. + return logInfo.code !== undefined; +} + +export function emitLogInfo(loader: Object, infos: LogInfo[], fastBuild: boolean = false, + resourcePath: string | null = null, hvigorLogger: Object | undefined = undefined): void { + if (infos && infos.length) { + infos.forEach((item) => { + hvigorLogger ? emitLogInfoFromHvigorLogger(hvigorLogger, item, loader, fastBuild, resourcePath) + : emitLogInfoFromLoader(loader, item, fastBuild, resourcePath); + }); + } +} + +function emitLogInfoFromHvigorLogger(hvigorLogger: Object, info: LogInfo, loader: Object, + fastBuild: boolean, resourcePath: string | null): void { + if (!isHvigorLogInfo(info)) { + emitLogInfoFromLoader(loader, info, fastBuild, resourcePath); + return; + } + const errorInfo: HvigorErrorInfo = buildErrorInfoFromLogInfo(info, info.fileName || resourcePath); + switch (info.type) { + case LogType.ERROR: + hvigorLogger.printError(errorInfo); + break; + case LogType.WARN: + hvigorLogger.printWarn(errorInfo.cause); + break; + case LogType.NOTE: + hvigorLogger.printInfo(errorInfo.cause); + break; + } +} + +function emitLogInfoFromLoader(loader: Object, info: LogInfo, fastBuild: boolean = false, + resourcePath: string | null = null): void { + const message: string = fastBuild ? getMessage(info.fileName || resourcePath, info, true) + : getMessage(info.fileName || loader.resourcePath, info); + switch (info.type) { + case LogType.ERROR: + fastBuild ? loader.error(message) : loader.emitError(message); + break; + case LogType.WARN: + fastBuild ? loader.warn(message) : loader.emitWarning(message); + break; + case LogType.NOTE: + fastBuild ? loader.info(message) : loader.emitWarning(message); + break; + } +} + +export function addLog( + type: LogType, + message: string, + pos: number, + log: LogInfo[], + sourceFile: ts.SourceFile, + hvigorLogInfo?: HvigorLogInfo +): void { + const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(pos); + log.push({ + type: type, + message: message, + line: posOfNode.line + 1, + column: posOfNode.character + 1, + fileName: sourceFile.fileName, + ...hvigorLogInfo + }); +} + +export function getMessage(fileName: string, info: LogInfo, fastBuild: boolean = false): string { + let message: string; + if (info.line && info.column) { + message = `BUILD${info.type} File: ${fileName}:${info.line}:${info.column}\n ${info.message}`; + } else { + message = `BUILD${info.type} File: ${fileName}\n ${info.message}`; + } + if (fastBuild) { + message = message.replace(/^BUILD/, 'ArkTS:'); + } + return message; +} + +export function getTransformLog(transformLog: IFileLog): LogInfo[] { + const sourceFile: ts.SourceFile = transformLog.sourceFile; + const logInfos: LogInfo[] = transformLog.errors.map((item) => { + if (item.pos || item.pos === 0) { + if (!item.column || !item.line) { + const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(item.pos); + item.line = posOfNode.line + 1; + item.column = posOfNode.character + 1; + } + } else { + item.line = item.line || undefined; + item.column = item.column || undefined; + } + if (!item.fileName) { + item.fileName = sourceFile.fileName; + } + return item; + }); + return logInfos; +} + +class ComponentInfo { + private _id: number = 0; + private _componentNames: Set = new Set(['ForEach']); + public set id(id: number) { + this._id = id; + } + public get id(): number { + return this._id; + } + public set componentNames(componentNames: Set) { + this._componentNames = componentNames; + } + public get componentNames(): Set { + return this._componentNames; + } +} + +export let componentInfo: ComponentInfo = new ComponentInfo(); + +export function hasDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | + ts.StructDeclaration | ts.ClassDeclaration, decortorName: string, + customBuilder: ts.Decorator[] = null, log: LogInfo[] = null): boolean { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (decorators && decorators.length) { + const extendResult = { + Extend: false, + AnimatableExtend: false + }; + for (let i = 0; i < decorators.length; i++) { + const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim(); + if (log && EXTEND_DECORATORS.includes(decortorName)) { + if (originalDecortor === COMPONENT_EXTEND_DECORATOR) { + extendResult.Extend = true; + } + if (originalDecortor === COMPONENT_ANIMATABLE_EXTEND_DECORATOR) { + extendResult.AnimatableExtend = true; + } + } else { + if (originalDecortor === decortorName) { + if (customBuilder) { + customBuilder.push(...decorators.slice(i + 1), ...decorators.slice(0, i)); + } + return true; + } + } + } + if (log && extendResult.Extend && extendResult.AnimatableExtend) { + log.push({ + type: LogType.ERROR, + message: `The function can not be decorated by '@Extend' and '@AnimatableExtend' at the same time.`, + pos: node.getStart(), + code: '10905111' + }); + } + return (decortorName === COMPONENT_EXTEND_DECORATOR && extendResult.Extend) || + (decortorName === COMPONENT_ANIMATABLE_EXTEND_DECORATOR && extendResult.AnimatableExtend); + } + return false; +} + +const STATEMENT_EXPECT: number = 1128; +const SEMICOLON_EXPECT: number = 1005; +const STATESTYLES_EXPECT: number = 1003; +export const IGNORE_ERROR_CODE: number[] = [STATEMENT_EXPECT, SEMICOLON_EXPECT, STATESTYLES_EXPECT]; + +export function readFile(dir: string, utFiles: string[]): void { + try { + const files: string[] = fs.readdirSync(dir); + files.forEach((element) => { + const filePath: string = path.join(dir, element); + const status: fs.Stats = fs.statSync(filePath); + if (status.isDirectory()) { + readFile(filePath, utFiles); + } else { + utFiles.push(filePath); + } + }); + } catch (e) { + console.error(red, 'ArkTS ERROR: ' + e, reset); + } +} + +export function circularFile(inputPath: string, outputPath: string): void { + if (!inputPath || !outputPath) { + return; + } + fs.readdir(inputPath, function (err, files) { + if (!files) { + return; + } + files.forEach(file => { + const inputFile: string = path.resolve(inputPath, file); + const outputFile: string = path.resolve(outputPath, file); + const fileStat: fs.Stats = fs.statSync(inputFile); + if (fileStat.isFile()) { + copyFile(inputFile, outputFile); + } else { + circularFile(inputFile, outputFile); + } + }); + }); +} + +function copyFile(inputFile: string, outputFile: string): void { + try { + const parent: string = path.join(outputFile, '..'); //??? + if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { + mkDir(parent); + } + if (fs.existsSync(outputFile)) { + return; + } + const readStream: fs.ReadStream = fs.createReadStream(inputFile); + const writeStream: fs.WriteStream = fs.createWriteStream(outputFile); + readStream.pipe(writeStream); + readStream.on('close', function () { + writeStream.end(); + }); + } catch (err) { + throw err.message; + } +} + +export function mkDir(path_: string): void { + const parent: string = path.join(path_, '..'); + if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { + mkDir(parent); + } + fs.mkdirSync(path_); +} + +export function toUnixPath(data: string): string { + if (/^win/.test(require('os').platform())) { + const fileTmps: string[] = data.split(path.sep); + const newData: string = path.posix.join(...fileTmps); + return newData; + } + return data; +} + +export function tryToLowerCasePath(filePath: string): string { + return toUnixPath(filePath).toLowerCase(); +} + +export function toHashData(path: string): string { + const content: string = fs.readFileSync(path).toString(); + const hash: Hash = createHash('sha256'); + hash.update(content); + return hash.digest('hex'); +} + +export function writeFileSync(filePath: string, content: string): void { + if (!fs.existsSync(filePath)) { + const parent: string = path.join(filePath, '..'); + if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { + mkDir(parent); + } + } + fs.writeFileSync(filePath, content); +} +export function genLoaderOutPathOfHar(filePath: string, cachePath: string, buildPath: string, moduleRootPath: string, projectRootPath): string { + filePath = toUnixPath(filePath); + buildPath = toUnixPath(buildPath); + const cacheRootPath: string = toUnixPath(cachePath); + const moduleName = toUnixPath(moduleRootPath).replace(toUnixPath(projectRootPath), ''); + const relativeFilePath: string = filePath.replace(cacheRootPath, '').replace(moduleName, ''); + const output: string = path.join(buildPath, relativeFilePath); + return output; +} + +export function genTemporaryPath(filePath: string, projectPath: string, buildPath: string, projectConfig: Object, + metaInfo: Object, buildInHar: boolean = false): string { + filePath = toUnixPath(filePath).replace(/\.[cm]js$/, EXTNAME_JS); + projectPath = toUnixPath(projectPath); + + if (process.env.compileTool === 'rollup') { + let relativeFilePath: string = ''; + if (metaInfo) { + if (metaInfo.isLocalDependency) { + // When buildInHar and compileHar are both True, + // this is the path under the PackageHar directory being spliced ​​together. + // Here, only the relative path based on moduleRootPath needs to be retained. + // eg. moduleA/index.js --> index.js --> PackageHar/index.js + // eg. moduleA/src/main/ets/test.js --> src/main/ets/test.js --> PackageHar/src/main/ets/test.js + const moduleName: string = buildInHar && projectConfig.compileHar ? '' : metaInfo.moduleName; + relativeFilePath = filePath.replace(toUnixPath(metaInfo.belongModulePath), moduleName); + } else { + relativeFilePath = filePath.replace(toUnixPath(metaInfo.belongProjectPath), ''); + } + } else { + relativeFilePath = filePath.replace(toUnixPath(projectConfig.projectRootPath), ''); + } + const output: string = path.join(buildPath, relativeFilePath); + return output; + } + + if (isPackageModulesFile(filePath, projectConfig)) { + const packageDir: string = projectConfig.packageDir; + const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir)); + let output: string = ''; + if (filePath.indexOf(fakePkgModulesPath) === -1) { + const hapPath: string = toUnixPath(projectConfig.projectRootPath); + const tempFilePath: string = filePath.replace(hapPath, ''); + const relativeFilePath: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1); + output = path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, MAIN, relativeFilePath); + } else { + output = filePath.replace(fakePkgModulesPath, + path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, AUXILIARY)); + } + return output; + } + + if (filePath.indexOf(projectPath) !== -1) { + const relativeFilePath: string = filePath.replace(projectPath, ''); + const output: string = path.join(buildPath, buildInHar ? '' : TEMPORARY, relativeFilePath); + return output; + } + + return ''; +} + +export function isPackageModulesFile(filePath: string, projectConfig: Object): boolean { + filePath = toUnixPath(filePath); + const hapPath: string = toUnixPath(projectConfig.projectRootPath); + const tempFilePath: string = filePath.replace(hapPath, ''); + const packageDir: string = projectConfig.packageDir; + if (tempFilePath.indexOf(packageDir) !== -1) { + const fakePkgModulesPath: string = toUnixPath(path.resolve(projectConfig.projectRootPath, packageDir)); + if (filePath.indexOf(fakePkgModulesPath) !== -1) { + return true; + } + if (projectConfig.modulePathMap) { + for (const key in projectConfig.modulePathMap) { + const value: string = projectConfig.modulePathMap[key]; + const fakeModulePkgModulesPath: string = toUnixPath(path.resolve(value, packageDir)); + if (filePath.indexOf(fakeModulePkgModulesPath) !== -1) { + return true; + } + } + } + } + + return false; +} + +export interface GeneratedFileInHar { + sourcePath: string; + sourceCachePath?: string; + obfuscatedSourceCachePath?: string; + originalDeclarationCachePath?: string; + originalDeclarationContent?: string; + obfuscatedDeclarationCachePath?: string; +} + +export const harFilesRecord: Map = new Map(); + +export function generateSourceFilesInHar(sourcePath: string, sourceContent: string, suffix: string, + projectConfig: Object, modulePathMap?: Object): void { + const belongModuleInfo: Object = getBelongModuleInfo(sourcePath, modulePathMap, projectConfig.projectRootPath); + // compileShared: compile shared har of project + let jsFilePath: string = genTemporaryPath(sourcePath, + projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath, + projectConfig.compileShared || projectConfig.byteCodeHar ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath, //??? + projectConfig, belongModuleInfo, projectConfig.compileShared); + if (!jsFilePath.match(new RegExp(projectConfig.packageDir))) { + jsFilePath = jsFilePath.replace(/\.ets$/, suffix).replace(/\.ts$/, suffix); + if (projectConfig.obfuscateHarType === 'uglify' && suffix === '.js') { + sourceContent = uglifyJS.minify(sourceContent).code; + } + // collect the declaration files for obfuscation + if (projectConfig.compileMode === ESMODULE && (/\.d\.e?ts$/).test(jsFilePath)) { + sourcePath = toUnixPath(sourcePath); + const genFilesInHar: GeneratedFileInHar = { + sourcePath: sourcePath, + originalDeclarationCachePath: jsFilePath, + originalDeclarationContent: sourceContent + }; + harFilesRecord.set(sourcePath, genFilesInHar); + return; + } else { + mkdirsSync(path.dirname(jsFilePath)); + } + fs.writeFileSync(jsFilePath, sourceContent); + } +} + +export function mkdirsSync(dirname: string): boolean { + if (fs.existsSync(dirname)) { + return true; + } else if (mkdirsSync(path.dirname(dirname))) { + fs.mkdirSync(dirname); + return true; + } + + return false; +} + +export function nodeLargeOrEqualTargetVersion(targetVersion: number): boolean { + const currentNodeVersion: number = parseInt(process.versions.node.split('.')[0]); + if (currentNodeVersion >= targetVersion) { + return true; + } + + return false; +} + +export function removeDir(dirName: string): void { + if (fs.existsSync(dirName)) { + if (nodeLargeOrEqualTargetVersion(16)) { + fs.rmSync(dirName, { recursive: true }); + } else { + fs.rmdirSync(dirName, { recursive: true }); + } + } +} + +export function parseErrorMessage(message: string): string { + const messageArrary: string[] = message.split('\n'); + let logContent: string = ''; + messageArrary.forEach(element => { + if (!(/^at/.test(element.trim()))) { + logContent = logContent + element + '\n'; + } + }); + return logContent; +} + +export function isWindows(): boolean { + return os.type() === WINDOWS; +} + +export function isLinux(): boolean { + return os.type() === LINUX; +} + +export function isMac(): boolean { + return os.type() === MAC; +} + +export function isHarmonyOs(): boolean { + return os.type() === HARMONYOS; +} + +export function maxFilePathLength(): number { + if (isWindows()) { + return 32766; + } else if (isLinux() || isHarmonyOs()) { + return 4095; + } else if (isMac()) { + return 1016; + } else { + return -1; + } +} + +export function validateFilePathLength(filePath: string, logger: Object): boolean { + if (maxFilePathLength() < 0) { + logger.error(red, 'Unknown OS platform', reset); + process.exitCode = FAIL; + return false; + } else if (filePath.length > 0 && filePath.length <= maxFilePathLength()) { + return true; + } else if (filePath.length > maxFilePathLength()) { + logger.error(red, `The length of ${filePath} exceeds the limitation of current platform, which is ` + + `${maxFilePathLength()}. Please try moving the project folder to avoid deeply nested file path and try again`, + reset); + process.exitCode = FAIL; + return false; + } else { + logger.error(red, 'Validate file path failed', reset); + process.exitCode = FAIL; + return false; + } +} + +export function validateFilePathLengths(filePaths: Array, logger: any): boolean { + filePaths.forEach((filePath) => { + if (!validateFilePathLength(filePath, logger)) { + return false; + } + return true; + }); + return true; +} + +export function unlinkSync(filePath: string): void { + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } +} + +export function getExtensionIfUnfullySpecifiedFilepath(filePath: string): string { + if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { + return ''; + } + + let extension: string = EXTNAME_ETS; + if (fs.existsSync(filePath + '.ts') && fs.statSync(filePath + '.ts').isFile()) { + extension = '.ts'; + } else if (fs.existsSync(filePath + '.d.ts') && fs.statSync(filePath + '.d.ts').isFile()) { + extension = '.d.ts'; + } else if (fs.existsSync(filePath + '.d.ets') && fs.statSync(filePath + '.d.ets').isFile()) { + extension = '.d.ets'; + } else if (fs.existsSync(filePath + '.js') && fs.statSync(filePath + '.js').isFile()) { + extension = '.js'; + } else if (fs.existsSync(filePath + '.json') && fs.statSync(filePath + '.json').isFile()) { + extension = '.json'; + } + + return extension; +} + +export function shouldWriteChangedList(watchModifiedFiles: string[], + watchRemovedFiles: string[]): boolean { + if (projectConfig.compileMode === ESMODULE && process.env.watchMode === 'true' && !projectConfig.isPreview && + projectConfig.changedFileList && (watchRemovedFiles.length + watchModifiedFiles.length)) { + if (process.env.compileTool !== 'rollup') { + if (!(watchModifiedFiles.length === 1 && + watchModifiedFiles[0] === projectConfig.projectPath && !watchRemovedFiles.length)) { + return true; + } else { + return false; + } + } + return true; + } + return false; +} + +interface HotReloadIncrementalTime { + hotReloadIncrementalStartTime: string; + hotReloadIncrementalEndTime: string; +} + +export const hotReloadIncrementalTime: HotReloadIncrementalTime = { + hotReloadIncrementalStartTime: '', + hotReloadIncrementalEndTime: '' +}; + +interface FilesObj { + modifiedFiles: string[], + removedFiles: string[] +} + +let allModifiedFiles: Set = new Set(); + +export function getHotReloadFiles(watchModifiedFiles: string[], + watchRemovedFiles: string[], hotReloadSupportFiles: Set): FilesObj { + hotReloadIncrementalTime.hotReloadIncrementalStartTime = new Date().getTime().toString(); + watchRemovedFiles = watchRemovedFiles.map(file => path.relative(projectConfig.projectPath, file)); + allModifiedFiles = new Set([...allModifiedFiles, ...watchModifiedFiles + .filter(file => fs.statSync(file).isFile() && + (hotReloadSupportFiles.has(file) || !['.ets', '.ts', '.js'].includes(path.extname(file)))) + .map(file => path.relative(projectConfig.projectPath, file))] + .filter(file => !watchRemovedFiles.includes(file))); + return { + modifiedFiles: [...allModifiedFiles], + removedFiles: [...watchRemovedFiles] + }; +} + +export function getResolveModules(projectPath: string, faMode: boolean): string[] { + if (faMode) { + return [ + path.resolve(projectPath, '../../../../../'), //??? + path.resolve(projectPath, '../../../../' + projectConfig.packageDir), + path.resolve(projectPath, '../../../../../' + projectConfig.packageDir), + path.resolve(projectPath, '../../') + ]; + } else { + return [ + path.resolve(projectPath, '../../../../'), + path.resolve(projectPath, '../../../' + projectConfig.packageDir), + path.resolve(projectPath, '../../../../' + projectConfig.packageDir), + path.resolve(projectPath, '../') + ]; + } +} + +export function writeUseOSFiles(useOSFiles: Set): void { + let info: string = ''; + if (!fs.existsSync(projectConfig.aceSoPath)) { + const parent: string = path.resolve(projectConfig.aceSoPath, '..'); + if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { + mkDir(parent); + } + } else { + info = fs.readFileSync(projectConfig.aceSoPath, 'utf-8') + '\n'; + } + fs.writeFileSync(projectConfig.aceSoPath, info + Array.from(useOSFiles).join('\n')); +} + + +export function writeCollectionFile(cachePath: string, appCollection: Map>, + allComponentsOrModules: Map>, fileName: string, allFiles: Set = null, + widgetPath: string = undefined): void { + for (let key of appCollection.keys()) { + if (appCollection.get(key).size === 0) { + allComponentsOrModules.delete(key); + continue; + } + if (allFiles && !allFiles.has(key)) { + continue; + } + const newKey: string = projectConfig.projectRootPath ? path.relative(projectConfig.projectRootPath, key) : key; + allComponentsOrModules.set(newKey, Array.from(appCollection.get(key))); + } + const content: string = JSON.stringify(Object.fromEntries(allComponentsOrModules), null, 2); + writeFileSync(path.resolve(cachePath, fileName), content); + if (widgetPath) { + writeFileSync(path.resolve(widgetPath, fileName), content); + } +} + +export function getAllComponentsOrModules(allFiles: Set, + cacheCollectionFileName: string): Map> { + const cacheCollectionFilePath: string = path.resolve(projectConfig.cachePath, cacheCollectionFileName); + const allComponentsOrModules: Map> = new Map(); + if (!fs.existsSync(cacheCollectionFilePath)) { + return allComponentsOrModules; + } + const lastComponentsOrModules = require(cacheCollectionFilePath); + for (let key in lastComponentsOrModules) { + if (allFiles.has(key)) { + allComponentsOrModules.set(key, lastComponentsOrModules[key]); + } + } + return allComponentsOrModules; +} + +export function getPossibleBuilderTypeParameter(parameters: ts.ParameterDeclaration[]): string[] { + const parameterNames: string[] = []; + if (!partialUpdateConfig.builderCheck) { + if (isDollarParameter(parameters)) { + parameters[0].type.members.forEach((member) => { + if (member.name && ts.isIdentifier(member.name)) { + parameterNames.push(member.name.escapedText.toString()); + } + }); + } else { + parameters.forEach((parameter) => { + if (parameter.name && ts.isIdentifier(parameter.name)) { + parameterNames.push(parameter.name.escapedText.toString()); + } + }); + } + } + return parameterNames; +} + +function isDollarParameter(parameters: ts.ParameterDeclaration[]): boolean { + return parameters.length === 1 && parameters[0].name && ts.isIdentifier(parameters[0].name) && + parameters[0].name.escapedText.toString() === $$ && parameters[0].type && ts.isTypeLiteralNode(parameters[0].type) && + parameters[0].type.members && parameters[0].type.members.length > 0; +} + +interface ChildrenCacheFile { + fileName: string, + mtimeMs: number, +} + +export interface CacheFile { + mtimeMs: number, + children: Array, +} + +export interface RouterInfo { + name: string, + buildFunction: string, +} + +// Global Information & Method +export class ProcessFileInfo { + buildStart: boolean = true; + wholeFileInfo: { [id: string]: SpecialArkTSFileInfo | TSFileInfo } = {}; // Save ArkTS & TS file's infomation + transformedFiles: Set = new Set(); // ArkTS & TS Files which should be transformed in this compilation + cachedFiles: string[] = []; // ArkTS & TS Files which should not be transformed in this compilation + shouldHaveEntry: string[] = []; // Which file should have @Entry decorator + resourceToFile: { [resource: string]: Set } = {}; // Resource is used by which file + lastResourceList: Set = new Set(); + resourceList: Set = new Set(); // Whole project resource + shouldInvalidFiles: Set = new Set(); + resourceTableChanged: boolean = false; + currentArkTsFile: SpecialArkTSFileInfo; + reUseProgram: boolean = false; + resourcesArr: Set = new Set(); + lastResourcesSet: Set = new Set(); + transformCacheFiles: { [fileName: string]: CacheFile } = {}; + processBuilder: boolean = false; + processGlobalBuilder: boolean = false; + processLocalBuilder: boolean = false; + builderLikeCollection: Set = new Set(); + newTsProgram: ts.Program; + changeFiles: string[] = []; + isFirstBuild: boolean = true; + processForEach: number = 0; + processLazyForEach: number = 0; + processRepeat: boolean = false; + isAsPageImport: boolean = false; + overallObjectLinkCollection: Map> = new Map(); + overallLinkCollection: Map> = new Map(); + overallBuilderParamCollection: Map> = new Map(); + lazyForEachInfo: { + forEachParameters: ts.ParameterDeclaration, + isDependItem: boolean + } = { + forEachParameters: null, + isDependItem: false + }; + routerInfo: Map> = new Map(); + hasLocalBuilderInFile: boolean = false; + + addGlobalCacheInfo(resourceListCacheInfo: string[], + resourceToFileCacheInfo: { [resource: string]: Set }, + cacheFile: { [fileName: string]: CacheFile }): void { + if (this.buildStart) { + for (const element in resourceToFileCacheInfo) { + this.resourceToFile[element] = new Set(resourceToFileCacheInfo[element]); + } + this.lastResourceList = new Set(resourceListCacheInfo); + } + if (this.resourceTableChanged) { + this.compareResourceDiff(); + } + if (cacheFile) { + this.transformCacheFiles = cacheFile; + } + } + + addFileCacheInfo(id: string, fileCacheInfo: fileInfo): void { + if (fileCacheInfo && process.env.compileMode === 'moduleJson') { + if (Array.isArray(fileCacheInfo.fileToResourceList)) { + fileCacheInfo.fileToResourceList = new Set(fileCacheInfo.fileToResourceList); + } else { + fileCacheInfo.fileToResourceList = new Set(); + } + } + if (id.match(/(?): void { + this.shouldHaveEntry = Object.values(projectConfig.entryObj as string[]).filter((item) => { + return !entryFileWithoutEntryDecorator.has(item.toLowerCase()) && item.match(/(? | string[] } = Object.assign(resourceToFileCacheInfo, this.resourceToFile); + for (const id of this.transformedFiles) { + fileCacheInfo[id] = this.wholeFileInfo[id].fileInfo; + for (const resource of this.wholeFileInfo[id].newFileToResourceList) { + if (!(fileCacheInfo[id].fileToResourceList as Set).has(resource)) { + this.resourceToFileBecomeSet(resourceToFile, resource, id); + } + } + for (const resource of fileCacheInfo[id].fileToResourceList) { + if (!this.wholeFileInfo[id].newFileToResourceList.has(resource)) { + (resourceToFile[resource] as Set).delete(id); + } + } + fileCacheInfo[id].fileToResourceList = [...this.wholeFileInfo[id].newFileToResourceList]; + } + for (const id of this.cachedFiles) { + fileCacheInfo[id].fileToResourceList = [...fileCacheInfo[id].fileToResourceList]; + } + this.resourceToFile = resourceToFile as { [resource: string]: Set }; + for (const resource in resourceToFile) { + resourceToFile[resource] = [...resourceToFile[resource]]; + } + cache.set('fileCacheInfo', fileCacheInfo); + cache.set('resourceListCacheInfo', [...this.resourceList]); + cache.set('resourceToFileCacheInfo', resourceToFile); + } else { + const cacheInfo: { [id: string]: fileInfo } = cache.get('fileCacheInfo') || {}; + for (const id of this.transformedFiles) { + cacheInfo[id] = this.wholeFileInfo[id].fileInfo; + } + cache.set('fileCacheInfo', cacheInfo); + } + } + + resourceToFileBecomeSet(resourceToFile: { [resource: string]: Set | string[] }, resource: string, id: string): void { + if (!resourceToFile[resource]) { + resourceToFile[resource] = new Set(); + } + if (resourceToFile[resource] instanceof Set) { + resourceToFile[resource].add(id); + } else if (Array.isArray(resourceToFile[resource])) { + resourceToFile[resource] = new Set(resourceToFile[resource]); + resourceToFile[resource].add(id); + } else { + return; + } + } + + updateResourceList(resource: string): void { + this.resourceList.add(resource); + } + + compareResourceDiff(): void { + // delete resource + for (const resource of this.lastResourceList) { + if (!this.resourceList.has(resource) && this.resourceToFile[resource]) { + this.resourceToFile[resource].forEach(file => { + this.shouldInvalidFiles.add(file); + }); + } + } + // create resource + for (const resource of this.resourceList) { + if (!this.resourceToFile[resource]) { + this.resourceToFile[resource] = new Set(); + } + if (!this.lastResourceList.has(resource)) { + this.resourceToFile[resource].forEach(file => { + this.shouldInvalidFiles.add(file); + }); + } + } + } + + collectResourceInFile(resource: string, file: string): void { + if (this.wholeFileInfo[file]) { + this.wholeFileInfo[file].newFileToResourceList.add(resource); + } + } + + clearCollectedInfo(cache): void { + this.buildStart = false; + this.resourceTableChanged = false; + this.isAsPageImport = false; + this.saveCacheFileInfo(cache); + this.transformedFiles = new Set(); + this.cachedFiles = []; + this.lastResourceList = new Set([...this.resourceList]); + this.shouldInvalidFiles.clear(); + this.resourcesArr.clear(); + } + setCurrentArkTsFile(): void { + this.currentArkTsFile = new SpecialArkTSFileInfo(); + } + getCurrentArkTsFile(): SpecialArkTSFileInfo { + return this.currentArkTsFile; + } +} + +export let storedFileInfo: ProcessFileInfo = new ProcessFileInfo(); + +export interface fileInfo extends tsFileInfo { + hasEntry: boolean; // Has @Entry decorator or not +} + +export interface tsFileInfo { + fileToResourceList: Set | string[]; // How much Resource is used +} + +// Save single TS file information +class TSFileInfo { + fileInfo: tsFileInfo = { + fileToResourceList: new Set() + }; + newFileToResourceList: Set = new Set(); + constructor(cacheInfo: fileInfo, etsFile?: boolean) { + if (!etsFile) { + this.fileInfo = cacheInfo || this.fileInfo; + } + } +} + +// Save single ArkTS file information +class SpecialArkTSFileInfo extends TSFileInfo { + fileInfo: fileInfo = { + hasEntry: false, + fileToResourceList: new Set() + }; + recycleComponents: Set = new Set([]); + reuseComponentsV2: Set = new Set([]); + compFromDETS: Set = new Set(); + animatableExtendAttribute: Map> = new Map(); + pkgName: string; + + constructor(cacheInfo?: fileInfo) { + super(cacheInfo, true); + this.fileInfo = cacheInfo || this.fileInfo; + } + + get hasEntry(): boolean { + return this.fileInfo.hasEntry; + } + set hasEntry(value: boolean) { + this.fileInfo.hasEntry = value; + } +} + +export function setChecker(): void { + if (globalProgram.program) { + globalProgram.checker = globalProgram.program.getTypeChecker(); + globalProgram.strictChecker = globalProgram.program.getLinterTypeChecker(); + } else if (globalProgram.watchProgram) { + globalProgram.checker = globalProgram.watchProgram.getCurrentProgram().getProgram().getTypeChecker(); + } +} +export interface ExtendResult { + decoratorName: string; + componentName: string; +} + +export function resourcesRawfile(rawfilePath: string, resourcesArr: Set, resourceName: string = ''): void { + if (fs.existsSync(process.env.rawFileResource) && fs.statSync(rawfilePath).isDirectory()) { + const files: string[] = fs.readdirSync(rawfilePath); + files.forEach((file: string) => { + if (fs.statSync(path.join(rawfilePath, file)).isDirectory()) { + resourcesRawfile(path.join(rawfilePath, file), resourcesArr, resourceName ? resourceName + '/' + file : file); + } else { + if (resourceName) { + resourcesArr.add(resourceName + '/' + file); + } else { + resourcesArr.add(file); + } + } + }); + } +} + +export function differenceResourcesRawfile(oldRawfile: Set, newRawfile: Set): boolean { + if (oldRawfile.size !== 0 && oldRawfile.size === newRawfile.size) { + for (const singleRawfiles of oldRawfile.values()) { + if (!newRawfile.has(singleRawfiles)) { + return true; + } + } + return false; + } else if (oldRawfile.size === 0 && oldRawfile.size === newRawfile.size) { + return false; + } else { + return true; + } +} + +export function isString(text: unknown): text is string { + return typeof text === 'string'; +} + +function getRollupCacheStoreKey(projectConfig: object): string { + let keyInfo: string[] = [projectConfig.compileSdkVersion, projectConfig.compatibleSdkVersion, projectConfig.runtimeOS, + projectConfig.etsLoaderPath]; + return keyInfo.join('#'); +} + +function getRollupCacheKey(projectConfig: object): string { + let isWidget: string = projectConfig.widgetCompile ? 'widget' : 'non-widget'; + let ohosTestInfo: string = 'non-ohosTest'; + if (projectConfig.testFrameworkPar) { + ohosTestInfo = JSON.stringify(projectConfig.testFrameworkPar); + } + + let keyInfo: string[] = [projectConfig.entryModuleName, projectConfig.targetName, isWidget, ohosTestInfo, + projectConfig.needCoverageInsert, projectConfig.isOhosTest]; + return keyInfo.join('#'); +} + +function clearRollupCacheStore(cacheStoreManager: object, currentKey: string): void { + if (!cacheStoreManager) { + return; + } + + for (let key of cacheStoreManager.keys()) { + if (key !== currentKey) { + cacheStoreManager.unmount(key); + } + } +} + +export function startTimeStatisticsLocation(startTimeEvent: CompileEvent): void { + if (startTimeEvent) { + startTimeEvent.start(); + } +} + +export function stopTimeStatisticsLocation(stopTimeEvent: CompileEvent): void { + if (stopTimeEvent) { + stopTimeEvent.stop(); + } +} +export let resolveModuleNamesTime: CompileEvent; +export class CompilationTimeStatistics { + hookEventFactory: HookEventFactoryType; + createProgramTime: CompileEvent; + runArkTSLinterTime: CompileEvent; + diagnosticTime: CompileEvent; + scriptSnapshotTime: CompileEvent; + processImportTime: CompileEvent; + processComponentClassTime: CompileEvent; + validateEtsTime: CompileEvent; + tsProgramEmitTime: CompileEvent; + shouldEmitJsTime: CompileEvent; + transformNodesTime: CompileEvent; + emitTime: CompileEvent; + printNodeTime: CompileEvent; + noSourceFileRebuildProgramTime: CompileEvent; + etsTransformBuildStartTime: CompileEvent; + etsTransformLoadTime: CompileEvent; + processKitImportTime: CompileEvent; + processUISyntaxTime: CompileEvent; + constructor(share, pluginName: string, hookName: string) { + if (share && share.getHookEventFactory) { + if (pluginName === 'etsChecker' && hookName === 'buildStart' && share.getHookEventFactory(pluginName, hookName)) { + this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); + this.createProgramTime = this.hookEventFactory.createEvent('createProgram'); + this.runArkTSLinterTime = this.hookEventFactory.createEvent('arkTSLinter'); + this.diagnosticTime = this.hookEventFactory.createEvent('diagnostic'); + this.scriptSnapshotTime = this.createProgramTime.createSubEvent('scriptSnapshot'); + resolveModuleNamesTime = this.hookEventFactory.createEvent('resolveModuleNames'); + } else if (pluginName === 'etsTransform' && hookName === 'transform' && share.getHookEventFactory(pluginName, hookName)) { + this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); + this.validateEtsTime = this.hookEventFactory.createEvent('validateEts'); + this.tsProgramEmitTime = this.hookEventFactory.createEvent('tsProgramEmit'); + this.shouldEmitJsTime = this.hookEventFactory.createEvent('shouldEmitJs'); + this.transformNodesTime = this.tsProgramEmitTime.createSubEvent('transformNodes'); + this.emitTime = this.tsProgramEmitTime.createSubEvent('emit'); + this.printNodeTime = this.hookEventFactory.createEvent('printNode'); + this.noSourceFileRebuildProgramTime = this.hookEventFactory.createEvent('noSourceFileRebuildProgram'); + this.processKitImportTime = this.tsProgramEmitTime.createSubEvent('processKitImport'); + this.processUISyntaxTime = this.tsProgramEmitTime.createSubEvent('processUISyntax'); + this.processImportTime = this.processUISyntaxTime.createSubEvent('processImport'); + this.processComponentClassTime = this.processUISyntaxTime.createSubEvent('processComponentClass'); + } else if (pluginName === 'etsTransform' && hookName === 'buildStart' && share.getHookEventFactory(pluginName, hookName)) { + this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); + this.etsTransformBuildStartTime = this.hookEventFactory.createEvent('etsTransformBuildStart'); + } else if (pluginName === 'etsTransform' && hookName === 'load' && share.getHookEventFactory(pluginName, hookName)) { + this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); + this.etsTransformLoadTime = this.hookEventFactory.createEvent('etsTransformLoad'); + } + } + } +} + +interface HookEventFactoryType { + createEvent(name: string): CompileEvent | undefined; +} + +type CompileEventState = 'created' | 'beginning' | 'running' | 'failed' | 'success' | 'warn'; +interface CompileEvent { + start(time?: number): CompileEvent; + stop(state?: CompileEventState, time?: number): void; + startAsyncEvent(time: number): CompileEvent; + stopAsyncEvent(state?: CompileEventState, TIME?: number): void; + createSubEvent(name: string): CompileEvent; +} + +export function resetUtils(): void { + componentInfo = new ComponentInfo(); + harFilesRecord.clear(); + storedFileInfo = new ProcessFileInfo(); +} + +export function getStoredFileInfo(): ProcessFileInfo { + return storedFileInfo; +} + +export class EntryOptionValue { + routeName: ts.Expression; + storage: ts.Expression; + useSharedStorage: ts.Expression; +} + +function sharedNode(): ts.CallExpression { + return ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU), + ts.factory.createIdentifier(GET_SHARED) + ), + undefined, + [] + ); +} + +export function createGetShared(entryOptionValue: EntryOptionValue): ts.ConditionalExpression { + return ts.factory.createConditionalExpression( + entryOptionValue.useSharedStorage, + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + sharedNode(), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + entryOptionValue.storage || ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) + ); +} + +export function createGetSharedForVariable(entryOptionNode: ts.Expression, isShare: boolean = true): ts.ConditionalExpression { + return ts.factory.createConditionalExpression( + ts.factory.createPropertyAccessExpression( + entryOptionNode, + ts.factory.createIdentifier(USE_SHARED_STORAGE) + ), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + sharedNode(), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + isShare ? ts.factory.createPropertyAccessExpression( + entryOptionNode, ts.factory.createIdentifier(STORAGE)) : + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) + ); +} + +export function judgeUseSharedStorageForExpresion(entryOptionNode: ts.Expression): ts.BinaryExpression { + return ts.factory.createBinaryExpression( + entryOptionNode, + ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + entryOptionNode, + ts.factory.createIdentifier(USE_SHARED_STORAGE) + ), + ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsToken), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) + ) + ); +} + +export function getRollupCache(rollupShareObject: object, projectConfig: object, key: string): object | undefined { + if (!rollupShareObject) { + return undefined; + } + + // Preferentially get cache object from the rollup’s cache interface. + if (rollupShareObject.cache) { + // Only the cache object’s name as the cache key is required. + return rollupShareObject.cache.get(key); + } + + // Try to get cache object from the rollup's cacheStoreManager interface. + if (rollupShareObject.cacheStoreManager) { + // The cache under cacheStoreManager is divided into two layers. The key for the first layer of cache is determined + // by the SDK and runtimeOS, accessed through the `mount` interface. The key for the second layer of cache is + // determined by the compilation task and the name of the cache object, + // accessed through `getCache` or `setCache` interface. + const cacheStoreKey: string = getRollupCacheStoreKey(projectConfig); + const cacheServiceKey: string = getRollupCacheKey(projectConfig) + '#' + key; + + // Clear the cache if the SDK path or runtimeOS changed + clearRollupCacheStore(rollupShareObject.cacheStoreManager, cacheStoreKey); + return rollupShareObject.cacheStoreManager.mount(cacheStoreKey).getCache(cacheServiceKey); + } + + return undefined; +} + +export function setRollupCache(rollupShareObject: object, projectConfig: object, key: string, value: object): void { + if (!rollupShareObject) { + return; + } + + // Preferentially set cache object to the rollup’s cache interface. + if (rollupShareObject.cache) { + // Only the cache object’s name as the cache key is required. + rollupShareObject.cache.set(key, value); + return; + } + + // Try to set cache object to the rollup's cacheStoreManager interface. + if (rollupShareObject.cacheStoreManager) { + const cacheStoreKey: string = getRollupCacheStoreKey(projectConfig); + const cacheServiceKey: string = getRollupCacheKey(projectConfig) + '#' + key; + + rollupShareObject.cacheStoreManager.mount(cacheStoreKey).setCache(cacheServiceKey, value); + } +} + +export function removeDecorator(decorators: readonly ts.Decorator[], decoratorName: string): readonly ts.Decorator[] { + return decorators.filter((item: ts.Node) => { + if (ts.isDecorator(item) && ts.isIdentifier(item.expression) && + item.expression.escapedText.toString() === decoratorName) { + return false; + } + return true; + }); +} + +export function isFileInProject(filePath: string, projectRootPath: string): boolean { + const relativeFilePath: string = toUnixPath(path.relative(toUnixPath(projectRootPath), toUnixPath(filePath))); + // When processing ohmurl, hsp's filePath is consistent with moduleRequest + return fs.existsSync(filePath) && fs.statSync(filePath).isFile() && !relativeFilePath.startsWith('../'); //??? +} + +export function getProjectRootPath(filePath: string, projectConfig: Object, rootPathSet: Object): string { + if (rootPathSet) { + for (const rootPath of rootPathSet) { + if (isFileInProject(filePath, rootPath)) { + return rootPath; + } + } + } + return projectConfig.projectRootPath; +} + +export function getBelongModuleInfo(filePath: string, modulePathMap: Object, projectRootPath: string): Object { + for (const moduleName of Object.keys(modulePathMap)) { + if (toUnixPath(filePath).startsWith(toUnixPath(modulePathMap[moduleName]) + '/')) { + return { + isLocalDependency: true, + moduleName: moduleName, + belongModulePath: modulePathMap[moduleName] + }; + } + } + return { + isLocalDependency: false, + moduleName: '', + belongModulePath: projectRootPath + }; +} \ No newline at end of file diff --git a/compiler/src/interop/src/validate_ui_syntax.ts b/compiler/src/interop/src/validate_ui_syntax.ts new file mode 100644 index 0000000000000000000000000000000000000000..036f8a6095d9d2c0831fd2a57e4d92c6e8744e91 --- /dev/null +++ b/compiler/src/interop/src/validate_ui_syntax.ts @@ -0,0 +1,1870 @@ +/* + * Copyright (c) 2021 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 ts from 'typescript'; +import path from 'path'; + +import { + INNER_COMPONENT_DECORATORS, + COMPONENT_DECORATOR_ENTRY, + COMPONENT_DECORATOR_PREVIEW, + COMPONENT_DECORATOR_COMPONENT, + COMPONENT_DECORATOR_CUSTOM_DIALOG, + NATIVE_MODULE, + SYSTEM_PLUGIN, + OHOS_PLUGIN, + INNER_COMPONENT_MEMBER_DECORATORS, + COMPONENT_FOREACH, + COMPONENT_LAZYFOREACH, + COMPONENT_STATE_DECORATOR, + COMPONENT_LINK_DECORATOR, + COMPONENT_PROP_DECORATOR, + COMPONENT_STORAGE_PROP_DECORATOR, + COMPONENT_STORAGE_LINK_DECORATOR, + COMPONENT_PROVIDE_DECORATOR, + COMPONENT_CONSUME_DECORATOR, + COMPONENT_OBJECT_LINK_DECORATOR, + COMPONENT_OBSERVED_DECORATOR, + COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, + COMPONENT_LOCAL_STORAGE_PROP_DECORATOR, + COMPONENT_CONCURRENT_DECORATOR, + CHECK_EXTEND_DECORATORS, + COMPONENT_STYLES_DECORATOR, + RESOURCE_NAME_TYPE, + COMPONENT_BUTTON, + COMPONENT_TOGGLE, + COMPONENT_BUILDERPARAM_DECORATOR, + ESMODULE, + CARD_ENABLE_DECORATORS, + CARD_LOG_TYPE_DECORATORS, + JSBUNDLE, + COMPONENT_DECORATOR_REUSEABLE, + STRUCT_DECORATORS, + STRUCT_CONTEXT_METHOD_DECORATORS, + CHECK_COMPONENT_EXTEND_DECORATOR, + CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR, + CLASS_TRACK_DECORATOR, + COMPONENT_REQUIRE_DECORATOR, + COMPONENT_SENDABLE_DECORATOR, + CLASS_MIN_TRACK_DECORATOR, + MIN_OBSERVED, + COMPONENT_NON_DECORATOR, + COMPONENT_DECORATOR_COMPONENT_V2, + OBSERVED, + SENDABLE, + TYPE, + COMPONENT_LOCAL_BUILDER_DECORATOR, + COMPONENT_DECORATOR_REUSABLE_V2 +} from './pre_define'; +import { + INNER_COMPONENT_NAMES, + AUTOMIC_COMPONENT, + SINGLE_CHILD_COMPONENT, + SPECIFIC_CHILD_COMPONENT, + BUILDIN_STYLE_NAMES, + COMPONENT_SYSTEMAPI_NAMES, + EXTEND_ATTRIBUTE, + GLOBAL_STYLE_FUNCTION, + STYLES_ATTRIBUTE, + CUSTOM_BUILDER_METHOD, + GLOBAL_CUSTOM_BUILDER_METHOD, + INNER_CUSTOM_BUILDER_METHOD, + INNER_STYLE_FUNCTION, + INNER_CUSTOM_LOCALBUILDER_METHOD +} from './component_map'; +import { + LogType, + LogInfo, + componentInfo, + addLog, + hasDecorator, + storedFileInfo, + ExtendResult +} from './utils'; +import { globalProgram, projectConfig, abilityPagesFullPath } from '../main'; +import { + collectExtend, + isExtendFunction, + transformLog, + validatorCard +} from './process_ui_syntax'; +import { + isBuilderOrLocalBuilder, + builderConditionType +} from './process_component_class'; +import { stateObjectCollection } from './process_component_member'; +import { collectSharedModule } from './fast_build/ark_compiler/check_shared_module'; +import constantDefine from './constant_define'; +import processStructComponentV2, { StructInfo } from './process_struct_componentV2'; +import logMessageCollection from './log_message_collection'; + +export class ComponentCollection { + localStorageName: string = null; + localStorageNode: ts.Identifier | ts.ObjectLiteralExpression = null; + localSharedStorage: ts.Node = null; + entryComponentPos: number = null; + entryComponent: string = null; + previewComponent: Array = []; + customDialogs: Set = new Set([]); + customComponents: Set = new Set([]); + currentClassName: string = null; +} + +export class IComponentSet { + properties: Set = new Set(); + regulars: Set = new Set(); + states: Set = new Set(); + links: Set = new Set(); + props: Set = new Set(); + storageProps: Set = new Set(); + storageLinks: Set = new Set(); + provides: Set = new Set(); + consumes: Set = new Set(); + objectLinks: Set = new Set(); + localStorageLink: Map> = new Map(); + localStorageProp: Map> = new Map(); + builderParams: Set = new Set(); + builderParamData: Set = new Set(); + propData: Set = new Set(); + regularInit: Set = new Set(); + stateInit: Set = new Set(); + provideInit: Set = new Set(); + privateCollection: Set = new Set(); + regularStaticCollection: Set = new Set(); +} + +export let componentCollection: ComponentCollection = new ComponentCollection(); + +export const observedClassCollection: Set = new Set(); +export const enumCollection: Set = new Set(); +export const classMethodCollection: Map>> = new Map(); +export const dollarCollection: Set = new Set(); + +export const propertyCollection: Map> = new Map(); +export const stateCollection: Map> = new Map(); +export const linkCollection: Map> = new Map(); +export const propCollection: Map> = new Map(); +export const regularCollection: Map> = new Map(); +export const storagePropCollection: Map> = new Map(); +export const storageLinkCollection: Map> = new Map(); +export const provideCollection: Map> = new Map(); +export const consumeCollection: Map> = new Map(); +export const objectLinkCollection: Map> = new Map(); +export const builderParamObjectCollection: Map> = new Map(); +export const localStorageLinkCollection: Map>> = new Map(); +export const localStoragePropCollection: Map>> = new Map(); +export const builderParamInitialization: Map> = new Map(); +export const propInitialization: Map> = new Map(); +export const regularInitialization: Map> = new Map(); +export const stateInitialization: Map> = new Map(); +export const provideInitialization: Map> = new Map(); +export const privateCollection: Map> = new Map(); +export const regularStaticCollection: Map> = new Map(); + +export const isStaticViewCollection: Map = new Map(); + +export const useOSFiles: Set = new Set(); +export const sourcemapNamesCollection: Map> = new Map(); +export const originalImportNamesMap: Map = new Map(); + +export function validateUISyntax(source: string, content: string, filePath: string, + fileQuery: string, sourceFile: ts.SourceFile = null): LogInfo[] { + let log: LogInfo[] = []; + if (process.env.compileMode === 'moduleJson' || + path.resolve(filePath) !== path.resolve(projectConfig.projectPath || '', 'app.ets')) { + componentCollection = new ComponentCollection(); + const res: LogInfo[] = checkComponentDecorator(source, filePath, fileQuery, sourceFile); + if (res) { + log = log.concat(res); + } + const allComponentNames: Set = + new Set([...INNER_COMPONENT_NAMES, ...componentCollection.customComponents]); + checkUISyntax(filePath, allComponentNames, content, log, sourceFile, fileQuery); + componentCollection.customComponents.forEach(item => componentInfo.componentNames.add(item)); + } + + if (projectConfig.compileMode === ESMODULE) { + collectSharedModule(source, filePath, sourceFile); + } + + return log; +} + +function checkComponentDecorator(source: string, filePath: string, + fileQuery: string, sourceFile: ts.SourceFile | null): LogInfo[] | null { + const log: LogInfo[] = []; + if (!sourceFile) { + sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); + } + if (sourceFile && sourceFile.statements && sourceFile.statements.length) { + const result: DecoratorResult = { + entryCount: 0, + previewCount: 0 + }; + sourceFile.statements.forEach((item, index, arr) => { + if (isObservedClass(item)) { + // @ts-ignore + observedClassCollection.add(item.name.getText()); + } + if (ts.isEnumDeclaration(item) && item.name) { + enumCollection.add(item.name.getText()); + } + if (ts.isStructDeclaration(item)) { + validateStructSpec(item, result, log, sourceFile); + } + if (ts.isMissingDeclaration(item)) { + const decorators = ts.getAllDecorators(item); + for (let i = 0; i < decorators.length; i++) { + if (decorators[i] && /struct/.test(decorators[i].getText())) { + const message: string = `Please use a valid decorator.`; + addLog(LogType.ERROR, message, item.getStart(), log, sourceFile, { code: '10905234' }); + break; + } + } + } + }); + if (process.env.compileTool === 'rollup') { + if (result.entryCount > 0) { + storedFileInfo.wholeFileInfo[filePath].hasEntry = true; + } else { + storedFileInfo.wholeFileInfo[filePath].hasEntry = false; + } + } + validateEntryAndPreviewCount(result, fileQuery, sourceFile.fileName, projectConfig.isPreview, + !!projectConfig.checkEntry, log); + } + + return log.length ? log : null; +} + +function validateStructSpec(item: ts.StructDeclaration, result: DecoratorResult, log: LogInfo[], + sourceFile: ts.SourceFile | null): void { + if (item.name && ts.isIdentifier(item.name)) { + const componentName: string = item.name.getText(); + componentCollection.customComponents.add(componentName); + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item); + if (decorators && decorators.length) { + checkDecorators(decorators, result, item.name, log, sourceFile, item); + } else { + const message: string = `Decorator '@Component', '@ComponentV2', or '@CustomDialog' is missing for struct '${componentName}'.`; + addLog(LogType.ERROR, message, item.getStart(), log, sourceFile, { code: '10905233' }); + } + } else { + const message: string = `A struct must have a name.`; + addLog(LogType.ERROR, message, item.getStart(), log, sourceFile, { code: '10905232' }); + } +} + +function validateEntryAndPreviewCount(result: DecoratorResult, fileQuery: string, + fileName: string, isPreview: boolean, checkEntry: boolean, log: LogInfo[]): void { + if (result.previewCount > 10 && (fileQuery === '?entry' || process.env.watchMode === 'true')) { + log.push({ + type: LogType.ERROR, + message: `A page can contain at most 10 '@Preview' decorators.`, + fileName: fileName, + code: '10905404' + }); + } + if (result.entryCount > 1 && fileQuery === '?entry') { + log.push({ + type: LogType.ERROR, + message: `A page can't contain more than one '@Entry' decorator`, + fileName: fileName, + code: '10905231' + }); + } + if (isPreview && !checkEntry && result.previewCount < 1 && result.entryCount !== 1 && + fileQuery === '?entry') { + log.push({ + type: LogType.ERROR, + message: `A page which is being previewed must have one and only one '@Entry' ` + + `decorator, or at least one '@Preview' decorator.`, + fileName: fileName, + code: '10905403' + }); + } else if ((!isPreview || isPreview && checkEntry) && result.entryCount !== 1 && fileQuery === '?entry' && + !abilityPagesFullPath.has(path.resolve(fileName).toLowerCase())) { + log.push({ + type: LogType.ERROR, + message: `A page configured in '${projectConfig.pagesJsonFileName} or build-profile.json5' must have one and only one '@Entry' decorator.`, + fileName: fileName, + code: '10905402', + solutions: [`Please make sure that the splash page has one and only one '@Entry' decorator.`] + }); + } +} + +export function isObservedClass(node: ts.Node): boolean { + if (ts.isClassDeclaration(node) && hasDecorator(node, COMPONENT_OBSERVED_DECORATOR)) { + return true; + } + return false; +} + +export function isCustomDialogClass(node: ts.Node): boolean { + if (ts.isStructDeclaration(node) && hasDecorator(node, COMPONENT_DECORATOR_CUSTOM_DIALOG)) { + return true; + } + return false; +} + +interface DecoratorResult { + entryCount: number; + previewCount: number; +} + +function checkDecorators(decorators: readonly ts.Decorator[], result: DecoratorResult, + component: ts.Identifier, log: LogInfo[], sourceFile: ts.SourceFile, node: ts.StructDeclaration): void { + const componentName: string = component.getText(); + const structInfo: StructInfo = processStructComponentV2.getOrCreateStructInfo(componentName); + let hasInnerComponentDecorator: boolean = false; + decorators.forEach((element) => { + let name: string = element.getText().replace(/\([^\(\)]*\)/, '').trim(); + if (element.expression && element.expression.expression && ts.isIdentifier(element.expression.expression)) { + name = '@' + element.expression.expression.getText(); + } + if (INNER_COMPONENT_DECORATORS.has(name)) { + hasInnerComponentDecorator = true; + switch (name) { + case COMPONENT_DECORATOR_ENTRY: + checkEntryComponent(node, log, sourceFile); + result.entryCount++; + componentCollection.entryComponent = componentName; + componentCollection.entryComponentPos = node.getStart(); + collectLocalStorageName(element); + break; + case COMPONENT_DECORATOR_PREVIEW: + result.previewCount++; + componentCollection.previewComponent.push(componentName); + break; + case COMPONENT_DECORATOR_COMPONENT_V2: + structInfo.isComponentV2 = true; + break; + case COMPONENT_DECORATOR_COMPONENT: + structInfo.isComponentV1 = true; + break; + case COMPONENT_DECORATOR_CUSTOM_DIALOG: + componentCollection.customDialogs.add(componentName); + structInfo.isCustomDialog = true; + break; + case COMPONENT_DECORATOR_REUSEABLE: + storedFileInfo.getCurrentArkTsFile().recycleComponents.add(componentName); + structInfo.isReusable = true; + break; + case COMPONENT_DECORATOR_REUSABLE_V2: + storedFileInfo.getCurrentArkTsFile().reuseComponentsV2.add(componentName); + structInfo.isReusableV2 = true; + break; + } + } else { + validateInvalidStructDecorator(element, componentName, log, sourceFile); + } + }); + validateStruct(hasInnerComponentDecorator, componentName, component, log, sourceFile, structInfo); +} + +function validateInvalidStructDecorator(element: ts.Decorator, componentName: string, log: LogInfo[], + sourceFile: ts.SourceFile): void { + const pos: number = element.expression ? element.expression.pos : element.pos; + const message: string = `The struct '${componentName}' use invalid decorator.`; + addLog(LogType.WARN, message, pos, log, sourceFile); +} + +function validateStruct(hasInnerComponentDecorator: boolean, componentName: string, component: ts.Identifier, + log: LogInfo[], sourceFile: ts.SourceFile, structInfo: StructInfo): void { + if (!hasInnerComponentDecorator) { + const message: string = `Decorator '@Component', '@ComponentV2', or '@CustomDialog' is missing for struct '${componentName}'.`; + addLog(LogType.ERROR, message, component.pos, log, sourceFile, { code: '10905230' }); + } else if (structInfo.isComponentV2 && (structInfo.isComponentV1 || structInfo.isReusable || structInfo.isCustomDialog) ) { + const message: string = `The struct '${componentName}' can not be decorated with '@ComponentV2' ` + + `and '@Component', '@Reusable', '@CustomDialog' at the same time.`; + addLog(LogType.ERROR, message, component.pos, log, sourceFile, { code: '10905229' }); + } else if (structInfo.isReusableV2 && !structInfo.isComponentV2) { + const message: string = `@ReusableV2 is only applicable to custom components decorated by @ComponentV2.`; + addLog(LogType.ERROR, message, component.pos, log, sourceFile, { code: '10905242' }); + } + if (structInfo.isReusable && structInfo.isReusableV2) { + const message: string = `The @Reusable and @ReusableV2 decoraotrs cannot be applied simultaneously.`; + addLog(LogType.ERROR, message, component.pos, log, sourceFile, { code: '10905241' }); + } + if (BUILDIN_STYLE_NAMES.has(componentName) && !COMPONENT_SYSTEMAPI_NAMES.has(componentName)) { + const message: string = `The struct '${componentName}' cannot have the same name ` + + `as the built-in attribute '${componentName}'.`; + addLog(LogType.ERROR, message, component.pos, log, sourceFile, { code: '10905228' }); + } + if (INNER_COMPONENT_NAMES.has(componentName)) { + const message: string = `The struct '${componentName}' cannot have the same name ` + + `as the built-in component '${componentName}'.`; + addLog(LogType.ERROR, message, component.pos, log, sourceFile, { code: '10905227' }); + } +} + +function checkConcurrentDecorator(node: ts.FunctionDeclaration | ts.MethodDeclaration, log: LogInfo[], + sourceFile: ts.SourceFile): void { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (projectConfig.compileMode === JSBUNDLE) { + const message: string = `@Concurrent can only be used in ESMODULE compile mode.`; + addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile); + } + if (ts.isMethodDeclaration(node)) { + const message: string = `@Concurrent can not be used on method. please use it on function declaration.`; + addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile, { code: '10905123' }); + } + if (node.asteriskToken) { + let hasAsync: boolean = false; + const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + const checkAsyncModifier = (modifier: ts.Modifier) : boolean => modifier.kind === ts.SyntaxKind.AsyncKeyword; + modifiers && (hasAsync = modifiers.some(checkAsyncModifier)); + const funcKind: string = hasAsync ? 'Async generator' : 'Generator'; + const message: string = `@Concurrent can not be used on ${funcKind} function declaration.`; + addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile, { code: '10905122' }); + } +} + +function collectLocalStorageName(node: ts.Decorator): void { + if (node && node.expression && ts.isCallExpression(node.expression)) { + if (node.expression.arguments && node.expression.arguments.length) { + node.expression.arguments.forEach((item: ts.Node, index: number) => { + if (ts.isIdentifier(item) && index === 0) { + componentCollection.localStorageName = item.getText(); + componentCollection.localStorageNode = item; + } else if (ts.isObjectLiteralExpression(item) && index === 0) { + componentCollection.localStorageName = null; + componentCollection.localStorageNode = item; + } else { + componentCollection.localSharedStorage = item; + } + }); + } + } else { + componentCollection.localStorageName = null; + componentCollection.localStorageNode = null; + } +} + +function checkUISyntax(filePath: string, allComponentNames: Set, content: string, + log: LogInfo[], sourceFile: ts.SourceFile | null, fileQuery: string): void { + if (!sourceFile) { + sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); + } + visitAllNode(sourceFile, sourceFile, allComponentNames, log, false, false, false, false, fileQuery, false, false); +} + +function visitAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, allComponentNames: Set, + log: LogInfo[], structContext: boolean, classContext: boolean, isObservedClass: boolean, + isComponentV2: boolean, fileQuery: string, isObservedV1Class: boolean, isSendableClass: boolean): void { + if (ts.isStructDeclaration(node) && node.name && ts.isIdentifier(node.name)) { + structContext = true; + const structName: string = node.name.escapedText.toString(); + const structInfo: StructInfo = processStructComponentV2.getOrCreateStructInfo(structName); + if (structInfo.isComponentV2) { + processStructComponentV2.parseComponentProperty(node, structInfo, log, sourceFileNode); + isComponentV2 = true; + } else { + collectComponentProps(node, structInfo); + } + } + if (ts.isClassDeclaration(node) && node.name && ts.isIdentifier(node.name)) { + classContext = true; + [isObservedV1Class, isObservedClass, isSendableClass] = parseClassDecorator(node, sourceFileNode, log); + } + if (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) { + methodDecoratorCollect(node); + if (hasDecorator(node, COMPONENT_CONCURRENT_DECORATOR)) { + // ark compiler's feature + checkConcurrentDecorator(node, log, sourceFileNode); + } + validateFunction(node, sourceFileNode, log); + } + checkDecoratorCount(node, sourceFileNode, log); + checkDecorator(sourceFileNode, node, log, structContext, classContext, isObservedClass, isComponentV2, + isObservedV1Class, isSendableClass); + ts.forEachChild(node, (child: ts.Node) => visitAllNode(child, sourceFileNode, allComponentNames, log, + structContext, classContext, isObservedClass, isComponentV2, fileQuery, isObservedV1Class, isSendableClass)); + structContext = false; + classContext = false; + isObservedClass = false; + isObservedV1Class = false; + isSendableClass = false; +} + +const v1ComponentDecorators: string[] = [ + 'State', 'Prop', 'Link', 'Provide', 'Consume', + 'StorageLink', 'StorageProp', 'LocalStorageLink', 'LocalStorageProp' +]; +const v2ComponentDecorators: string[] = [ + 'Local', 'Param', 'Event', 'Provider', 'Consumer' +]; +function validatePropertyInStruct(structContext: boolean, decoratorNode: ts.Identifier, + decoratorName: string, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + if (structContext) { + const isV1Decorator: boolean = v1ComponentDecorators.includes(decoratorName); + const isV2Decorator: boolean = v2ComponentDecorators.includes(decoratorName); + if (!isV1Decorator && !isV2Decorator) { + return; + } + const classResult: ClassDecoratorResult = new ClassDecoratorResult(); + const propertyNode: ts.PropertyDeclaration = getPropertyNodeByDecorator(decoratorNode); + if (propertyNode && propertyNode.type && globalProgram.checker) { + validatePropertyType(propertyNode.type, classResult); + } + let message: string; + if (isV1Decorator && classResult.hasObservedV2) { + message = `The type of the @${decoratorName} property can not be a class decorated with @ObservedV2.`; + addLog(LogType.ERROR, message, decoratorNode.getStart(), log, sourceFileNode, { code: '10905348' }); + return; + } + } +} + +function getPropertyNodeByDecorator(decoratorNode: ts.Identifier): ts.PropertyDeclaration { + if (ts.isDecorator(decoratorNode.parent) && ts.isPropertyDeclaration(decoratorNode.parent.parent)) { + return decoratorNode.parent.parent; + } + if (ts.isCallExpression(decoratorNode.parent) && ts.isDecorator(decoratorNode.parent.parent) && + ts.isPropertyDeclaration(decoratorNode.parent.parent.parent)) { + return decoratorNode.parent.parent.parent; + } + return undefined; +} + +function validatePropertyType(node: ts.TypeNode, classResult: ClassDecoratorResult): void { + if (ts.isUnionTypeNode(node) && node.types && node.types.length) { + node.types.forEach((item: ts.TypeNode) => { + validatePropertyType(item, classResult); + }); + } + if (ts.isTypeReferenceNode(node) && node.typeName) { + const typeNode: ts.Type = globalProgram.checker.getTypeAtLocation(node.typeName); + parsePropertyType(typeNode, classResult); + } +} + +function parsePropertyType(type: ts.Type, classResult: ClassDecoratorResult): void { + // @ts-ignore + if (type && type.types && type.types.length) { + // @ts-ignore + type.types.forEach((item: ts.Type) => { + parsePropertyType(item, classResult); + }); + } + if (type && type.symbol && type.symbol.valueDeclaration && + ts.isClassDeclaration(type.symbol.valueDeclaration)) { + const result: ClassDecoratorResult = getClassDecoratorResult(type.symbol.valueDeclaration); + if (result.hasObserved) { + classResult.hasObserved = result.hasObserved; + } + if (result.hasObservedV2) { + classResult.hasObservedV2 = result.hasObservedV2; + } + } +} + +function checkDecoratorCount(node: ts.Node, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + if (ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isMethodDeclaration(node)) { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + let innerDecoratorCount: number = 0; + const exludeDecorators: string[] = ['@Require', '@Once']; + const v1MethodDecorators: string[] = ['@Builder', '@Styles']; + const v1DecoratorMap: Map = new Map(); + const v2DecoratorMap: Map = new Map(); + let checkDecoratorCount: number = 0; + decorators.forEach((item: ts.Decorator) => { + const decoratorName: string = item.getText().replace(/\([^\(\)]*\)/, ''); + if (!exludeDecorators.includes(decoratorName) && (constantDefine.DECORATOR_V2.includes(decoratorName) || + decoratorName === '@BuilderParam')) { + const count: number = v2DecoratorMap.get(decoratorName) || 0; + v2DecoratorMap.set(decoratorName, count + 1); + return; + } + if (v1MethodDecorators.includes(decoratorName)) { + const count: number = v1DecoratorMap.get(decoratorName) || 0; + v1DecoratorMap.set(decoratorName, count + 1); + return; + } + if (decoratorName === COMPONENT_LOCAL_BUILDER_DECORATOR && decorators.length > 1) { + checkDecoratorCount = checkDecoratorCount + 1; + return; + } + }); + const v2DecoratorMapKeys: string[] = Array.from(v2DecoratorMap.keys()); + const v2DecoratorMapValues: number[] = Array.from(v2DecoratorMap.values()); + const v1DecoratorMapKeys: string[] = Array.from(v1DecoratorMap.keys()); + const v1DecoratorMapValues: number[] = Array.from(v1DecoratorMap.values()); + innerDecoratorCount = v2DecoratorMapKeys.length + v1DecoratorMapKeys.length; + logMessageCollection.checkLocalBuilderDecoratorCount(node, sourceFileNode, checkDecoratorCount, log); + if (innerDecoratorCount > 1) { + const message: string = 'The member property or method can not be decorated by multiple built-in decorators.'; + addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode, { code: '10905121' }); + } + const v2Duplicate: boolean = v2DecoratorMapValues.length && + v2DecoratorMapValues.some((count: number) => count > 1); + const v1Duplicate: boolean = v1DecoratorMapValues.length && + v1DecoratorMapValues.some((count: number) => count > 1); + const duplicateMessage: string = 'Duplicate decorators for method are not allowed.'; + if (v1Duplicate) { + addLog(LogType.WARN, duplicateMessage, node.getStart(), log, sourceFileNode); + } else if (v2Duplicate) { + addLog(LogType.ERROR, duplicateMessage, node.getStart(), log, sourceFileNode, { code: '10905119' }); + } + } +} + +export function methodDecoratorCollect(node: ts.MethodDeclaration | ts.FunctionDeclaration): void { + const extendResult: ExtendResult = { decoratorName: '', componentName: '' }; + const builderCondition: builderConditionType = { + isBuilder: false, + isLocalBuilder: false + }; + if (isBuilderOrLocalBuilder(node, builderCondition)) { + if (builderCondition.isBuilder) { + CUSTOM_BUILDER_METHOD.add(node.name.getText()); + if (ts.isFunctionDeclaration(node)) { + GLOBAL_CUSTOM_BUILDER_METHOD.add(node.name.getText()); + } else { + INNER_CUSTOM_BUILDER_METHOD.add(node.name.getText()); + } + } else if (builderCondition.isLocalBuilder) { + INNER_CUSTOM_LOCALBUILDER_METHOD.add(node.name.getText()); + } + } else if (ts.isFunctionDeclaration(node) && isExtendFunction(node, extendResult)) { + if (extendResult.decoratorName === CHECK_COMPONENT_EXTEND_DECORATOR) { + collectExtend(EXTEND_ATTRIBUTE, extendResult.componentName, node.name.getText()); + } + if (extendResult.decoratorName === CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR) { + collectExtend(storedFileInfo.getCurrentArkTsFile().animatableExtendAttribute, + extendResult.componentName, node.name.getText()); + } + } else if (hasDecorator(node, COMPONENT_STYLES_DECORATOR)) { + collectStyles(node); + } +} + +function collectStyles(node: ts.FunctionLikeDeclarationBase): void { + if (ts.isBlock(node.body) && node.body.statements) { + if (ts.isFunctionDeclaration(node)) { + GLOBAL_STYLE_FUNCTION.set(node.name.getText(), node.body); + } else { + INNER_STYLE_FUNCTION.set(node.name.getText(), node.body); + } + STYLES_ATTRIBUTE.add(node.name.getText()); + BUILDIN_STYLE_NAMES.add(node.name.getText()); + } +} + +function validateFunction(node: ts.MethodDeclaration | ts.FunctionDeclaration, + sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + if (ts.isFunctionDeclaration(node)) { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + const decoratorMap: Map = new Map(); + decorators.forEach((item: ts.Decorator) => { + const decoratorName: string = item.getText().replace(/\([^\(\)]*\)/, '') + .replace(/^@/, '').trim(); + const count: number = decoratorMap.get(decoratorName) || 0; + decoratorMap.set(decoratorName, count + 1); + }); + const decoratorValues: number[] = Array.from(decoratorMap.values()); + const hasDuplicate: boolean = decoratorValues.length && + decoratorValues.some((count: number) => count > 1); + if (hasDuplicate) { + const message: string = 'Duplicate decorators for function are not allowed.'; + addLog(LogType.WARN, message, node.getStart(), log, sourceFileNode); + } + const decoratorKeys: string[] = Array.from(decoratorMap.keys()); + if (decoratorKeys.length > 1 || decoratorKeys.includes('LocalBuilder')) { + const message: string = 'A function can only be decorated by one of the ' + + `'AnimatableExtend, Builder, Extend, Styles, Concurrent and Sendable'.`; + addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode, { code: '10905117' }); + } + } +} + +function checkDecorator(sourceFileNode: ts.SourceFile, node: ts.Node, + log: LogInfo[], structContext: boolean, classContext: boolean, isObservedClass: boolean, + isComponentV2: boolean, isObservedV1Class: boolean, isSendableClass: boolean): void { + if (ts.isIdentifier(node) && (ts.isDecorator(node.parent) || + (ts.isCallExpression(node.parent) && ts.isDecorator(node.parent.parent)))) { + const decoratorName: string = node.escapedText.toString(); + setLocalBuilderInFile(decoratorName); + validateStructDecorator(sourceFileNode, node, log, structContext, decoratorName, isComponentV2); + validateMethodDecorator(sourceFileNode, node, log, structContext, decoratorName); + validateClassDecorator(sourceFileNode, node, log, classContext, decoratorName, isObservedClass, + isObservedV1Class, isSendableClass); + validatePropertyInStruct(structContext, node, decoratorName, sourceFileNode, log); + return; + } + if (ts.isDecorator(node)) { + validateSingleDecorator(node, sourceFileNode, log, isComponentV2); + } +} + +function setLocalBuilderInFile(decoratorName: string): void { + if (decoratorName === 'LocalBuilder') { + storedFileInfo.hasLocalBuilderInFile = true; + } +} + +function validateSingleDecorator(node: ts.Decorator, sourceFileNode: ts.SourceFile, + log: LogInfo[], isComponentV2: boolean): void { + const decoratorName: string = node.getText().replace(/\([^\(\)]*\)/, ''); + if (decoratorName === constantDefine.COMPUTED_DECORATOR && node.parent && !ts.isGetAccessor(node.parent)) { + const message: string = `@Computed can only decorate 'GetAccessor'.`; + addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode, { code: '10905116' }); + return; + } + const partialDecoratorCollection: string[] = [constantDefine.MONITOR_DECORATOR, COMPONENT_LOCAL_BUILDER_DECORATOR]; + if (partialDecoratorCollection.includes(decoratorName) && node.parent && + !ts.isMethodDeclaration(node.parent)) { + const message: string = `'${decoratorName}' can only decorate method.`; + addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode, { code: '10905115' }); + return; + } + if (isMemberForComponentV2(decoratorName, isComponentV2) && node.parent && + !ts.isPropertyDeclaration(node.parent)) { + const message: string = `'${decoratorName}' can only decorate member property.`; + addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode, { code: '10905346' }); + return; + } +} + +function isMemberForComponentV2(decoratorName: string, isComponentV2: boolean): boolean { + return constantDefine.COMPONENT_MEMBER_DECORATOR_V2.includes(decoratorName) || + (isComponentV2 && decoratorName === '@BuilderParam'); +} + +const classDecorators: string[] = [CLASS_TRACK_DECORATOR, CLASS_MIN_TRACK_DECORATOR, MIN_OBSERVED]; +const classMemberDecorators: string[] = [CLASS_TRACK_DECORATOR, CLASS_MIN_TRACK_DECORATOR, TYPE, + constantDefine.MONITOR, constantDefine.COMPUTED]; + +function validTypeCallback(node: ts.Identifier): boolean { + let isSdkPath: boolean = true; + if (globalProgram.checker && process.env.compileTool === 'rollup') { + const symbolObj: ts.Symbol = getSymbolIfAliased(node); + const fileName: string = symbolObj?.valueDeclaration?.getSourceFile()?.fileName; + isSdkPath = /@ohos.arkui.*/.test(fileName); + } + return isSdkPath; +} + +function isTypeFromSdkCallback(classContext: boolean, decoratorName: string, isTypeFromSdk: boolean): boolean { + if (!classContext && decoratorName === TYPE && isTypeFromSdk) { + return true; + } + return false; +} + +function validateClassDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], + classContext: boolean, decoratorName: string, isObservedClass: boolean, isObservedV1Class: boolean, + isSendableClass: boolean): void { + const isTypeFromSdk: boolean = validTypeCallback(node); + if (!classContext && (classDecorators.includes(decoratorName) || isTypeFromSdkCallback(classContext, decoratorName, isTypeFromSdk))) { + const message: string = `The '@${decoratorName}' decorator can only be used in 'class'.`; + addLog(LogType.ERROR, message, node.pos, log, sourceFileNode, { code: '10905345' }); + } else if (classContext && classMemberDecorators.includes(decoratorName)) { + validateMemberInClass(isObservedClass, decoratorName, node, log, sourceFileNode, isObservedV1Class, isSendableClass, isTypeFromSdk); + } +} + +function validateMemberInClass(isObservedClass: boolean, decoratorName: string, node: ts.Identifier, + log: LogInfo[], sourceFileNode: ts.SourceFile, isObservedV1Class: boolean, isSendableClass: boolean, isTypeFromSdk: boolean): void { + if (decoratorName === CLASS_TRACK_DECORATOR) { + if (isObservedClass) { + const message: string = `The '@${decoratorName}' decorator can not be used in a 'class' decorated with ObservedV2.`; + addLog(LogType.ERROR, message, node.pos, log, sourceFileNode, { code: '10905344' }); + } + return; + } + if (decoratorName === TYPE) { + if (isTypeFromSdk) { + validType(sourceFileNode, node, log, decoratorName, isObservedV1Class, isSendableClass); + } + return; + } + if (!isObservedClass || !isPropertyForTrace(node, decoratorName)) { + const info: string = decoratorName === CLASS_MIN_TRACK_DECORATOR ? 'variables' : 'method'; + const message: string = `The '@${decoratorName}' can decorate only member ${info} within a 'class' decorated with ObservedV2.`; + addLog(LogType.ERROR, message, node.pos, log, sourceFileNode, { code: '10905343' }); + return; + } +} + +function validType(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], decoratorName: string, + isObservedV1Class: boolean, isSendableClass: boolean): void { + if (isSendableClass) { + const message: string = `The '@${decoratorName}' decorator can not be used in a 'class' decorated with Sendable.`; + addLog(LogType.ERROR, message, node.pos, log, sourceFileNode, { code: '10905342' }); + } + if (isObservedV1Class) { + const message: string = `The '@${decoratorName}' decorator can not be used in a 'class' decorated with Observed.`; + addLog(LogType.ERROR, message, node.pos, log, sourceFileNode, { code: '10905341' }); + } + if (ts.isDecorator(node.parent?.parent) && !ts.isPropertyDeclaration(node.parent?.parent?.parent)) { + const message: string = `The '@${decoratorName}' can decorate only member variables in a 'class'.`; + addLog(LogType.ERROR, message, node.pos, log, sourceFileNode, { code: '10905340' }); + } +} + +function isPropertyForTrace(node: ts.Identifier, decoratorName: string): boolean { + if (decoratorName === CLASS_MIN_TRACK_DECORATOR && ts.isDecorator(node.parent) && + !ts.isPropertyDeclaration(node.parent.parent)) { + return false; + } + return true; +} + +class ClassDecoratorResult { + hasObserved: boolean = false; + hasObservedV2: boolean = false; + hasSendable: boolean = false; +} + +function parseClassDecorator(node: ts.ClassDeclaration, sourceFileNode: ts.SourceFile, + log: LogInfo[]): [boolean, boolean, boolean] { + const classResult: ClassDecoratorResult = getClassDecoratorResult(node); + validateMutilObserved(node, classResult, sourceFileNode, log); + if (classResult.hasObserved || classResult.hasObservedV2) { + parseInheritClass(node, classResult, sourceFileNode, log); + } + return [classResult.hasObserved, classResult.hasObservedV2, classResult.hasSendable]; +} + +function getClassDecoratorResult(node: ts.ClassDeclaration): ClassDecoratorResult { + const classResult: ClassDecoratorResult = new ClassDecoratorResult(); + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + decorators.forEach((item: ts.Decorator) => { + if (ts.isIdentifier(item.expression)) { + const decoratorName: string = item.expression.escapedText.toString(); + switch (decoratorName) { + case MIN_OBSERVED: + classResult.hasObservedV2 = true; + break; + case OBSERVED: + classResult.hasObserved = true; + break; + case SENDABLE: + classResult.hasSendable = true; + } + } + }); + return classResult; +} + +function validateMutilObserved(node: ts.ClassDeclaration, classResult: ClassDecoratorResult, + sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + if (classResult.hasObserved && classResult.hasObservedV2) { + const message: string = `A class can not be decorated by '@Observed' and '@ObservedV2' at the same time.`; + addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode, { code: '10905226' }); + } +} + +function parseInheritClass(node: ts.ClassDeclaration, childClassResult: ClassDecoratorResult, + sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + if (globalProgram.checker && process.env.compileTool === 'rollup' && node.heritageClauses) { + for (const heritageClause of node.heritageClauses) { + if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword && heritageClause.types && + heritageClause.types.length) { + getClassNode(heritageClause.types[0].expression, childClassResult, node, sourceFileNode, log); + } + } + } +} + +function getClassNode(parentType: ts.Node, childClassResult: ClassDecoratorResult, + childClass: ts.ClassDeclaration, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + const symbol: ts.Symbol = parentType && getSymbolIfAliased(parentType); + if (symbol && symbol.valueDeclaration) { + if (ts.isClassDeclaration(symbol.valueDeclaration)) { + validateInheritClassDecorator(symbol.valueDeclaration, childClassResult, childClass, sourceFileNode, log); + return; + } + if (ts.isPropertyAssignment(symbol.valueDeclaration)) { + getClassNode(symbol.valueDeclaration.initializer, childClassResult, childClass, sourceFileNode, log); + return; + } + if (ts.isShorthandPropertyAssignment(symbol.valueDeclaration)) { + parseShorthandPropertyForClass(symbol.valueDeclaration, childClassResult, childClass, sourceFileNode, log); + return; + } + } +} + +function parseShorthandPropertyForClass(node: ts.ShorthandPropertyAssignment, childClassResult: ClassDecoratorResult, + childClass: ts.ClassDeclaration, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + const shortSymbol: ts.Symbol = globalProgram.checker.getShorthandAssignmentValueSymbol(node); + if (shortSymbol && shortSymbol.valueDeclaration && ts.isClassDeclaration(shortSymbol.valueDeclaration)) { + validateInheritClassDecorator(shortSymbol.valueDeclaration, childClassResult, childClass, sourceFileNode, log); + } +} + +export function getSymbolIfAliased(node: ts.Node): ts.Symbol { + const symbol: ts.Symbol = globalProgram.checker.getSymbolAtLocation(node); + if (symbol && (symbol.getFlags() & ts.SymbolFlags.Alias) !== 0) { + return globalProgram.checker.getAliasedSymbol(symbol); + } + return symbol; +} + +function validateInheritClassDecorator(parentNode: ts.ClassDeclaration, childClassResult: ClassDecoratorResult, + childClass: ts.ClassDeclaration, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + const parentClassResult: ClassDecoratorResult = getClassDecoratorResult(parentNode); + if (childClassResult.hasObservedV2 && parentClassResult.hasObserved) { + const message: string = `Because the current class is decorated by '@ObservedV2', ` + + `it can not inherit a class decorated by '@Observed'.`; + addLog(LogType.ERROR, message, childClass.getStart(), log, sourceFileNode, { code: '10905225' }); + return; + } + if (childClassResult.hasObserved && parentClassResult.hasObservedV2) { + const message: string = `Because the current class is decorated by '@Observed', ` + + `it can not inherit a class decorated by '@ObservedV2'.`; + addLog(LogType.ERROR, message, childClass.getStart(), log, sourceFileNode, { code: '10905224' }); + return; + } +} + +function validateStructDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], + structContext: boolean, decoratorName: string, isComponentV2: boolean): void { + const name: string = `@${decoratorName}`; + if (structContext) { + if (isComponentV2) { + if (constantDefine.COMPONENT_MEMBER_DECORATOR_V1.includes(name)) { + const message: string = `The '@${decoratorName}' decorator can only be used in a 'struct' decorated with '@Component'.`; + addLog(LogType.ERROR, message, node.pos, log, sourceFileNode, { code: '10905339' }); + } + } else if (constantDefine.DECORATOR_V2.includes(name)) { + const message: string = `The '@${decoratorName}' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`; + addLog(LogType.ERROR, message, node.pos, log, sourceFileNode, { code: '10905338' }); + } + } else if (STRUCT_DECORATORS.has(name) || constantDefine.COMPONENT_MEMBER_DECORATOR_V2.includes(name)) { + const message: string = `The '@${decoratorName}' decorator can only be used with 'struct'.`; + addLog(LogType.ERROR, message, node.pos, log, sourceFileNode, { code: '10905337' }); + } +} + +function validateMethodDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], + structContext: boolean, decoratorName: string): void { + if (ts.isMethodDeclaration(node.parent.parent) || + (ts.isDecorator(node.parent.parent) && ts.isMethodDeclaration(node.parent.parent.parent))) { + if (!structContext && STRUCT_CONTEXT_METHOD_DECORATORS.has(`@${decoratorName}`)) { + const message: string = `The '@${decoratorName}' decorator can only be used in 'struct'.`; + addLog(LogType.ERROR, message, node.pos, log, sourceFileNode, { code: '10905114' }); + } + if (CHECK_EXTEND_DECORATORS.includes(decoratorName)) { + const message: string = `The '@${decoratorName}' decorator can not be a member property method of a 'class' or 'struct'.`; + addLog(LogType.ERROR, message, node.pos, log, sourceFileNode, { code: '10905113' }); + } + } +} + +export function isSendableClassDeclaration(classDeclarationNode: ts.ClassDeclaration): boolean { + return hasDecorator(classDeclarationNode, COMPONENT_SENDABLE_DECORATOR) || + (classDeclarationNode.members && classDeclarationNode.members.some((member: ts.Node) => { + // Check if the constructor has "use sendable" as the first statement + // (Sendable classes already transformed by process_ui_syntax.ts) + if (ts.isConstructorDeclaration(member)) { + if (!(member as ts.ConstructorDeclaration).body || + !(member as ts.ConstructorDeclaration).body.statements) { + return false; + } + const constructorStatements: ts.Statement[] = (member as ts.ConstructorDeclaration).body.statements; + if (constructorStatements && constructorStatements[0] && + ts.isExpressionStatement(constructorStatements[0])) { + const expression: ts.Node = (constructorStatements[0] as ts.ExpressionStatement).expression; + return expression && ts.isStringLiteral(expression) && + (expression as ts.StringLiteral).text === 'use sendable'; + } + } + return false; + })); +} + +export function checkAllNode( + node: ts.EtsComponentExpression, + allComponentNames: Set, + sourceFileNode: ts.SourceFile, + log: LogInfo[] +): void { + if (ts.isIdentifier(node.expression)) { + checkNoChildComponent(node, sourceFileNode, log); + checkOneChildComponent(node, allComponentNames, sourceFileNode, log); + checkSpecificChildComponent(node, allComponentNames, sourceFileNode, log); + } +} + +interface ParamType { + name: string, + value: string, +} + +function checkNoChildComponent(node: ts.EtsComponentExpression, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + const isCheckType: ParamType = { name: null, value: null}; + if (hasChild(node, isCheckType)) { + const componentName: string = (node.expression as ts.Identifier).escapedText.toString(); + const pos: number = node.expression.getStart(); + let message: string; + let code: string | undefined; + if (isCheckType.name === null) { + message = `The component '${componentName}' can't have any child.`; + code = '10905222'; + } else { + message = `When the component '${componentName}' set '${isCheckType.name}' is '${isCheckType.value}'` + + `, can't have any child.`; + code = '10905223'; + } + addLog(LogType.ERROR, message, pos, log, sourceFileNode, { code }); + } +} + +function hasChild(node: ts.EtsComponentExpression, isCheckType: ParamType): boolean { + const nodeName: ts.Identifier = node.expression as ts.Identifier; + if ((AUTOMIC_COMPONENT.has(nodeName.escapedText.toString()) || judgeComponentType(nodeName, node, isCheckType)) && + getNextNode(node)) { + return true; + } + return false; +} + +function judgeComponentType(nodeName: ts.Identifier, etsComponentExpression: ts.EtsComponentExpression, + isCheckType: ParamType): boolean { + return COMPONENT_TOGGLE === nodeName.escapedText.toString() && + etsComponentExpression.arguments && etsComponentExpression.arguments[0] && + ts.isObjectLiteralExpression(etsComponentExpression.arguments[0]) && + etsComponentExpression.arguments[0].getText() && + judgeToggleComponentParamType(etsComponentExpression.arguments[0].getText(), isCheckType); +} + +function judgeToggleComponentParamType(param: string, isCheckType: ParamType): boolean { + if (param.indexOf(RESOURCE_NAME_TYPE) > -1) { + isCheckType.name = RESOURCE_NAME_TYPE; + const match: string[] = param.match(/\b(Checkbox|Switch|Button)\b/); + if (match && match.length) { + isCheckType.value = match[0]; + if (isCheckType.value === COMPONENT_BUTTON) { + return false; + } + return true; + } + } + return false; +} + +function getNextNode(node: ts.EtsComponentExpression): ts.Block { + if (node.body && ts.isBlock(node.body)) { + const statementsArray: ts.Block = node.body; + return statementsArray; + } + return undefined; +} + +function checkOneChildComponent(node: ts.EtsComponentExpression, allComponentNames: Set, + sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + const isCheckType: ParamType = { name: null, value: null}; + if (hasNonSingleChild(node, allComponentNames, isCheckType)) { + const componentName: string = (node.expression as ts.Identifier).escapedText.toString(); + const pos: number = node.expression.getStart(); + let message: string; + let code: string | undefined; + if (isCheckType.name === null) { + message = `The component '${componentName}' can only have a single child component.`; + code = '10905220'; + } else { + message = `When the component '${componentName}' set '${isCheckType.name}' is ` + + `'${isCheckType.value}', can only have a single child component.`; + code = '10905221'; + } + addLog(LogType.ERROR, message, pos, log, sourceFileNode, { code }); + } +} + +function hasNonSingleChild(node: ts.EtsComponentExpression, allComponentNames: Set, + isCheckType: ParamType): boolean { + const nodeName: ts.Identifier = node.expression as ts.Identifier; + const blockNode: ts.Block = getNextNode(node); + if (SINGLE_CHILD_COMPONENT.has(nodeName.escapedText.toString()) || !judgeComponentType(nodeName, node, isCheckType) && + isCheckType.value === COMPONENT_BUTTON) { + if (!blockNode) { + return false; + } + if (blockNode && blockNode.statements) { + const length: number = blockNode.statements.length; + if (!length) { + return false; + } + if (length > 3) { + return true; + } + const childCount: number = getBlockChildrenCount(blockNode, allComponentNames); + if (childCount > 1) { + return true; + } + } + } + return false; +} + +function getBlockChildrenCount(blockNode: ts.Block, allComponentNames: Set): number { + let maxCount: number = 0; + const length: number = blockNode.statements.length; + for (let i = 0; i < length; ++i) { + const item: ts.Node = blockNode.statements[i]; + if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) && + isForEachComponent(item.expression)) { + maxCount += 2; + } + if (ts.isIfStatement(item)) { + maxCount += getIfChildrenCount(item, allComponentNames); + } + if (ts.isExpressionStatement(item) && ts.isEtsComponentExpression(item.expression)) { + maxCount += 1; + } + if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression)) { + let newNode = item.expression; + while (newNode.expression) { + if (ts.isEtsComponentExpression(newNode) || ts.isCallExpression(newNode) && + isComponent(newNode, allComponentNames)) { + maxCount += 1; + } + newNode = newNode.expression; + } + } + if (maxCount > 1) { + break; + } + } + return maxCount; +} + +function isComponent(node: ts.EtsComponentExpression | ts.CallExpression, allComponentNames: Set): boolean { + if (ts.isIdentifier(node.expression) && + allComponentNames.has(node.expression.escapedText.toString())) { + return true; + } + return false; +} + +function isForEachComponent(node: ts.EtsComponentExpression | ts.CallExpression): boolean { + if (ts.isIdentifier(node.expression)) { + const componentName: string = node.expression.escapedText.toString(); + return componentName === COMPONENT_FOREACH || componentName === COMPONENT_LAZYFOREACH; + } + return false; +} + +function getIfChildrenCount(ifNode: ts.IfStatement, allComponentNames: Set): number { + const maxCount: number = + Math.max(getStatementCount(ifNode.thenStatement, allComponentNames), + getStatementCount(ifNode.elseStatement, allComponentNames)); + return maxCount; +} + +function getStatementCount(node: ts.Node, allComponentNames: Set): number { + let maxCount: number = 0; + if (!node) { + return maxCount; + } else if (ts.isBlock(node)) { + maxCount = getBlockChildrenCount(node, allComponentNames); + } else if (ts.isIfStatement(node)) { + maxCount = getIfChildrenCount(node, allComponentNames); + } else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && + isForEachComponent(node.expression)) { + maxCount = 2; + } else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && + !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames)) { + maxCount = 1; + } + return maxCount; +} + +function checkSpecificChildComponent(node: ts.EtsComponentExpression, allComponentNames: Set, + sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + if (hasNonspecificChild(node, allComponentNames)) { + const componentName: string = (node.expression as ts.Identifier).escapedText.toString(); + const pos: number = node.expression.getStart(); + const specificChildArray: string = + Array.from(SPECIFIC_CHILD_COMPONENT.get(componentName)).join(' and '); + const message: string = + `The component '${componentName}' can only have the child component ${specificChildArray}.`; + addLog(LogType.ERROR, message, pos, log, sourceFileNode, { code: '10905219' }); + } +} + +function hasNonspecificChild(node: ts.EtsComponentExpression, + allComponentNames: Set): boolean { + const nodeName: ts.Identifier = node.expression as ts.Identifier; + const nodeNameString: string = nodeName.escapedText.toString(); + const blockNode: ts.Block = getNextNode(node); + let isNonspecific: boolean = false; + if (SPECIFIC_CHILD_COMPONENT.has(nodeNameString) && blockNode) { + const specificChildSet: Set = SPECIFIC_CHILD_COMPONENT.get(nodeNameString); + isNonspecific = isNonspecificChildBlock(blockNode, specificChildSet, allComponentNames); + if (isNonspecific) { + return isNonspecific; + } + } + return isNonspecific; +} + +function isNonspecificChildBlock(blockNode: ts.Block, specificChildSet: Set, + allComponentNames: Set): boolean { + if (blockNode.statements) { + const length: number = blockNode.statements.length; + for (let i = 0; i < length; ++i) { + const item: ts.Node = blockNode.statements[i]; + if (ts.isIfStatement(item) && isNonspecificChildIf(item, specificChildSet, allComponentNames)) { + return true; + } + if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) && + isForEachComponent(item.expression) && + isNonspecificChildForEach(item.expression, specificChildSet, allComponentNames)) { + return true; + } + if (ts.isBlock(item) && isNonspecificChildBlock(item, specificChildSet, allComponentNames)) { + return true; + } + if (ts.isExpressionStatement(item)) { + let newNode: any = item.expression; + while (newNode.expression) { + if (ts.isEtsComponentExpression(newNode) && ts.isIdentifier(newNode.expression) && + !isForEachComponent(newNode) && isComponent(newNode, allComponentNames)) { + const isNonspecific: boolean = + isNonspecificChildNonForEach(newNode, specificChildSet); + if (isNonspecific) { + return isNonspecific; + } + if (i + 1 < length && ts.isBlock(blockNode.statements[i + 1])) { + ++i; + } + } + newNode = newNode.expression; + } + } + } + } + return false; +} + +function isNonspecificChildIf(node: ts.IfStatement, specificChildSet: Set, + allComponentNames: Set): boolean { + return isNonspecificChildIfStatement(node.thenStatement, specificChildSet, allComponentNames) || + isNonspecificChildIfStatement(node.elseStatement, specificChildSet, allComponentNames); +} + +function isNonspecificChildForEach(node: ts.EtsComponentExpression, specificChildSet: Set, + allComponentNames: Set): boolean { + if (ts.isCallExpression(node) && node.arguments && + node.arguments.length > 1 && ts.isArrowFunction(node.arguments[1])) { + const arrowFunction: ts.ArrowFunction = node.arguments[1] as ts.ArrowFunction; + const body: ts.Block | ts.EtsComponentExpression | ts.IfStatement = + arrowFunction.body as ts.Block | ts.EtsComponentExpression | ts.IfStatement; + if (!body) { + return false; + } + if (ts.isBlock(body) && isNonspecificChildBlock(body, specificChildSet, allComponentNames)) { + return true; + } + if (ts.isIfStatement(body) && isNonspecificChildIf(body, specificChildSet, allComponentNames)) { + return true; + } + if (ts.isCallExpression(body) && isForEachComponent(body) && + isNonspecificChildForEach(body, specificChildSet, allComponentNames)) { + return true; + } + if (ts.isEtsComponentExpression(body) && !isForEachComponent(body) && + isComponent(body, allComponentNames) && + isNonspecificChildNonForEach(body, specificChildSet)) { + return true; + } + } + return false; +} + +function isNonspecificChildNonForEach(node: ts.EtsComponentExpression, + specificChildSet: Set): boolean { + if (ts.isIdentifier(node.expression) && + !specificChildSet.has(node.expression.escapedText.toString())) { + return true; + } + return false; +} + +function isNonspecificChildIfStatement(node: ts.Node, specificChildSet: Set, + allComponentNames: Set): boolean { + if (!node) { + return false; + } + if (ts.isBlock(node) && isNonspecificChildBlock(node, specificChildSet, allComponentNames)) { + return true; + } + if (ts.isIfStatement(node) && isNonspecificChildIf(node, specificChildSet, allComponentNames)) { + return true; + } + if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && + isForEachComponent(node.expression) && + isNonspecificChildForEach(node.expression, specificChildSet, allComponentNames)) { + return true; + } + if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && + !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames) && + isNonspecificChildNonForEach(node.expression, specificChildSet)) { + return true; + } + return false; +} + +function collectComponentProps(node: ts.StructDeclaration, structInfo: StructInfo): void { + const componentName: string = node.name.getText(); + const componentSet: IComponentSet = getComponentSet(node, true); + propertyCollection.set(componentName, componentSet.properties); + stateCollection.set(componentName, componentSet.states); + linkCollection.set(componentName, componentSet.links); + storedFileInfo.overallLinkCollection.set(componentName, componentSet.links); + propCollection.set(componentName, componentSet.props); + regularCollection.set(componentName, componentSet.regulars); + storagePropCollection.set(componentName, componentSet.storageProps); + storageLinkCollection.set(componentName, componentSet.storageLinks); + provideCollection.set(componentName, componentSet.provides); + consumeCollection.set(componentName, componentSet.consumes); + objectLinkCollection.set(componentName, componentSet.objectLinks); + storedFileInfo.overallObjectLinkCollection.set(componentName, componentSet.objectLinks); + localStorageLinkCollection.set(componentName, componentSet.localStorageLink); + localStoragePropCollection.set(componentName, componentSet.localStorageProp); + builderParamObjectCollection.set(componentName, componentSet.builderParams); + builderParamInitialization.set(componentName, componentSet.builderParamData); + propInitialization.set(componentName, componentSet.propData); + regularInitialization.set(componentName, componentSet.regularInit); + stateInitialization.set(componentName, componentSet.stateInit); + provideInitialization.set(componentName, componentSet.provideInit); + privateCollection.set(componentName, componentSet.privateCollection); + regularStaticCollection.set(componentName, componentSet.regularStaticCollection); + structInfo.updatePropsDecoratorsV1.push( + ...componentSet.states, ...componentSet.props, + ...componentSet.provides, ...componentSet.objectLinks + ); + structInfo.linkDecoratorsV1.push(...componentSet.links); +} + +export function getComponentSet(node: ts.StructDeclaration, uiCheck: boolean = false): IComponentSet { + const componentSet: IComponentSet = new IComponentSet(); + traversalComponentProps(node, componentSet, uiCheck); + return componentSet; +} + +class RecordRequire { + hasRequire: boolean = false; + hasProp: boolean = false; + hasBuilderParam: boolean = false; + hasRegular: boolean = false; + hasState: boolean = false; + hasProvide: boolean = false; +} + +function traversalComponentProps(node: ts.StructDeclaration, componentSet: IComponentSet, + uiCheck: boolean = false): void { + let isStatic: boolean = true; + if (node.members) { + const currentMethodCollection: Set = new Set(); + node.members.forEach(item => { + if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) { + const propertyName: string = item.name.getText(); + componentSet.properties.add(propertyName); + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item); + const accessQualifierResult: AccessQualifierResult = getAccessQualifier(item, uiCheck); + if (!decorators || !decorators.length) { + componentSet.regulars.add(propertyName); + setPrivateCollection(componentSet, accessQualifierResult, propertyName, COMPONENT_NON_DECORATOR); + setRegularStaticCollaction(componentSet, accessQualifierResult, propertyName); + } else { + isStatic = false; + let hasValidatePrivate: boolean = false; + const recordRequire: RecordRequire = new RecordRequire(); + for (let i = 0; i < decorators.length; i++) { + const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); + if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { + dollarCollection.add('$' + propertyName); + collectionStates(decorators[i], decoratorName, propertyName, + componentSet, recordRequire); + setPrivateCollection(componentSet, accessQualifierResult, propertyName, decoratorName); + validateAccessQualifier(item, propertyName, decoratorName, accessQualifierResult, + recordRequire, uiCheck, hasValidatePrivate); + hasValidatePrivate = true; + } + } + regularAndRequire(decorators, componentSet, recordRequire, propertyName, accessQualifierResult); + checkRequire(propertyName, componentSet, recordRequire); + } + } + if (ts.isMethodDeclaration(item) && item.name && ts.isIdentifier(item.name)) { + validateStateVariable(item); + currentMethodCollection.add(item.name.getText()); + } + }); + collectCurrentClassMethod(node, currentMethodCollection); + } + isStaticViewCollection.set(node.name.getText(), isStatic); +} + +function collectCurrentClassMethod(node: ts.StructDeclaration, currentMethodCollection: Set): void { + const componentMethodCollection: Map> = new Map(); + componentMethodCollection.set(node.name.getText(), currentMethodCollection); + const sourceFile: ts.SourceFile = node.getSourceFile(); + const filePath: string = sourceFile ? sourceFile.fileName : undefined; + if (filePath) { + const pageMethodCollection: Map> = classMethodCollection.get(filePath); + if (!pageMethodCollection) { + classMethodCollection.set(filePath, componentMethodCollection); + } else if (!pageMethodCollection.get(node.name.getText())) { + pageMethodCollection.set(node.name.getText(), currentMethodCollection); + } + } +} + +const FORBIDDEN_PUBLIC_ACCESS: string[] = [COMPONENT_STORAGE_PROP_DECORATOR, + COMPONENT_STORAGE_LINK_DECORATOR, COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, + COMPONENT_LOCAL_STORAGE_PROP_DECORATOR, COMPONENT_CONSUME_DECORATOR +]; +const FORBIDDEN_PRIVATE_ACCESS: string[] = [COMPONENT_LINK_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]; + +function validateAccessQualifier(node: ts.PropertyDeclaration, propertyName: string, + decoratorName: string, accessQualifierResult: AccessQualifierResult, + recordRequire: RecordRequire, uiCheck: boolean = false, hasValidatePrivate: boolean): void { + if (uiCheck) { + if (accessQualifierResult.hasPublic && FORBIDDEN_PUBLIC_ACCESS.includes(decoratorName)) { + transformLog.errors.push({ + type: LogType.WARN, + message: `Property '${propertyName}' can not be decorated with both ${decoratorName} and public.`, + pos: node.getStart() + }); + } + if (accessQualifierResult.hasPrivate) { + if (FORBIDDEN_PRIVATE_ACCESS.includes(decoratorName)) { + transformLog.errors.push({ + type: LogType.WARN, + message: `Property '${propertyName}' can not be decorated with both ${decoratorName} and private.`, + pos: node.getStart() + }); + } + if (recordRequire.hasRequire && !hasValidatePrivate) { + transformLog.errors.push({ + type: LogType.WARN, + message: `Property '${propertyName}' can not be decorated with both @Require and private.`, + pos: node.getStart() + }); + } + } + } +} + +const SUPPORT_PRIVATE_PROPS: string[] = [COMPONENT_NON_DECORATOR, COMPONENT_STATE_DECORATOR, + COMPONENT_PROP_DECORATOR, COMPONENT_PROVIDE_DECORATOR, COMPONENT_BUILDERPARAM_DECORATOR +]; + +function setPrivateCollection(componentSet: IComponentSet, accessQualifierResult: AccessQualifierResult, + propertyName: string, decoratorName: string): void { + if (accessQualifierResult.hasPrivate && SUPPORT_PRIVATE_PROPS.includes(decoratorName)) { + componentSet.privateCollection.add(propertyName); + } +} + +function setRegularStaticCollaction(componentSet: IComponentSet, accessQualifierResult: AccessQualifierResult, + propertyName: string): void { + if (accessQualifierResult.hasStatic) { + componentSet.regularStaticCollection.add(propertyName); + } +} + +class AccessQualifierResult { + hasPrivate: boolean = false; + hasPublic: boolean = false; + hasStatic: boolean = false; +} + +function getAccessQualifier(node: ts.PropertyDeclaration, uiCheck: boolean = false): AccessQualifierResult { + const modifiers: readonly ts.Modifier[] = ts.getModifiers(node); + const accessQualifierResult: AccessQualifierResult = new AccessQualifierResult(); + if (modifiers && modifiers.length) { + modifiers.forEach((item) => { + if (item.kind === ts.SyntaxKind.PrivateKeyword) { + accessQualifierResult.hasPrivate = true; + } + if (item.kind === ts.SyntaxKind.PublicKeyword) { + accessQualifierResult.hasPublic = true; + } + if (item.kind === ts.SyntaxKind.StaticKeyword) { + accessQualifierResult.hasStatic = true; + } + if (uiCheck && item.kind === ts.SyntaxKind.ProtectedKeyword) { + transformLog.errors.push({ + type: LogType.WARN, + message: `The member attributes of a struct can not be protected.`, + pos: node.getStart() + }); + } + }); + } + return accessQualifierResult; +} + +function regularAndRequire(decorators: readonly ts.Decorator[], componentSet: IComponentSet, + recordRequire: RecordRequire, propertyName: string, accessQualifierResult: AccessQualifierResult): void { + if (decorators && decorators.length === 1 && decorators[0].getText() === COMPONENT_REQUIRE_DECORATOR) { + componentSet.regulars.add(propertyName); + recordRequire.hasRegular = true; + setPrivateCollection(componentSet, accessQualifierResult, propertyName, COMPONENT_NON_DECORATOR); + } +} + +function checkRequire(name: string, componentSet: IComponentSet, recordRequire: RecordRequire): void { + if (recordRequire.hasRequire) { + setInitValue('hasProp', 'propData', name, componentSet, recordRequire); + setInitValue('hasBuilderParam', 'builderParamData', name, componentSet, recordRequire); + setInitValue('hasRegular', 'regularInit', name, componentSet, recordRequire); + setInitValue('hasState', 'stateInit', name, componentSet, recordRequire); + setInitValue('hasProvide', 'provideInit', name, componentSet, recordRequire); + } +} + +function setInitValue(requirekey: string, initKey: string, name: string, componentSet: IComponentSet, + recordRequire: RecordRequire): void { + if (recordRequire[requirekey]) { + componentSet[initKey].add(name); + } +} + +function collectionStates(node: ts.Decorator, decorator: string, name: string, + componentSet: IComponentSet, recordRequire: RecordRequire): void { + switch (decorator) { + case COMPONENT_STATE_DECORATOR: + componentSet.states.add(name); + recordRequire.hasState = true; + break; + case COMPONENT_LINK_DECORATOR: + componentSet.links.add(name); + break; + case COMPONENT_PROP_DECORATOR: + recordRequire.hasProp = true; + componentSet.props.add(name); + break; + case COMPONENT_STORAGE_PROP_DECORATOR: + componentSet.storageProps.add(name); + break; + case COMPONENT_STORAGE_LINK_DECORATOR: + componentSet.storageLinks.add(name); + break; + case COMPONENT_PROVIDE_DECORATOR: + recordRequire.hasProvide = true; + componentSet.provides.add(name); + break; + case COMPONENT_CONSUME_DECORATOR: + componentSet.consumes.add(name); + break; + case COMPONENT_OBJECT_LINK_DECORATOR: + componentSet.objectLinks.add(name); + break; + case COMPONENT_BUILDERPARAM_DECORATOR: + recordRequire.hasBuilderParam = true; + componentSet.builderParams.add(name); + break; + case COMPONENT_LOCAL_STORAGE_LINK_DECORATOR : + collectionlocalStorageParam(node, name, componentSet.localStorageLink); + break; + case COMPONENT_LOCAL_STORAGE_PROP_DECORATOR: + collectionlocalStorageParam(node, name, componentSet.localStorageProp); + break; + case COMPONENT_REQUIRE_DECORATOR: + recordRequire.hasRequire = true; + break; + } +} + +function collectionlocalStorageParam(node: ts.Decorator, name: string, + localStorage: Map>): void { + const localStorageParam: Set = new Set(); + if (node && ts.isCallExpression(node.expression) && node.expression.arguments && + node.expression.arguments.length) { + localStorage.set(name, localStorageParam.add( + node.expression.arguments[0].getText())); + } +} + +export interface ReplaceResult { + content: string, + log: LogInfo[] +} + +export function sourceReplace(source: string, sourcePath: string): ReplaceResult { + let content: string = source; + const log: LogInfo[] = []; + content = preprocessExtend(content); + content = preprocessNewExtend(content); + // process @system. + content = processSystemApi(content, false, sourcePath); + collectImportNames(content, sourcePath); + + return { + content: content, + log: log + }; +} + +export function preprocessExtend(content: string, extendCollection?: Set): string { + const REG_EXTEND: RegExp = /@Extend(\s+)([^\.\s]+)\.([^\(]+)\(/gm; + return content.replace(REG_EXTEND, (item, item1, item2, item3) => { + collectExtend(EXTEND_ATTRIBUTE, item2, '__' + item2 + '__' + item3); + collectExtend(EXTEND_ATTRIBUTE, item2, item3); + if (extendCollection) { + extendCollection.add(item3); + } + return `@Extend(${item2})${item1}function __${item2}__${item3}(`; + }); +} + +export function preprocessNewExtend(content: string, extendCollection?: Set): string { + const REG_EXTEND: RegExp = /@Extend\s*\([^\)]+\)\s*function\s+([^\(\s]+)\s*\(/gm; + return content.replace(REG_EXTEND, (item, item1) => { + if (extendCollection) { + extendCollection.add(item1); + } + return item; + }); +} + +function replaceSystemApi(item: string, systemValue: string, moduleType: string, systemKey: string): string { + // if change format, please update regexp in transformModuleSpecifier + if (NATIVE_MODULE.has(`${moduleType}.${systemKey}`)) { + item = `var ${systemValue} = globalThis.requireNativeModule('${moduleType}.${systemKey}')`; + } else if (moduleType === SYSTEM_PLUGIN || moduleType === OHOS_PLUGIN) { + item = `var ${systemValue} = globalThis.requireNapi('${systemKey}')`; + } + return item; +} + +function replaceLibSo(importValue: string, libSoKey: string, sourcePath: string = null): string { + if (sourcePath) { + useOSFiles.add(sourcePath); + } + // if change format, please update regexp in transformModuleSpecifier + return projectConfig.bundleName && projectConfig.moduleName ? + `var ${importValue} = globalThis.requireNapi("${libSoKey}", true, "${projectConfig.bundleName}/${projectConfig.moduleName}");` : + `var ${importValue} = globalThis.requireNapi("${libSoKey}", true);`; +} + +export function processSystemApi(content: string, isProcessAllowList: boolean = false, + sourcePath: string = null, isSystemModule: boolean = false): string { + if (isProcessAllowList && projectConfig.compileMode === ESMODULE) { + // remove the unused system api import decl like following when compile as [esmodule] + // in the result_process phase + // e.g. import "@ohos.application.xxx" + const REG_UNUSED_SYSTEM_IMPORT: RegExp = /import(?:\s*)['"]@(system|ohos)\.(\S+)['"]/g; + content = content.replace(REG_UNUSED_SYSTEM_IMPORT, ''); + } + + const REG_IMPORT_DECL: RegExp = isProcessAllowList ? projectConfig.compileMode === ESMODULE ? + /import\s+(.+)\s+from\s+['"]@(system|ohos)\.(\S+)['"]/g : + /(import|const)\s+(.+)\s*=\s*(\_\_importDefault\()?require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)(\))?/g : + /(import|export)\s+(?:(.+)|\{([\s\S]+)\})\s+from\s+['"](\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"](\S+)['"]\s*\)/g; + + const systemValueCollection: Set = new Set(); + const processedContent: string = content.replace(REG_IMPORT_DECL, (item, item1, item2, item3, item4, item5, item6) => { + const importValue: string = isProcessAllowList ? projectConfig.compileMode === ESMODULE ? item1 : item2 : item2 || item5; + + if (isProcessAllowList) { + systemValueCollection.add(importValue); + if (projectConfig.compileMode !== ESMODULE) { + collectSourcemapNames(sourcePath, importValue, item5); + return replaceSystemApi(item, importValue, item4, item5); + } + collectSourcemapNames(sourcePath, importValue, item3); + return replaceSystemApi(item, importValue, item2, item3); + } + + const moduleRequest: string = item4 || item6; + if (/^@(system|ohos)\./.test(moduleRequest)) { // ohos/system.api + // ets & ts file need compile with .d.ts, so do not replace at the phase of pre_process + if (!isSystemModule) { + return item; + } + const result: RegExpMatchArray = moduleRequest.match(/^@(system|ohos)\.(\S+)$/); + const moduleType: string = result[1]; + const apiName: string = result[2]; + return replaceSystemApi(item, importValue, moduleType, apiName); + } else if (/^lib(\S+)\.so$/.test(moduleRequest)) { // libxxx.so + const result: RegExpMatchArray = moduleRequest.match(/^lib(\S+)\.so$/); + const libSoKey: string = result[1]; + return replaceLibSo(importValue, libSoKey, sourcePath); + } + return item; + }); + return processInnerModule(processedContent, systemValueCollection); +} + +function collectSourcemapNames(sourcePath: string, changedName: string, originalName: string): void { + if (sourcePath == null) { + return; + } + const cleanSourcePath: string = sourcePath.replace('.ets', '.js').replace('.ts', '.js'); + if (!sourcemapNamesCollection.has(cleanSourcePath)) { + return; + } + + const map: Map = sourcemapNamesCollection.get(cleanSourcePath); + if (map.has(changedName)) { + return; + } + + for (const entry of originalImportNamesMap.entries()) { + const key: string = entry[0]; + const value: string = entry[1]; + if (value === '@ohos.' + originalName || value === '@system.' + originalName) { + map.set(changedName.trim(), key); + sourcemapNamesCollection.set(cleanSourcePath, map); + originalImportNamesMap.delete(key); + break; + } + } +} + +export function collectImportNames(content: string, sourcePath: string = null): void { + const REG_IMPORT_DECL: RegExp = + /(import|export)\s+(.+)\s+from\s+['"](\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"](\S+)['"]\s*\)/g; + + const decls: string[] = content.match(REG_IMPORT_DECL); + if (decls !== null) { + decls.forEach(decl => { + const parts: string[] = decl.split(' '); + if (parts.length === 4 && parts[0] === 'import' && parts[2] === 'from' && !parts[3].includes('.so')) { + originalImportNamesMap.set(parts[1], parts[3].replace(/'/g, '')); + } + }); + } + + if (sourcePath && sourcePath !== null) { + const cleanSourcePath: string = sourcePath.replace('.ets', '.js').replace('.ts', '.js'); + if (!sourcemapNamesCollection.has(cleanSourcePath)) { + sourcemapNamesCollection.set(cleanSourcePath, new Map()); + } + } +} + +function processInnerModule(content: string, systemValueCollection: Set): string { + systemValueCollection.forEach(element => { + const target: string = element.trim() + '.default'; + while (content.includes(target)) { + content = content.replace(target, element.trim()); + } + }); + return content; +} + +export function resetComponentCollection(): void { + componentCollection.entryComponent = null; + componentCollection.entryComponentPos = null; + componentCollection.previewComponent = []; + stateObjectCollection.clear(); + builderParamInitialization.clear(); + propInitialization.clear(); + propCollection.clear(); + objectLinkCollection.clear(); + linkCollection.clear(); + storedFileInfo.overallLinkCollection.clear(); + storedFileInfo.overallObjectLinkCollection.clear(); + storedFileInfo.overallBuilderParamCollection.clear(); + regularInitialization.clear(); + stateInitialization.clear(); + provideInitialization.clear(); + privateCollection.clear(); + regularStaticCollection.clear(); +} + +function checkEntryComponent(node: ts.StructDeclaration, log: LogInfo[], sourceFile: ts.SourceFile): void { + const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + if (modifiers) { + for (let i = 0; i < modifiers.length; i++) { + if (modifiers[i].kind === ts.SyntaxKind.ExportKeyword) { + const message: string = `It's not a recommended way to export struct with @Entry decorator, ` + + `which may cause ACE Engine error in component preview mode.`; + addLog(LogType.WARN, message, node.getStart(), log, sourceFile); + break; + } + } + } +} + +function validateStateVariable(node: ts.MethodDeclaration): void { + const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); + if (decorators && decorators.length) { + for (let i = 0; i < decorators.length; i++) { + const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); + if (CARD_ENABLE_DECORATORS[decoratorName]) { + validatorCard(transformLog.errors, CARD_LOG_TYPE_DECORATORS, + decorators[i].getStart(), decoratorName); + } + if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { + transformLog.errors.push({ + type: LogType.ERROR, + message: `'${decorators[i].getText()}' can not decorate the method.`, + pos: decorators[i].getStart(), + code: '10905112' + }); + } + } + } +} + +export function getObservedPropertyCollection(className: string): Set { + const observedProperthCollection: Set = new Set([ + ...stateCollection.get(className), + ...linkCollection.get(className), + ...propCollection.get(className), + ...storageLinkCollection.get(className), + ...storageLinkCollection.get(className), + ...provideCollection.get(className), + ...consumeCollection.get(className), + ...objectLinkCollection.get(className) + ]); + getLocalStorageCollection(className, observedProperthCollection); + return observedProperthCollection; +} + +export function getLocalStorageCollection(componentName: string, collection: Set): void { + if (localStorageLinkCollection.get(componentName)) { + for (const key of localStorageLinkCollection.get(componentName).keys()) { + collection.add(key); + } + } + if (localStoragePropCollection.get(componentName)) { + for (const key of localStoragePropCollection.get(componentName).keys()) { + collection.add(key); + } + } +} + +export function resetValidateUiSyntax(): void { + observedClassCollection.clear(); + enumCollection.clear(); + classMethodCollection.clear(); + dollarCollection.clear(); + stateCollection.clear(); + regularCollection.clear(); + storagePropCollection.clear(); + storageLinkCollection.clear(); + provideCollection.clear(); + consumeCollection.clear(); + builderParamObjectCollection.clear(); + localStorageLinkCollection.clear(); + localStoragePropCollection.clear(); + isStaticViewCollection.clear(); + useOSFiles.clear(); + sourcemapNamesCollection.clear(); + originalImportNamesMap.clear(); +}