1/* 2 * Copyright (c) 2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use rollupObject file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16// Execute this file first to avoid circular dependency problems 17 18import { expect } from 'chai'; 19import mocha from 'mocha'; 20import fs from "fs"; 21import path from "path"; 22import MagicString from 'magic-string'; 23import sinon from 'sinon'; 24import * as ts from 'typescript' 25 26import { 27 getBuildModeInLowerCase, 28 getPackageInfo, 29 genSourceMapFileName, 30 isOhModules, 31 isEs2Abc, 32 writeArkguardObfuscatedSourceCode, 33 writeMinimizedSourceCode, 34 tryMangleFileName, 35 transformLazyImport 36} from '../../lib/ark_utils'; 37import { 38 DEBUG, 39 RELEASE, 40 OH_MODULES, 41 EXTNAME_TS, 42 EXTNAME_JS, 43 EXTNAME_ETS, 44 OBFUSCATION_TOOL 45} from '../../lib/fast_build/ark_compiler/common/ark_define'; 46import RollUpPluginMock from './mock/rollup_mock/rollup_plugin_mock'; 47import { 48 BUNDLE_NAME_DEFAULT, 49 ENTRY_MODULE_NAME_DEFAULT, 50 EXTNAME_MAP, 51 ENTRYABILITY_JS 52} from './mock/rollup_mock/common'; 53import projectConfig from './utils/processProjectConfig'; 54import { 55 ES2ABC, 56 TS2ABC 57} from '../../lib/pre_define'; 58import { changeFileExtension } from '../../lib/fast_build/ark_compiler/utils'; 59import ModuleSourceFileMock from './mock/class_mock/module_source_files_mock'; 60import { 61 genTemporaryPath, 62 toUnixPath 63} from '../../lib/utils'; 64import { 65 ObConfigResolver, 66 MergedConfig 67} from '../../lib/fast_build/ark_compiler/common/ob_config_resolver'; 68import { 69 utProcessArkConfig 70} from '../../lib/fast_build/ark_compiler/common/process_ark_config'; 71import { ModuleSourceFile } from '../../lib/fast_build/ark_compiler/module/module_source_file'; 72import { PROJECT_ROOT, TERSER_PROCESSED_EXPECTED_CODE } from './mock/rollup_mock/path_config'; 73import { GEN_ABC_PLUGIN_NAME } from '../../lib/fast_build/ark_compiler/common/ark_define'; 74import { SourceMapGenerator } from '../../lib/fast_build/ark_compiler/generate_sourcemap'; 75import { ArkObfuscator } from 'arkguard'; 76 77mocha.describe('test ark_utils file api', function () { 78 mocha.before(function () { 79 this.rollup = new RollUpPluginMock(); 80 }); 81 82 mocha.after(() => { 83 delete this.rollup; 84 }); 85 86 mocha.it('1-1: test getBuildModeInLowerCase under build debug', function () { 87 this.rollup.build(); 88 const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig); 89 expect(buildMode === DEBUG).to.be.true; 90 }); 91 92 mocha.it('1-2: test getBuildModeInLowerCase under build release', function () { 93 this.rollup.build(RELEASE); 94 const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig); 95 expect(buildMode === RELEASE).to.be.true; 96 }); 97 98 mocha.it('1-3: test getBuildModeInLowerCase under preview debug', function () { 99 this.rollup.preview(); 100 const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig); 101 expect(buildMode === DEBUG).to.be.true; 102 }); 103 104 mocha.it('1-4: test getBuildModeInLowerCase under hot reload debug', function () { 105 this.rollup.hotReload(); 106 const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig); 107 expect(buildMode === DEBUG).to.be.true; 108 }); 109 110 mocha.it('2-1: test getPackageInfo under build debug', function () { 111 this.rollup.build(); 112 const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath); 113 expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true; 114 expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true; 115 }); 116 117 mocha.it('2-2: test getPackageInfo under build release', function () { 118 this.rollup.build(RELEASE); 119 const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath); 120 expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true; 121 expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true; 122 }); 123 124 mocha.it('2-3: test getPackageInfo under preview debug', function () { 125 this.rollup.preview(); 126 const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath); 127 expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true; 128 expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true; 129 }); 130 131 mocha.it('2-4: test getPackageInfo under hot reload debug', function () { 132 this.rollup.hotReload(); 133 const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath); 134 expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true; 135 expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true; 136 }); 137 138 mocha.it('3-1: test genSourceMapFileName under build debug', function () { 139 this.rollup.build(); 140 for (const filePath of this.rollup.share.allFiles) { 141 if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) { 142 const originPath = genSourceMapFileName(filePath); 143 const expectedPath = `${filePath}${EXTNAME_MAP}`; 144 expect(originPath === expectedPath).to.be.true; 145 } else if (filePath.endsWith(EXTNAME_ETS)) { 146 const originPath = genSourceMapFileName(filePath); 147 expect(originPath === filePath).to.be.true; 148 } 149 } 150 }); 151 152 mocha.it('3-2: test genSourceMapFileName under build release', function () { 153 this.rollup.build(RELEASE); 154 for (const filePath of this.rollup.share.allFiles) { 155 if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) { 156 const originPath = genSourceMapFileName(filePath); 157 const expectedPath = `${filePath}${EXTNAME_MAP}`; 158 expect(originPath === expectedPath).to.be.true; 159 } 160 } 161 }); 162 163 mocha.it('3-3: test genSourceMapFileName under preview debug', function () { 164 this.rollup.preview(); 165 for (const filePath of this.rollup.share.allFiles) { 166 if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) { 167 const originPath = genSourceMapFileName(filePath); 168 const expectedPath = `${filePath}${EXTNAME_MAP}`; 169 expect(originPath === expectedPath).to.be.true; 170 } 171 } 172 }); 173 174 mocha.it('3-4: test genSourceMapFileName under hot reload debug', function () { 175 this.rollup.hotReload(); 176 for (const filePath of this.rollup.share.allFiles) { 177 if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) { 178 const originPath = genSourceMapFileName(filePath); 179 const expectedPath = `${filePath}${EXTNAME_MAP}`; 180 expect(originPath === expectedPath).to.be.true; 181 } 182 } 183 }); 184 185 mocha.it('4-1: test isOhModules under build debug', function () { 186 this.rollup.build(); 187 const returnInfo = isOhModules(this.rollup.share.projectConfig); 188 expect(returnInfo === false).to.be.true; 189 this.rollup.share.projectConfig.packageDir = OH_MODULES; 190 const returnInfoOh = isOhModules(this.rollup.share.projectConfig); 191 expect(returnInfoOh).to.be.true; 192 }); 193 194 mocha.it('4-2: test isOhModules under build release', function () { 195 this.rollup.build(RELEASE); 196 const returnInfo = isOhModules(this.rollup.share.projectConfig); 197 expect(returnInfo === false).to.be.true; 198 this.rollup.share.projectConfig.packageDir = OH_MODULES; 199 const returnInfoOh = isOhModules(this.rollup.share.projectConfig); 200 expect(returnInfoOh).to.be.true; 201 }); 202 203 mocha.it('4-3: test isOhModules under preview debug', function () { 204 this.rollup.preview(); 205 const returnInfo = isOhModules(this.rollup.share.projectConfig); 206 expect(returnInfo === false).to.be.true; 207 this.rollup.share.projectConfig.packageDir = OH_MODULES; 208 const returnInfoOh = isOhModules(this.rollup.share.projectConfig); 209 expect(returnInfoOh).to.be.true; 210 }); 211 212 mocha.it('4-4: test isOhModules under hot reload debug', function () { 213 this.rollup.hotReload(); 214 const returnInfo = isOhModules(this.rollup.share.projectConfig); 215 expect(returnInfo === false).to.be.true; 216 this.rollup.share.projectConfig.packageDir = OH_MODULES; 217 const returnInfoOh = isOhModules(this.rollup.share.projectConfig); 218 expect(returnInfoOh).to.be.true; 219 }); 220 221 mocha.it('4-5: test isOhModules under hot fix debug', function () { 222 projectConfig.buildMode = DEBUG; 223 const returnInfo = isOhModules(projectConfig); 224 expect(returnInfo).to.be.true; 225 }); 226 227 mocha.it('4-6: test isOhModules under hot fix release', function () { 228 projectConfig.buildMode = RELEASE; 229 const returnInfo = isOhModules(projectConfig); 230 expect(returnInfo).to.be.true; 231 }); 232 233 mocha.it('5-1: test isEs2Abc under build debug', function () { 234 this.rollup.build(); 235 this.rollup.share.projectConfig.pandaMode = ES2ABC; 236 const returnInfo = isEs2Abc(this.rollup.share.projectConfig); 237 expect(returnInfo).to.be.true; 238 239 this.rollup.share.projectConfig.pandaMode = TS2ABC; 240 const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig); 241 expect(returnInfoTS2ABC === false).to.be.true; 242 243 this.rollup.share.projectConfig.pandaMode = "undefined"; 244 const returnInfoUndef = isEs2Abc(this.rollup.share.projectConfig); 245 expect(returnInfoUndef).to.be.true; 246 247 this.rollup.share.projectConfig.pandaMode = undefined; 248 const returnInfoUndefined = isEs2Abc(this.rollup.share.projectConfig); 249 expect(returnInfoUndefined).to.be.true; 250 }); 251 252 mocha.it('5-2: test isEs2Abc under build release', function () { 253 this.rollup.build(RELEASE); 254 this.rollup.share.projectConfig.pandaMode = ES2ABC; 255 const returnInfo = isEs2Abc(this.rollup.share.projectConfig); 256 expect(returnInfo).to.be.true; 257 258 this.rollup.share.projectConfig.pandaMode = TS2ABC; 259 const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig); 260 expect(returnInfoTS2ABC).to.be.false; 261 }); 262 263 mocha.it('5-3: test isEs2Abc under preview debug', function () { 264 this.rollup.preview(); 265 this.rollup.share.projectConfig.pandaMode = ES2ABC; 266 const returnInfo = isEs2Abc(this.rollup.share.projectConfig); 267 expect(returnInfo).to.be.true; 268 269 this.rollup.share.projectConfig.pandaMode = TS2ABC; 270 const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig); 271 expect(returnInfoTS2ABC).to.be.false; 272 }); 273 274 mocha.it('5-4: test isEs2Abc under hot reload debug', function () { 275 this.rollup.hotReload(); 276 this.rollup.share.projectConfig.pandaMode = ES2ABC; 277 const returnInfo = isEs2Abc(this.rollup.share.projectConfig); 278 expect(returnInfo).to.be.true; 279 280 this.rollup.share.projectConfig.pandaMode = TS2ABC; 281 const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig); 282 expect(returnInfoTS2ABC).to.be.false; 283 }); 284 285 mocha.it('5-5: test isEs2Abc under hot fix debug', function () { 286 projectConfig.buildMode = DEBUG; 287 projectConfig.pandaMode = ES2ABC; 288 const returnInfo = isEs2Abc(projectConfig); 289 expect(returnInfo).to.be.true; 290 291 projectConfig.pandaMode = TS2ABC; 292 const returnInfoTS2ABC = isEs2Abc(projectConfig); 293 expect(returnInfoTS2ABC).to.be.false; 294 }); 295 296 mocha.it('5-6: test isEs2Abc under hot fix release', function () { 297 projectConfig.buildMode = RELEASE; 298 projectConfig.pandaMode = ES2ABC; 299 const returnInfo = isEs2Abc(projectConfig); 300 expect(returnInfo).to.be.true; 301 302 projectConfig.pandaMode = TS2ABC; 303 const returnInfoTS2ABC = isEs2Abc(projectConfig); 304 expect(returnInfoTS2ABC).to.be.false; 305 }); 306 307 mocha.it('6-1: test the error message of writeArkguardObfuscatedSourceCode', async function () { 308 this.rollup.build(RELEASE); 309 SourceMapGenerator.initInstance(this.rollup); 310 const logger = this.rollup.share.getLogger(GEN_ABC_PLUGIN_NAME); 311 const stub = sinon.stub(logger, 'error'); 312 const red: string = '\x1B[31m'; 313 try { 314 await writeArkguardObfuscatedSourceCode( 315 {content: undefined, buildFilePath: '', relativeSourceFilePath: '', originSourceFilePath: ''}, 316 logger, this.rollup.share.projectConfig, {}); 317 } catch (e) { 318 } 319 expect(stub.calledWith(red, 320 `ArkTS:INTERNAL ERROR: Failed to obfuscate file '' with arkguard. TypeError: Cannot read properties of undefined (reading 'obfuscate')` 321 )).to.be.true; 322 stub.restore(); 323 SourceMapGenerator.cleanSourceMapObject(); 324 }); 325 326 mocha.it('7-1: test the error message of writeMinimizedSourceCode', async function () { 327 this.rollup.build(RELEASE); 328 const logger = this.rollup.share.getLogger(GEN_ABC_PLUGIN_NAME); 329 const stub = sinon.stub(logger, 'error'); 330 const red: string = '\x1B[31m'; 331 const reset: string = '\x1B[39m'; 332 try { 333 await writeMinimizedSourceCode(undefined, '', logger); 334 } catch (e) { 335 } 336 expect(stub.calledWith(red, 337 'ArkTS:INTERNAL ERROR: Failed to obfuscate source code for ', reset 338 )).to.be.true; 339 stub.restore(); 340 }); 341 342 mocha.it('8-1: test tryMangleFileName when obfuscation is disabled', async function () { 343 const filePath = '/mnt/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/release/src/main/ets/entryability/EntryAbility.ts'; 344 const originalFilePath = '/mnt/application/entry/src/main/ets/entryability/EntryAbility.ets'; 345 const projectConfig = { 346 projectRootPath: '/mnt/application', 347 packageDir: 'oh_modules', 348 modulePathMap: { 349 entry: '/mnt/application/entry' 350 }, 351 obfuscationMergedObConfig: { 352 options: { 353 enableFileNameObfuscation: false 354 } 355 } 356 } 357 const result = tryMangleFileName(filePath, projectConfig, originalFilePath); 358 expect(result === filePath).to.be.true; 359 }); 360 361 mocha.it('8-2: test tryMangleFileName when obfuscation is enabled', async function () { 362 const filePath = '/mnt/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/release/src/main/ets/entryability/EntryAbility.ts'; 363 const originalFilePath = '/mnt/application/entry/src/main/ets/entryability/EntryAbility.ets'; 364 const newFilePath = '/mnt/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/release/src/main/ets/a/b.ts'; 365 const projectConfig = { 366 projectRootPath: '/mnt/application', 367 packageDir: 'oh_modules', 368 modulePathMap: { 369 entry: '/mnt/application/entry' 370 }, 371 obfuscationMergedObConfig: { 372 options: { 373 enableFileNameObfuscation: true 374 } 375 } 376 } 377 378 const printerConfig = { 379 //Print obfuscation time&memory usage of all files and obfuscation processes 380 mFilesPrinter: false, 381 //Print time&memory usage of a single file obfuscation in transform processes 382 mSingleFilePrinter: false, 383 //Print sum up time of transform processes during obfuscation 384 mSumPrinter: false, 385 //Output path of printer 386 mOutputPath: "" 387 } 388 389 const arkguardConfig = { 390 mRenameFileName: { 391 mEnable: true, 392 mNameGeneratorType: 1, 393 mReservedFileNames: [ 394 'entry', 395 'mnt', 396 'application', 397 'entry', 398 'src', 399 'main', 400 'ets', 401 'build', 402 'default', 403 'cache', 404 'default', 405 'default@CompileArkTS', 406 'esmodule', 407 'release' 408 ], 409 }, 410 mPerformancePrinter: printerConfig 411 } 412 413 let arkObfuscator: ArkObfuscator = new ArkObfuscator(); 414 arkObfuscator.init(arkguardConfig); 415 const content = `function foo() {}` 416 const obfuscateResult = arkObfuscator.obfuscate(content, filePath, undefined); 417 obfuscateResult.then(result => { 418 const afterObfuscateFilePath = result.filePath; 419 expect(afterObfuscateFilePath === newFilePath).to.be.true; 420 }) 421 422 const result = tryMangleFileName(filePath, projectConfig, originalFilePath); 423 expect(result === newFilePath).to.be.true; 424 }); 425 426 mocha.it('9-1: test writeArkguardObfuscatedSourceCode when obfuscation is enabled', async function () { 427 this.rollup.build(RELEASE); 428 const logger = this.rollup.share.getLogger(GEN_ABC_PLUGIN_NAME); 429 const arkguardConfig = { 430 mCompact: false, 431 mDisableConsole: false, 432 mSimplify: false, 433 mRemoveComments: true, 434 mNameObfuscation: { 435 mEnable: true, 436 mNameGeneratorType: 1, 437 mReservedNames: [], 438 mRenameProperties: false, 439 mReservedProperties: [], 440 mKeepStringProperty: true, 441 mTopLevel: false, 442 mReservedToplevelNames: [], 443 mUniversalReservedProperties: [], 444 mUniversalReservedToplevelNames: [], 445 }, 446 mRemoveDeclarationComments: { 447 mEnable: true, 448 mReservedComments: [] 449 }, 450 mEnableSourceMap: true, 451 mEnableNameCache: true, 452 mRenameFileName: { 453 mEnable: false, 454 mNameGeneratorType: 1, 455 mReservedFileNames: [], 456 }, 457 mExportObfuscation: false, 458 mPerformancePrinter: { 459 mFilesPrinter: false, 460 mSingleFilePrinter: false, 461 mSumPrinter: false, 462 mOutputPath: "" 463 }, 464 mKeepFileSourceCode: { 465 mKeepSourceOfPaths: new Set(), 466 mkeepFilesAndDependencies: new Set(), 467 }, 468 }; 469 470 const sourceMapGenerator: SourceMapGenerator = SourceMapGenerator.initInstance(this.rollup); 471 sourceMapGenerator.setNewSoureMaps(false); 472 const arkObfuscator: ArkObfuscator = new ArkObfuscator(); 473 arkObfuscator.init(arkguardConfig); 474 475 const projectConfig = { 476 projectRootPath: PROJECT_ROOT, 477 arkObfuscator: arkObfuscator, 478 packageDir: 'oh_modules', 479 localPackageSet: new Set(), 480 useTsHar: false, 481 useNormalized: false 482 }; 483 484 const sourceFileName = 'sourceFile.ts'; 485 const originalSourceFilePath = path.join(__dirname, '../../test/ark_compiler_ut/testdata/har_obfuscation', sourceFileName); 486 const moduleInfo = { 487 content: fs.readFileSync(originalSourceFilePath, 'utf-8'), 488 buildFilePath: `${PROJECT_ROOT}/har_obfuscation/build/default/cache/${sourceFileName}`, 489 relativeSourceFilePath: `har_obfuscation/${sourceFileName}`, 490 originSourceFilePath: originalSourceFilePath, 491 rollupModuleId: originalSourceFilePath 492 }; 493 494 try { 495 await writeArkguardObfuscatedSourceCode(moduleInfo, logger, projectConfig, {}); 496 } catch (e) { 497 } 498 const expectedResult = `export function add(d: number, e: number): number { 499 return d + e; 500} 501export function findElement<a>(b: a[], c: (item: a) => boolean): a | undefined { 502 return b.find(c); 503} 504`; 505 const result = fs.readFileSync(moduleInfo.buildFilePath, 'utf-8'); 506 expect(result === expectedResult).to.be.true; 507 508 const declFileName = 'sourceFile.d.ts'; 509 const originalDeclFilePath = path.join(__dirname, '../../test/ark_compiler_ut/testdata/har_obfuscation/', declFileName); 510 511 const declModuleInfo = { 512 content: fs.readFileSync(originalDeclFilePath, 'utf-8'), 513 buildFilePath: `${PROJECT_ROOT}/har_obfuscation/build/default/cache/${declFileName}`, 514 relativeSourceFilePath: `har_obfuscation/build/default/cache/${declFileName}`, 515 originSourceFilePath: originalSourceFilePath, 516 rollupModuleId: originalSourceFilePath 517 }; 518 519 try { 520 await writeArkguardObfuscatedSourceCode(declModuleInfo, logger, projectConfig, {}); 521 } catch (e) { 522 } 523 524 const expectedDeclResult = `export declare function add(d: number, e: number): number; 525export declare function findElement<a>(b: a[], c: (item: a) => boolean): a | undefined; 526`; 527 const declResult = fs.readFileSync(declModuleInfo.buildFilePath, 'utf-8'); 528 expect(declResult === expectedDeclResult).to.be.true; 529 }); 530 531 mocha.it('10-1: test transformLazyImport (js code): perform lazy conversion', function () { 532 const code: string = ` 533 import { test } from "./test"; 534 import { test1 as t } from "./test1"; 535 const a = "a" + test() + t(); 536 `; 537 const expectCode: string = 'import lazy { test } from "./test";\n' + 538 'import lazy { test1 as t } from "./test1";\n' + 539 'const a = "a" + test() + t();\n'; 540 const result: string = transformLazyImport(code, undefined, "index.js"); 541 expect(result === expectCode).to.be.true; 542 }); 543 544 mocha.it('10-2: test transformLazyImport (js code): no lazy conversion', function () { 545 const code: string = ` 546 import lazy { test } from "./test"; 547 import lazy { test1 as t } from "./test1"; 548 import test2 from "./test2"; 549 import * as test3 from "./test3"; 550 import test4, { test5 } from "./test4"; 551 import "test6"; 552 `; 553 const expectCode: string = 'import lazy { test } from "./test";\n' + 554 'import lazy { test1 as t } from "./test1";\n' + 555 'import test2 from "./test2";\n' + 556 'import * as test3 from "./test3";\n' + 557 'import test4, { test5 } from "./test4";\n' + 558 'import "test6";\n'; 559 const result: string = transformLazyImport(code, undefined, "index.js"); 560 expect(result === expectCode).to.be.true; 561 }); 562});