1/* 2 * Copyright (c) 2024-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this 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 16import fs from 'fs'; 17import path from 'path'; 18 19import { SceneConfig, SceneOptions, Sdk, TsConfig } from './Config'; 20import { initModulePathMap, ModelUtils } from './core/common/ModelUtils'; 21import { TypeInference } from './core/common/TypeInference'; 22import { VisibleValue } from './core/common/VisibleValue'; 23import { ArkClass } from './core/model/ArkClass'; 24import { ArkFile, Language } from './core/model/ArkFile'; 25import { ArkMethod } from './core/model/ArkMethod'; 26import { ArkNamespace } from './core/model/ArkNamespace'; 27import { ClassSignature, FileSignature, MethodSignature, NamespaceSignature } from './core/model/ArkSignature'; 28import Logger, { LOG_MODULE_TYPE } from './utils/logger'; 29import { Local } from './core/base/Local'; 30import { buildArkFileFromFile } from './core/model/builder/ArkFileBuilder'; 31import { fetchDependenciesFromFile, parseJsonText } from './utils/json5parser'; 32import { getAllFiles } from './utils/getAllFiles'; 33import { FileUtils, getFileRecursively } from './utils/FileUtils'; 34import { ArkExport, ExportInfo, ExportType } from './core/model/ArkExport'; 35import { addInitInConstructor, buildDefaultConstructor } from './core/model/builder/ArkMethodBuilder'; 36import { DEFAULT_ARK_CLASS_NAME, STATIC_INIT_METHOD_NAME } from './core/common/Const'; 37import { CallGraph } from './callgraph/model/CallGraph'; 38import { CallGraphBuilder } from './callgraph/model/builder/CallGraphBuilder'; 39import { IRInference } from './core/common/IRInference'; 40import { ImportInfo } from './core/model/ArkImport'; 41import { ALL, CONSTRUCTOR_NAME, TSCONFIG_JSON } from './core/common/TSConst'; 42import { BUILD_PROFILE_JSON5, OH_PACKAGE_JSON5 } from './core/common/EtsConst'; 43import { SdkUtils } from './core/common/SdkUtils'; 44import { PointerAnalysisConfig } from './callgraph/pointerAnalysis/PointerAnalysisConfig'; 45import { ValueUtil } from './core/common/ValueUtil'; 46 47const logger = Logger.getLogger(LOG_MODULE_TYPE.ARKANALYZER, 'Scene'); 48 49enum SceneBuildStage { 50 BUILD_INIT, 51 SDK_INFERRED, 52 CLASS_DONE, 53 METHOD_DONE, 54 CLASS_COLLECTED, 55 METHOD_COLLECTED, 56 TYPE_INFERRED, 57} 58 59/** 60 * The Scene class includes everything in the analyzed project. 61 * We should be able to re-generate the project's code based on this class. 62 */ 63export class Scene { 64 private projectName: string = ''; 65 private projectFiles: string[] = []; 66 private realProjectDir: string = ''; 67 68 private moduleScenesMap: Map<string, ModuleScene> = new Map(); 69 private modulePath2NameMap: Map<string, string> = new Map<string, string>(); 70 71 private moduleSdkMap: Map<string, Sdk[]> = new Map(); 72 private projectSdkMap: Map<string, Sdk> = new Map(); 73 74 // values that are visible in curr scope 75 private visibleValue: VisibleValue = new VisibleValue(); 76 77 // signature string to model 78 private filesMap: Map<string, ArkFile> = new Map(); 79 private namespacesMap: Map<string, ArkNamespace> = new Map(); 80 private classesMap: Map<string, ArkClass> = new Map(); 81 private methodsMap: Map<string, ArkMethod> = new Map(); 82 // TODO: type of key should be signature object 83 private sdkArkFilesMap: Map<string, ArkFile> = new Map(); 84 private sdkGlobalMap: Map<string, ArkExport> = new Map<string, ArkExport>(); 85 private ohPkgContentMap: Map<string, { [k: string]: unknown }> = new Map<string, { [k: string]: unknown }>(); 86 private ohPkgFilePath: string = ''; 87 private ohPkgContent: { [k: string]: unknown } = {}; 88 private overRides: Map<string, string> = new Map(); 89 private overRideDependencyMap: Map<string, unknown> = new Map(); 90 private globalModule2PathMapping?: { [k: string]: string[] } | undefined; 91 private baseUrl?: string | undefined; 92 93 private buildStage: SceneBuildStage = SceneBuildStage.BUILD_INIT; 94 private fileLanguages: Map<string, Language> = new Map(); 95 96 private options!: SceneOptions; 97 private indexPathArray = ['Index.ets', 'Index.ts', 'Index.d.ets', 'Index.d.ts', 'index.ets', 'index.ts', 'index.d.ets', 'index.d.ts']; 98 99 private unhandledFilePaths: string[] = []; 100 private unhandledSdkFilePaths: string[] = []; 101 102 constructor() {} 103 104 /* 105 * Set all static field to be null, then all related objects could be freed by GC. 106 * This method could be called before drop Scene. 107 */ 108 public dispose(): void { 109 PointerAnalysisConfig.dispose(); 110 SdkUtils.dispose(); 111 ValueUtil.dispose(); 112 ModelUtils.dispose(); 113 } 114 115 public getOptions(): SceneOptions { 116 return this.options; 117 } 118 119 public getOverRides(): Map<string, string> { 120 return this.overRides; 121 } 122 123 public getOverRideDependencyMap(): Map<string, unknown> { 124 return this.overRideDependencyMap; 125 } 126 127 public clear(): void { 128 this.projectFiles = []; 129 130 this.moduleScenesMap.clear(); 131 this.modulePath2NameMap.clear(); 132 133 this.moduleSdkMap.clear(); 134 this.projectSdkMap.clear(); 135 136 this.filesMap.clear(); 137 this.namespacesMap.clear(); 138 this.classesMap.clear(); 139 this.methodsMap.clear(); 140 141 this.sdkArkFilesMap.clear(); 142 this.sdkGlobalMap.clear(); 143 this.ohPkgContentMap.clear(); 144 this.ohPkgContent = {}; 145 } 146 147 public getStage(): SceneBuildStage { 148 return this.buildStage; 149 } 150 151 /** 152 * Build scene object according to the {@link SceneConfig}. This API implements 3 functions. 153 * First is to build scene object from {@link SceneConfig}, second is to generate {@link ArkFile}s, 154 * and the last is to collect project import infomation. 155 * @param sceneConfig - a sceneConfig object, which is usally defined by user or Json file. 156 * @example 157 * 1. Build Scene object from scene config 158 159 ```typescript 160 // build config 161 const projectDir = ... ...; 162 const sceneConfig = new SceneConfig(); 163 sceneConfig.buildFromProjectDir(projectDir); 164 165 // build scene 166 const scene = new Scene(); 167 scene.buildSceneFromProjectDir(sceneConfig); 168 ``` 169 */ 170 public buildSceneFromProjectDir(sceneConfig: SceneConfig): void { 171 this.buildBasicInfo(sceneConfig); 172 this.genArkFiles(); 173 } 174 175 public buildSceneFromFiles(sceneConfig: SceneConfig): void { 176 this.buildBasicInfo(sceneConfig); 177 this.buildOhPkgContentMap(); 178 initModulePathMap(this.ohPkgContentMap); 179 this.getFilesOrderByDependency(); 180 } 181 182 /** 183 * Set the basic information of the scene using a config, 184 * such as the project's name, real path and files. 185 * @param sceneConfig - the config used to set the basic information of scene. 186 */ 187 public buildBasicInfo(sceneConfig: SceneConfig): void { 188 this.options = sceneConfig.getOptions(); 189 this.projectName = sceneConfig.getTargetProjectName(); 190 this.realProjectDir = fs.realpathSync(sceneConfig.getTargetProjectDirectory()); 191 this.projectFiles = sceneConfig.getProjectFiles(); 192 193 this.parseBuildProfile(); 194 195 this.parseOhPackage(); 196 let tsConfigFilePath; 197 if (this.options.tsconfig) { 198 tsConfigFilePath = path.join(sceneConfig.getTargetProjectDirectory(), this.options.tsconfig); 199 } else { 200 tsConfigFilePath = path.join(sceneConfig.getTargetProjectDirectory(), TSCONFIG_JSON); 201 } 202 if (fs.existsSync(tsConfigFilePath)) { 203 const tsConfigObj: TsConfig = fetchDependenciesFromFile(tsConfigFilePath); 204 this.findTsConfigInfoDeeply(tsConfigObj, tsConfigFilePath); 205 } else { 206 logger.warn('This project has no tsconfig.json!'); 207 } 208 209 // handle sdks 210 if (this.options.enableBuiltIn && !sceneConfig.getSdksObj().find(sdk => sdk.name === SdkUtils.BUILT_IN_NAME)) { 211 sceneConfig.getSdksObj().unshift(SdkUtils.BUILT_IN_SDK); 212 } 213 sceneConfig.getSdksObj()?.forEach(sdk => { 214 if (!sdk.moduleName) { 215 this.buildSdk(sdk.name, sdk.path); 216 this.projectSdkMap.set(sdk.name, sdk); 217 } else { 218 let moduleSdks = this.moduleSdkMap.get(sdk.moduleName); 219 if (moduleSdks) { 220 moduleSdks.push(sdk); 221 } else { 222 this.moduleSdkMap.set(sdk.moduleName, [sdk]); 223 } 224 } 225 }); 226 if (this.buildStage < SceneBuildStage.SDK_INFERRED) { 227 this.sdkArkFilesMap.forEach(file => { 228 IRInference.inferFile(file); 229 SdkUtils.mergeGlobalAPI(file, this.sdkGlobalMap); 230 }); 231 this.sdkArkFilesMap.forEach(file => { 232 SdkUtils.postInferredSdk(file, this.sdkGlobalMap); 233 }); 234 this.buildStage = SceneBuildStage.SDK_INFERRED; 235 } 236 this.fileLanguages = sceneConfig.getFileLanguages(); 237 } 238 239 private parseBuildProfile(): void { 240 const buildProfile = path.join(this.realProjectDir, BUILD_PROFILE_JSON5); 241 if (fs.existsSync(buildProfile)) { 242 let configurationsText: string; 243 try { 244 configurationsText = fs.readFileSync(buildProfile, 'utf-8'); 245 } catch (error) { 246 logger.error(`Error reading file: ${error}`); 247 return; 248 } 249 const buildProfileJson = parseJsonText(configurationsText); 250 SdkUtils.setEsVersion(buildProfileJson); 251 const modules = buildProfileJson.modules; 252 if (modules instanceof Array) { 253 modules.forEach(module => { 254 this.modulePath2NameMap.set(path.resolve(this.realProjectDir, path.join(module.srcPath)), module.name); 255 }); 256 } 257 } else { 258 logger.warn('There is no build-profile.json5 for this project.'); 259 } 260 } 261 262 private parseOhPackage(): void { 263 const OhPkgFilePath = path.join(this.realProjectDir, OH_PACKAGE_JSON5); 264 if (fs.existsSync(OhPkgFilePath)) { 265 this.ohPkgFilePath = OhPkgFilePath; 266 this.ohPkgContent = fetchDependenciesFromFile(this.ohPkgFilePath); 267 this.ohPkgContentMap.set(OhPkgFilePath, this.ohPkgContent); 268 if (this.ohPkgContent.overrides) { 269 let overRides = this.ohPkgContent.overrides as { [k: string]: unknown }; 270 for (const [key, value] of Object.entries(overRides)) { 271 this.overRides.set(key, value as string); 272 } 273 } 274 if (this.ohPkgContent.overrideDependencyMap) { 275 let globalOverRideDependencyMap = this.ohPkgContent.overrideDependencyMap as { [k: string]: unknown }; 276 for (const [key, value] of Object.entries(globalOverRideDependencyMap)) { 277 let globalDependency = fetchDependenciesFromFile(value as string); 278 this.overRideDependencyMap.set(key, globalDependency); 279 } 280 } 281 } else { 282 logger.warn('This project has no oh-package.json5!'); 283 } 284 } 285 286 private findTsConfigInfoDeeply(tsConfigObj: TsConfig, tsConfigFilePath: string): void { 287 if (tsConfigObj.extends) { 288 const extTsConfigObj: TsConfig = fetchDependenciesFromFile(path.join(path.dirname(tsConfigFilePath), tsConfigObj.extends)); 289 this.findTsConfigInfoDeeply(extTsConfigObj, tsConfigFilePath); 290 if (!this.baseUrl && !this.globalModule2PathMapping) { 291 this.addTsConfigInfo(extTsConfigObj); 292 } 293 } 294 if (!this.baseUrl && !this.globalModule2PathMapping) { 295 this.addTsConfigInfo(tsConfigObj); 296 } 297 } 298 299 private addTsConfigInfo(tsConfigObj: TsConfig): void { 300 if (tsConfigObj.compilerOptions && tsConfigObj.compilerOptions.paths) { 301 const paths = tsConfigObj.compilerOptions.paths; 302 if (paths) { 303 this.globalModule2PathMapping = paths; 304 } 305 } 306 if (tsConfigObj.compilerOptions && tsConfigObj.compilerOptions.baseUrl) { 307 this.baseUrl = tsConfigObj.compilerOptions.baseUrl; 308 } 309 } 310 311 private addDefaultConstructors(): void { 312 for (const file of this.getFiles()) { 313 for (const cls of ModelUtils.getAllClassesInFile(file)) { 314 buildDefaultConstructor(cls); 315 const constructor = cls.getMethodWithName(CONSTRUCTOR_NAME); 316 if (constructor !== null) { 317 addInitInConstructor(constructor); 318 } 319 } 320 } 321 } 322 323 private buildAllMethodBody(): void { 324 this.buildStage = SceneBuildStage.CLASS_DONE; 325 const methods: ArkMethod[] = []; 326 for (const file of this.getFiles()) { 327 for (const cls of file.getClasses()) { 328 for (const method of cls.getMethods(true)) { 329 methods.push(method); 330 } 331 } 332 } 333 for (const namespace of this.getNamespacesMap().values()) { 334 for (const cls of namespace.getClasses()) { 335 for (const method of cls.getMethods(true)) { 336 methods.push(method); 337 } 338 } 339 } 340 341 for (const method of methods) { 342 try { 343 method.buildBody(); 344 } catch (error) { 345 logger.error('Error building body:', method.getSignature(), error); 346 } finally { 347 method.freeBodyBuilder(); 348 } 349 } 350 351 ModelUtils.dispose(); 352 this.buildStage = SceneBuildStage.METHOD_DONE; 353 } 354 355 private genArkFiles(): void { 356 this.projectFiles.forEach(file => { 357 logger.trace('=== parse file:', file); 358 try { 359 const arkFile: ArkFile = new ArkFile(FileUtils.getFileLanguage(file, this.fileLanguages)); 360 arkFile.setScene(this); 361 buildArkFileFromFile(file, this.realProjectDir, arkFile, this.projectName); 362 this.filesMap.set(arkFile.getFileSignature().toMapKey(), arkFile); 363 } catch (error) { 364 logger.error('Error parsing file:', file, error); 365 this.unhandledFilePaths.push(file); 366 return; 367 } 368 }); 369 this.buildAllMethodBody(); 370 this.addDefaultConstructors(); 371 } 372 373 private getFilesOrderByDependency(): void { 374 for (const projectFile of this.projectFiles) { 375 this.getDependencyFilesDeeply(projectFile); 376 } 377 this.buildAllMethodBody(); 378 this.addDefaultConstructors(); 379 } 380 381 private getDependencyFilesDeeply(projectFile: string): void { 382 if (!this.options.supportFileExts!.includes(path.extname(projectFile))) { 383 return; 384 } 385 const fileSignature = new FileSignature(this.getProjectName(), path.relative(this.getRealProjectDir(), projectFile)); 386 if (this.filesMap.has(fileSignature.toMapKey()) || this.isRepeatBuildFile(projectFile)) { 387 return; 388 } 389 try { 390 const arkFile = new ArkFile(FileUtils.getFileLanguage(projectFile, this.fileLanguages)); 391 arkFile.setScene(this); 392 buildArkFileFromFile(projectFile, this.getRealProjectDir(), arkFile, this.getProjectName()); 393 for (const [modulePath, moduleName] of this.modulePath2NameMap) { 394 if (arkFile.getFilePath().startsWith(modulePath)) { 395 this.addArkFile2ModuleScene(modulePath, moduleName, arkFile); 396 break; 397 } 398 } 399 this.filesMap.set(arkFile.getFileSignature().toMapKey(), arkFile); 400 const importInfos = arkFile.getImportInfos(); 401 const repeatFroms: string[] = []; 402 this.findDependencyFiles(importInfos, arkFile, repeatFroms); 403 404 const exportInfos = arkFile.getExportInfos(); 405 this.findDependencyFiles(exportInfos, arkFile, repeatFroms); 406 } catch (error) { 407 logger.error('Error parsing file:', projectFile, error); 408 this.unhandledFilePaths.push(projectFile); 409 return; 410 } 411 } 412 413 private isRepeatBuildFile(projectFile: string): boolean { 414 for (const [key, file] of this.filesMap) { 415 if (key && file.getFilePath().toLowerCase() === projectFile.toLowerCase()) { 416 return true; 417 } 418 } 419 return false; 420 } 421 422 private addArkFile2ModuleScene(modulePath: string, moduleName: string, arkFile: ArkFile): void { 423 if (this.moduleScenesMap.has(moduleName)) { 424 let curModuleScene = this.moduleScenesMap.get(moduleName); 425 if (curModuleScene) { 426 curModuleScene.addArkFile(arkFile); 427 arkFile.setModuleScene(curModuleScene); 428 } 429 } else { 430 let moduleScene = new ModuleScene(this); 431 moduleScene.ModuleScenePartiallyBuilder(moduleName, modulePath); 432 moduleScene.addArkFile(arkFile); 433 this.moduleScenesMap.set(moduleName, moduleScene); 434 arkFile.setModuleScene(moduleScene); 435 } 436 } 437 438 private findDependencyFiles(importOrExportInfos: ImportInfo[] | ExportInfo[], arkFile: ArkFile, repeatFroms: string[]): void { 439 for (const importOrExportInfo of importOrExportInfos) { 440 const from = importOrExportInfo.getFrom(); 441 if (from && !repeatFroms.includes(from)) { 442 this.parseFrom(from, arkFile); 443 repeatFroms.push(from); 444 } 445 } 446 } 447 448 private parseFrom(from: string, arkFile: ArkFile): void { 449 if (/^@[a-z|\-]+?\/?/.test(from)) { 450 for (const [ohPkgContentPath, ohPkgContent] of this.ohPkgContentMap) { 451 this.findDependenciesByOhPkg(ohPkgContentPath, ohPkgContent, from, arkFile); 452 } 453 } else if (/^([^@]*\/)([^\/]*)$/.test(from) || /^[\.\./|\.\.]+$/.test(from)) { 454 this.findRelativeDependenciesByOhPkg(from, arkFile); 455 } else if (/^[@a-zA-Z0-9]+(\/[a-zA-Z0-9]+)*$/.test(from)) { 456 this.findDependenciesByTsConfig(from, arkFile); 457 } 458 } 459 460 private findDependenciesByTsConfig(from: string, arkFile: ArkFile): void { 461 if (this.globalModule2PathMapping) { 462 const paths: { [k: string]: string[] } = this.globalModule2PathMapping; 463 Object.keys(paths).forEach(key => this.parseTsConfigParms(paths, key, from, arkFile)); 464 } 465 } 466 467 private parseTsConfigParms(paths: { [k: string]: string[] }, key: string, from: string, arkFile: ArkFile): void { 468 const module2pathMapping = paths[key]; 469 if (key.includes(ALL)) { 470 this.processFuzzyMapping(key, from, module2pathMapping, arkFile); 471 } else if (from.startsWith(key)) { 472 let tail = from.substring(key.length, from.length); 473 module2pathMapping.forEach(pathMapping => { 474 let originPath = path.join(this.getRealProjectDir(), pathMapping, tail); 475 if (this.baseUrl) { 476 originPath = path.resolve(this.baseUrl, originPath); 477 } 478 this.findDependenciesByRule(originPath, arkFile); 479 }); 480 } 481 } 482 483 private processFuzzyMapping(key: string, from: string, module2pathMapping: string[], arkFile: ArkFile): void { 484 key = key.substring(0, key.indexOf(ALL) - 1); 485 if (from.substring(0, key.indexOf(ALL) - 1) === key) { 486 let tail = from.substring(key.indexOf(ALL) - 1, from.length); 487 module2pathMapping.forEach(pathMapping => { 488 pathMapping = pathMapping.substring(0, pathMapping.indexOf(ALL) - 1); 489 let originPath = path.join(this.getRealProjectDir(), pathMapping, tail); 490 if (this.baseUrl) { 491 originPath = path.join(this.baseUrl, originPath); 492 } 493 this.findDependenciesByRule(originPath, arkFile); 494 }); 495 } 496 } 497 498 private findDependenciesByRule(originPath: string, arkFile: ArkFile): void { 499 if ( 500 !this.findFilesByPathArray(originPath, this.indexPathArray, arkFile) && 501 !this.findFilesByExtNameArray(originPath, this.options.supportFileExts!, arkFile) 502 ) { 503 logger.trace(originPath + 'module mapperInfo is not found!'); 504 } 505 } 506 507 private findFilesByPathArray(originPath: string, pathArray: string[], arkFile: ArkFile): boolean { 508 for (const pathInfo of pathArray) { 509 const curPath = path.join(originPath, pathInfo); 510 if (fs.existsSync(curPath) && !this.isRepeatBuildFile(curPath)) { 511 this.addFileNode2DependencyGrap(curPath, arkFile); 512 return true; 513 } 514 } 515 return false; 516 } 517 518 private findFilesByExtNameArray(originPath: string, pathArray: string[], arkFile: ArkFile): boolean { 519 for (const pathInfo of pathArray) { 520 const curPath = originPath + pathInfo; 521 if (fs.existsSync(curPath) && !this.isRepeatBuildFile(curPath)) { 522 this.addFileNode2DependencyGrap(curPath, arkFile); 523 return true; 524 } 525 } 526 return false; 527 } 528 529 private findRelativeDependenciesByOhPkg(from: string, arkFile: ArkFile): void { 530 //relative path ../from ./from 531 //order 532 //1. ../from/oh-package.json5 -> [[name]] -> overRides/overRideDependencyMap? -> 533 //[[main]] -> file path ->dependencies(priority)+devDependencies? dynamicDependencies(not support) -> 534 //key overRides/overRideDependencyMap? 535 //2. ../from/index.ets(ts) 536 //3. ../from/index.d.ets(ts) 537 //4. ../from.ets(ts) 538 //5. ../from.d.ets(ts) 539 //2.3.4.5 random order 540 let originPath = this.getOriginPath(from, arkFile); 541 if (fs.existsSync(path.join(originPath, OH_PACKAGE_JSON5))) { 542 for (const [ohPkgContentPath, ohPkgContent] of this.ohPkgContentMap) { 543 this.findDependenciesByOhPkg(ohPkgContentPath, ohPkgContent, from, arkFile); 544 } 545 } 546 this.findDependenciesByRule(originPath, arkFile); 547 } 548 549 private findDependenciesByOhPkg( 550 ohPkgContentPath: string, 551 ohPkgContentInfo: { 552 [k: string]: unknown; 553 }, 554 from: string, 555 arkFile: ArkFile 556 ): void { 557 //module name @ohos/from 558 const ohPkgContent: { [k: string]: unknown } | undefined = ohPkgContentInfo; 559 //module main name is must be 560 if (ohPkgContent && ohPkgContent.name && from.startsWith(ohPkgContent.name.toString())) { 561 let originPath = ohPkgContentPath.toString().replace(OH_PACKAGE_JSON5, ''); 562 if (ohPkgContent.main) { 563 originPath = path.join(ohPkgContentPath.toString().replace(OH_PACKAGE_JSON5, ''), ohPkgContent.main.toString()); 564 if (ohPkgContent.dependencies) { 565 this.getDependenciesMapping(ohPkgContent.dependencies, ohPkgContentPath, from, arkFile); 566 } else if (ohPkgContent.devDependencies) { 567 this.getDependenciesMapping(ohPkgContent.devDependencies, ohPkgContentPath, from, arkFile); 568 } else if (ohPkgContent.dynamicDependencies) { 569 // dynamicDependencies not support 570 } 571 this.addFileNode2DependencyGrap(originPath, arkFile); 572 } 573 if (!this.findFilesByPathArray(originPath, this.indexPathArray, arkFile)) { 574 logger.trace(originPath + 'module mapperInfo is not found!'); 575 } 576 } 577 } 578 579 private getDependenciesMapping(dependencies: object, ohPkgContentPath: string, from: string, arkFile: ArkFile): void { 580 for (let [moduleName, modulePath] of Object.entries(dependencies)) { 581 logger.debug('dependencies:' + moduleName); 582 if (modulePath.startsWith('file:')) { 583 modulePath = modulePath.replace(/^file:/, ''); 584 } 585 const innerOhpackagePath = path.join(ohPkgContentPath.replace(OH_PACKAGE_JSON5, ''), modulePath.toString(), OH_PACKAGE_JSON5); 586 if (!this.ohPkgContentMap.has(innerOhpackagePath)) { 587 const innerModuleOhPkgContent = fetchDependenciesFromFile(innerOhpackagePath); 588 this.findDependenciesByOhPkg(innerOhpackagePath, innerModuleOhPkgContent, from, arkFile); 589 } 590 } 591 } 592 593 private getOriginPath(from: string, arkFile: ArkFile): string { 594 const parentPath = /^\.{1,2}\//.test(from) ? path.dirname(arkFile.getFilePath()) : arkFile.getProjectDir(); 595 return path.resolve(parentPath, from); 596 } 597 598 private addFileNode2DependencyGrap(filePath: string, arkFile: ArkFile): void { 599 this.getDependencyFilesDeeply(filePath); 600 this.filesMap.set(arkFile.getFileSignature().toMapKey(), arkFile); 601 } 602 603 private buildSdk(sdkName: string, sdkPath: string): void { 604 const allFiles = sdkName === SdkUtils.BUILT_IN_NAME ? SdkUtils.fetchBuiltInFiles() : 605 getAllFiles(sdkPath, this.options.supportFileExts!, this.options.ignoreFileNames); 606 allFiles.forEach(file => { 607 logger.trace('=== parse sdk file:', file); 608 try { 609 const arkFile: ArkFile = new ArkFile(FileUtils.getFileLanguage(file, this.fileLanguages)); 610 arkFile.setScene(this); 611 buildArkFileFromFile(file, path.normalize(sdkPath), arkFile, sdkName); 612 ModelUtils.getAllClassesInFile(arkFile).forEach(cls => { 613 cls.getDefaultArkMethod()?.buildBody(); 614 cls.getDefaultArkMethod()?.freeBodyBuilder(); 615 }); 616 const fileSig = arkFile.getFileSignature().toMapKey(); 617 this.sdkArkFilesMap.set(fileSig, arkFile); 618 SdkUtils.buildSdkImportMap(arkFile); 619 SdkUtils.loadGlobalAPI(arkFile, this.sdkGlobalMap); 620 } catch (error) { 621 logger.error('Error parsing file:', file, error); 622 this.unhandledSdkFilePaths.push(file); 623 return; 624 } 625 }); 626 } 627 628 /** 629 * Build the scene for harmony project. It resolves the file path of the project first, and then fetches 630 * dependencies from this file. Next, build a `ModuleScene` for this project to generate {@link ArkFile}. Finally, 631 * it build bodies of all methods, generate extended classes, and add DefaultConstructors. 632 */ 633 public buildScene4HarmonyProject(): void { 634 this.buildOhPkgContentMap(); 635 this.modulePath2NameMap.forEach((value, key) => { 636 let moduleScene = new ModuleScene(this); 637 moduleScene.ModuleSceneBuilder(value, key, this.options.supportFileExts!); 638 this.moduleScenesMap.set(value, moduleScene); 639 }); 640 initModulePathMap(this.ohPkgContentMap); 641 this.buildAllMethodBody(); 642 this.addDefaultConstructors(); 643 } 644 645 private buildOhPkgContentMap(): void { 646 this.modulePath2NameMap.forEach((value, key) => { 647 const moduleOhPkgFilePath = path.resolve(key, OH_PACKAGE_JSON5); 648 if (fs.existsSync(moduleOhPkgFilePath)) { 649 const moduleOhPkgContent = fetchDependenciesFromFile(moduleOhPkgFilePath); 650 this.ohPkgContentMap.set(moduleOhPkgFilePath, moduleOhPkgContent); 651 } 652 }); 653 } 654 655 public buildModuleScene(moduleName: string, modulePath: string, supportFileExts: string[]): void { 656 if (this.moduleScenesMap.get(moduleName)) { 657 return; 658 } 659 660 // get oh-package.json5 661 const moduleOhPkgFilePath = path.resolve(this.realProjectDir, path.join(modulePath, OH_PACKAGE_JSON5)); 662 if (fs.existsSync(moduleOhPkgFilePath)) { 663 const moduleOhPkgContent = fetchDependenciesFromFile(moduleOhPkgFilePath); 664 this.ohPkgContentMap.set(moduleOhPkgFilePath, moduleOhPkgContent); 665 } else { 666 logger.warn('Module: ', moduleName, 'has no oh-package.json5.'); 667 } 668 669 // parse moduleOhPkgContent, get dependencies and build dependent module 670 const moduleOhPkgContent = this.ohPkgContentMap.get(moduleOhPkgFilePath); 671 if (moduleOhPkgContent) { 672 if (moduleOhPkgContent.dependencies instanceof Object) { 673 this.processModuleOhPkgContent(moduleOhPkgContent.dependencies, moduleOhPkgFilePath, supportFileExts); 674 } 675 } 676 677 let moduleScene = new ModuleScene(this); 678 moduleScene.ModuleSceneBuilder(moduleName, modulePath, supportFileExts); 679 this.moduleScenesMap.set(moduleName, moduleScene); 680 681 this.buildAllMethodBody(); 682 } 683 684 private processModuleOhPkgContent(dependencies: Object, moduleOhPkgFilePath: string, supportFileExts: string[]): void { 685 Object.entries(dependencies).forEach(([k, v]) => { 686 const pattern = new RegExp('^(\\.\\.\\/\|\\.\\/)'); 687 if (typeof v === 'string') { 688 let dependencyModulePath: string = ''; 689 if (pattern.test(v)) { 690 dependencyModulePath = path.join(moduleOhPkgFilePath, v); 691 } else if (v.startsWith('file:')) { 692 const dependencyFilePath = path.join(moduleOhPkgFilePath, v.replace(/^file:/, '')); 693 const dependencyOhPkgPath = getFileRecursively(path.dirname(dependencyFilePath), OH_PACKAGE_JSON5); 694 dependencyModulePath = path.dirname(dependencyOhPkgPath); 695 } 696 const dependencyModuleName = this.modulePath2NameMap.get(dependencyModulePath); 697 if (dependencyModuleName) { 698 this.buildModuleScene(dependencyModuleName, dependencyModulePath, supportFileExts); 699 } 700 } 701 }); 702 } 703 704 /** 705 * Get the absolute path of current project. 706 * @returns The real project's directiory. 707 * @example 708 * 1. get real project directory, such as: 709 ```typescript 710 let projectDir = projectScene.getRealProjectDir(); 711 ``` 712 */ 713 public getRealProjectDir(): string { 714 return this.realProjectDir; 715 } 716 717 /** 718 * Returns the **string** name of the project. 719 * @returns The name of the project. 720 */ 721 public getProjectName(): string { 722 return this.projectName; 723 } 724 725 public getProjectFiles(): string[] { 726 return this.projectFiles; 727 } 728 729 public getSdkGlobal(globalName: string): ArkExport | null { 730 return this.sdkGlobalMap.get(globalName) || null; 731 } 732 733 /** 734 * Returns the file based on its signature. 735 * If no file can be found according to the input signature, **null** will be returned. 736 * A typical {@link ArkFile} contains: file's name (i.e., its relative path), project's name, 737 * project's dir, file's signature etc. 738 * @param fileSignature - the signature of file. 739 * @returns a file defined by ArkAnalyzer. **null** will be returned if no file could be found. 740 * @example 741 * 1. get ArkFile based on file signature. 742 743 ```typescript 744 if (...) { 745 const fromSignature = new FileSignature(); 746 fromSignature.setProjectName(im.getDeclaringArkFile().getProjectName()); 747 fromSignature.setFileName(fileName); 748 return scene.getFile(fromSignature); 749 } 750 ``` 751 */ 752 public getFile(fileSignature: FileSignature): ArkFile | null { 753 if (this.projectName === fileSignature.getProjectName()) { 754 return this.filesMap.get(fileSignature.toMapKey()) || null; 755 } else { 756 return this.sdkArkFilesMap.get(fileSignature.toMapKey()) || null; 757 } 758 } 759 760 /* 761 * Returns the absolute file paths that cannot be handled currently. 762 */ 763 public getUnhandledFilePaths(): string[] { 764 return this.unhandledFilePaths; 765 } 766 767 /* 768 * Returns the absolute sdk file paths that cannot be handled currently. 769 */ 770 public getUnhandledSdkFilePaths(): string[] { 771 return this.unhandledSdkFilePaths; 772 } 773 774 public setFile(file: ArkFile): void { 775 this.filesMap.set(file.getFileSignature().toMapKey(), file); 776 } 777 778 public hasSdkFile(fileSignature: FileSignature): boolean { 779 return this.sdkArkFilesMap.has(fileSignature.toMapKey()); 780 } 781 782 /** 783 * Get files of a {@link Scene}. Generally, a project includes several ets/ts files that define the different 784 * class. We need to generate {@link ArkFile} objects from these ets/ts files. 785 * @returns The array of {@link ArkFile} from `scene.filesMap.values()`. 786 * @example 787 * 1. In inferSimpleTypes() to check arkClass and arkMethod. 788 * ```typescript 789 * public inferSimpleTypes(): void { 790 * for (let arkFile of this.getFiles()) { 791 * for (let arkClass of arkFile.getClasses()) { 792 * for (let arkMethod of arkClass.getMethods()) { 793 * // ... ...; 794 * } 795 * } 796 * } 797 * } 798 * ``` 799 * 2. To iterate each method 800 * ```typescript 801 * for (const file of this.getFiles()) { 802 * for (const cls of file.getClasses()) { 803 * for (const method of cls.getMethods()) { 804 * // ... ... 805 * } 806 * } 807 * } 808 *``` 809 */ 810 public getFiles(): ArkFile[] { 811 return Array.from(this.filesMap.values()); 812 } 813 814 public getFileLanguages(): Map<string, Language> { 815 return this.fileLanguages; 816 } 817 818 public getSdkArkFiles(): ArkFile[] { 819 return Array.from(this.sdkArkFilesMap.values()); 820 } 821 822 public getModuleSdkMap(): Map<string, Sdk[]> { 823 return this.moduleSdkMap; 824 } 825 826 public getProjectSdkMap(): Map<string, Sdk> { 827 return this.projectSdkMap; 828 } 829 830 public getNamespace(namespaceSignature: NamespaceSignature): ArkNamespace | null { 831 const isProject = this.projectName === namespaceSignature.getDeclaringFileSignature().getProjectName(); 832 let namespace; 833 if (isProject) { 834 namespace = this.namespacesMap.get(namespaceSignature.toMapKey()); 835 } 836 if (namespace) { 837 return namespace; 838 } 839 namespace = this.getNamespaceBySignature(namespaceSignature); 840 if (isProject && namespace) { 841 this.namespacesMap.set(namespaceSignature.toMapKey(), namespace); 842 } 843 return namespace || null; 844 } 845 846 private getNamespaceBySignature(signature: NamespaceSignature): ArkNamespace | null { 847 const parentSignature = signature.getDeclaringNamespaceSignature(); 848 if (parentSignature) { 849 const parentNamespace = this.getNamespaceBySignature(parentSignature); 850 return parentNamespace?.getNamespace(signature) || null; 851 } else { 852 const arkFile = this.getFile(signature.getDeclaringFileSignature()); 853 return arkFile?.getNamespace(signature) || null; 854 } 855 } 856 857 private getNamespacesMap(): Map<string, ArkNamespace> { 858 if (this.buildStage === SceneBuildStage.CLASS_DONE) { 859 for (const file of this.getFiles()) { 860 ModelUtils.getAllNamespacesInFile(file).forEach(namespace => { 861 this.namespacesMap.set(namespace.getNamespaceSignature().toMapKey(), namespace); 862 }); 863 } 864 } 865 return this.namespacesMap; 866 } 867 868 public getNamespaces(): ArkNamespace[] { 869 return Array.from(this.getNamespacesMap().values()); 870 } 871 872 /** 873 * Returns the class according to the input class signature. 874 * @param classSignature - signature of the class to be obtained. 875 * @returns A class. 876 */ 877 public getClass(classSignature: ClassSignature): ArkClass | null { 878 const isProject = this.projectName === classSignature.getDeclaringFileSignature().getProjectName(); 879 let arkClass; 880 if (isProject) { 881 arkClass = this.classesMap.get(classSignature.toMapKey()); 882 } 883 if (arkClass) { 884 return arkClass; 885 } 886 const namespaceSignature = classSignature.getDeclaringNamespaceSignature(); 887 if (namespaceSignature) { 888 arkClass = this.getNamespaceBySignature(namespaceSignature)?.getClass(classSignature) || null; 889 } else { 890 const arkFile = this.getFile(classSignature.getDeclaringFileSignature()); 891 arkClass = arkFile?.getClass(classSignature); 892 } 893 if (isProject && arkClass) { 894 this.classesMap.set(classSignature.toMapKey(), arkClass); 895 } 896 return arkClass || null; 897 } 898 899 private getClassesMap(refresh?: boolean): Map<string, ArkClass> { 900 if (refresh || this.buildStage === SceneBuildStage.METHOD_DONE) { 901 this.classesMap.clear(); 902 for (const file of this.getFiles()) { 903 for (const cls of file.getClasses()) { 904 this.classesMap.set(cls.getSignature().toMapKey(), cls); 905 } 906 } 907 for (const namespace of this.getNamespacesMap().values()) { 908 for (const cls of namespace.getClasses()) { 909 this.classesMap.set(cls.getSignature().toMapKey(), cls); 910 } 911 } 912 if (this.buildStage < SceneBuildStage.CLASS_COLLECTED) { 913 this.buildStage = SceneBuildStage.CLASS_COLLECTED; 914 } 915 } 916 return this.classesMap; 917 } 918 919 public getClasses(): ArkClass[] { 920 return Array.from(this.getClassesMap().values()); 921 } 922 923 public getMethod(methodSignature: MethodSignature, refresh?: boolean): ArkMethod | null { 924 const isProject = this.projectName === methodSignature.getDeclaringClassSignature().getDeclaringFileSignature().getProjectName(); 925 let arkMethod; 926 if (isProject) { 927 arkMethod = this.methodsMap.get(methodSignature.toMapKey()); 928 } 929 if (arkMethod) { 930 return arkMethod; 931 } 932 arkMethod = this.getClass(methodSignature.getDeclaringClassSignature())?.getMethod(methodSignature); 933 if (isProject && arkMethod) { 934 this.methodsMap.set(methodSignature.toMapKey(), arkMethod); 935 } 936 return arkMethod || null; 937 } 938 939 private getMethodsMap(refresh?: boolean): Map<string, ArkMethod> { 940 if (refresh || (this.buildStage >= SceneBuildStage.METHOD_DONE && this.buildStage < SceneBuildStage.METHOD_COLLECTED)) { 941 this.methodsMap.clear(); 942 for (const cls of this.getClassesMap(refresh).values()) { 943 for (const method of cls.getMethods(true)) { 944 this.methodsMap.set(method.getSignature().toMapKey(), method); 945 } 946 } 947 if (this.buildStage < SceneBuildStage.METHOD_COLLECTED) { 948 this.buildStage = SceneBuildStage.METHOD_COLLECTED; 949 } 950 } 951 return this.methodsMap; 952 } 953 954 /** 955 * Returns the method associated with the method signature. 956 * If no method is associated with this signature, **null** will be returned. 957 * An {@link ArkMethod} includes: 958 * - Name: the **string** name of method. 959 * - Code: the **string** code of the method. 960 * - Line: a **number** indicating the line location, initialized as -1. 961 * - Column: a **number** indicating the column location, initialized as -1. 962 * - Parameters & Types of parameters: the parameters of method and their types. 963 * - View tree: the view tree of the method. 964 * - ... 965 * 966 * @param methodSignature - the signature of method. 967 * @returns The method associated with the method signature. 968 * @example 969 * 1. get method from getMethod. 970 971 ```typescript 972 const methodSignatures = this.CHA.resolveCall(xxx, yyy); 973 for (const methodSignature of methodSignatures) { 974 const method = this.scene.getMethod(methodSignature); 975 ... ... 976 } 977 ``` 978 */ 979 public getMethods(): ArkMethod[] { 980 return Array.from(this.getMethodsMap().values()); 981 } 982 983 public addToMethodsMap(method: ArkMethod): void { 984 this.methodsMap.set(method.getSignature().toMapKey(), method); 985 } 986 987 public removeMethod(method: ArkMethod): boolean { 988 return this.methodsMap.delete(method.getSignature().toMapKey()); 989 } 990 991 public removeClass(arkClass: ArkClass): boolean { 992 return this.classesMap.delete(arkClass.getSignature().toMapKey()); 993 } 994 995 public removeNamespace(namespace: ArkNamespace): boolean { 996 return this.namespacesMap.delete(namespace.getSignature().toMapKey()); 997 } 998 999 public removeFile(file: ArkFile): boolean { 1000 return this.filesMap.delete(file.getFileSignature().toMapKey()); 1001 } 1002 1003 public hasMainMethod(): boolean { 1004 return false; 1005 } 1006 1007 //Get the set of entry points that are used to build the call graph. 1008 public getEntryPoints(): MethodSignature[] { 1009 return []; 1010 } 1011 1012 /** get values that is visible in curr scope */ 1013 public getVisibleValue(): VisibleValue { 1014 return this.visibleValue; 1015 } 1016 1017 public getOhPkgContent(): { [p: string]: unknown } { 1018 return this.ohPkgContent; 1019 } 1020 1021 public getOhPkgContentMap(): Map<string, { [p: string]: unknown }> { 1022 return this.ohPkgContentMap; 1023 } 1024 1025 public getOhPkgFilePath(): string { 1026 return this.ohPkgFilePath; 1027 } 1028 1029 public makeCallGraphCHA(entryPoints: MethodSignature[]): CallGraph { 1030 let callGraph = new CallGraph(this); 1031 let callGraphBuilder = new CallGraphBuilder(callGraph, this); 1032 callGraphBuilder.buildClassHierarchyCallGraph(entryPoints); 1033 return callGraph; 1034 } 1035 1036 public makeCallGraphRTA(entryPoints: MethodSignature[]): CallGraph { 1037 let callGraph = new CallGraph(this); 1038 let callGraphBuilder = new CallGraphBuilder(callGraph, this); 1039 callGraphBuilder.buildRapidTypeCallGraph(entryPoints); 1040 return callGraph; 1041 } 1042 1043 /** 1044 * Infer type for each non-default method. It infers the type of each field/local/reference. 1045 * For example, the statement `let b = 5;`, the type of local `b` is `NumberType`; and for the statement `let s = 1046 * 'hello';`, the type of local `s` is `StringType`. The detailed types are defined in the Type.ts file. 1047 * @example 1048 * 1. Infer the type of each class field and method field. 1049 ```typescript 1050 const scene = new Scene(); 1051 scene.buildSceneFromProjectDir(sceneConfig); 1052 scene.inferTypes(); 1053 ``` 1054 */ 1055 public inferTypes(): void { 1056 1057 this.filesMap.forEach(file => { 1058 try { 1059 IRInference.inferFile(file); 1060 } catch (error) { 1061 logger.error('Error inferring types of project file:', file.getFileSignature(), error); 1062 } 1063 }); 1064 if (this.buildStage < SceneBuildStage.TYPE_INFERRED) { 1065 this.getMethodsMap(true); 1066 this.buildStage = SceneBuildStage.TYPE_INFERRED; 1067 } 1068 SdkUtils.dispose(); 1069 } 1070 1071 /** 1072 * Iterate all assignment statements in methods, 1073 * and set the type of left operand based on the type of right operand 1074 * if the left operand is a local variable as well as an unknown. 1075 * @Deprecated 1076 * @example 1077 * 1. Infer simple type when scene building. 1078 1079 ```typescript 1080 let scene = new Scene(); 1081 scene.buildSceneFromProjectDir(config); 1082 scene.inferSimpleTypes(); 1083 ``` 1084 */ 1085 public inferSimpleTypes(): void { 1086 for (let arkFile of this.getFiles()) { 1087 for (let arkClass of arkFile.getClasses()) { 1088 for (let arkMethod of arkClass.getMethods()) { 1089 TypeInference.inferSimpleTypeInMethod(arkMethod); 1090 } 1091 } 1092 } 1093 } 1094 1095 private addNSClasses( 1096 namespaceStack: ArkNamespace[], 1097 finalNamespaces: ArkNamespace[], 1098 classMap: Map<FileSignature | NamespaceSignature, ArkClass[]>, 1099 parentMap: Map<ArkNamespace, ArkNamespace | ArkFile> 1100 ): void { 1101 while (namespaceStack.length > 0) { 1102 const ns = namespaceStack.shift()!; 1103 const nsClass: ArkClass[] = []; 1104 for (const arkClass of ns.getClasses()) { 1105 nsClass.push(arkClass); 1106 } 1107 classMap.set(ns.getNamespaceSignature(), nsClass); 1108 if (ns.getNamespaces().length === 0) { 1109 finalNamespaces.push(ns); 1110 } else { 1111 for (const nsns of ns.getNamespaces()) { 1112 namespaceStack.push(nsns); 1113 parentMap.set(nsns, ns); 1114 } 1115 } 1116 } 1117 } 1118 1119 private addNSExportedClasses( 1120 finalNamespaces: ArkNamespace[], 1121 classMap: Map<FileSignature | NamespaceSignature, ArkClass[]>, 1122 parentMap: Map<ArkNamespace, ArkNamespace | ArkFile> 1123 ): void { 1124 while (finalNamespaces.length > 0) { 1125 const finalNS = finalNamespaces.shift()!; 1126 const exportClass = []; 1127 for (const arkClass of finalNS.getClasses()) { 1128 if (arkClass.isExported()) { 1129 exportClass.push(arkClass); 1130 } 1131 } 1132 const parent = parentMap.get(finalNS)!; 1133 if (parent instanceof ArkNamespace) { 1134 classMap.get(parent.getNamespaceSignature())?.push(...exportClass); 1135 } else if (parent instanceof ArkFile) { 1136 classMap.get(parent.getFileSignature())?.push(...exportClass); 1137 } 1138 let p = finalNS; 1139 while (!(parentMap.get(p) instanceof ArkFile) && p.isExported()) { 1140 const grandParent = parentMap.get(parentMap.get(p)! as ArkNamespace); 1141 if (grandParent instanceof ArkNamespace) { 1142 classMap.get(grandParent.getNamespaceSignature())?.push(...exportClass); 1143 p = parentMap.get(p)! as ArkNamespace; 1144 } else if (grandParent instanceof ArkFile) { 1145 classMap.get(grandParent.getFileSignature())?.push(...exportClass); 1146 break; 1147 } 1148 } 1149 if (parent instanceof ArkNamespace && !finalNamespaces.includes(parent)) { 1150 finalNamespaces.push(parent); 1151 } 1152 } 1153 } 1154 1155 private addFileImportedClasses(file: ArkFile, classMap: Map<FileSignature | NamespaceSignature, ArkClass[]>): void { 1156 const importClasses: ArkClass[] = []; 1157 const importNameSpaces: ArkNamespace[] = []; 1158 for (const importInfo of file.getImportInfos()) { 1159 const importClass = ModelUtils.getClassInImportInfoWithName(importInfo.getImportClauseName(), file); 1160 if (importClass && !importClasses.includes(importClass)) { 1161 importClasses.push(importClass); 1162 continue; 1163 } 1164 const importNameSpace = ModelUtils.getNamespaceInImportInfoWithName(importInfo.getImportClauseName(), file); 1165 if (importNameSpace && !importNameSpaces.includes(importNameSpace)) { 1166 try { 1167 // 遗留问题:只统计了项目文件的namespace,没统计sdk文件内部的引入 1168 const importNameSpaceClasses = classMap.get(importNameSpace.getNamespaceSignature())!; 1169 importClasses.push(...importNameSpaceClasses.filter(c => !importClasses.includes(c) && c.getName() !== DEFAULT_ARK_CLASS_NAME)); 1170 } catch {} 1171 } 1172 } 1173 const fileClasses = classMap.get(file.getFileSignature())!; 1174 fileClasses.push(...importClasses.filter(c => !fileClasses.includes(c))); 1175 // 子节点加上父节点的class 1176 const namespaceStack = [...file.getNamespaces()]; 1177 for (const ns of namespaceStack) { 1178 const nsClasses = classMap.get(ns.getNamespaceSignature())!; 1179 nsClasses.push(...fileClasses.filter(c => !nsClasses.includes(c) && c.getName() !== DEFAULT_ARK_CLASS_NAME)); 1180 } 1181 while (namespaceStack.length > 0) { 1182 const ns = namespaceStack.shift()!; 1183 const nsClasses = classMap.get(ns.getNamespaceSignature())!; 1184 for (const nsns of ns.getNamespaces()) { 1185 const nsnsClasses = classMap.get(nsns.getNamespaceSignature())!; 1186 nsnsClasses.push(...nsClasses.filter(c => !nsnsClasses.includes(c) && c.getName() !== DEFAULT_ARK_CLASS_NAME)); 1187 namespaceStack.push(nsns); 1188 } 1189 } 1190 } 1191 1192 public getClassMap(): Map<FileSignature | NamespaceSignature, ArkClass[]> { 1193 const classMap: Map<FileSignature | NamespaceSignature, ArkClass[]> = new Map(); 1194 for (const file of this.getFiles()) { 1195 const fileClass: ArkClass[] = []; 1196 const namespaceStack: ArkNamespace[] = []; 1197 const parentMap: Map<ArkNamespace, ArkNamespace | ArkFile> = new Map(); 1198 const finalNamespaces: ArkNamespace[] = []; 1199 for (const arkClass of file.getClasses()) { 1200 fileClass.push(arkClass); 1201 } 1202 for (const ns of file.getNamespaces()) { 1203 namespaceStack.push(ns); 1204 parentMap.set(ns, file); 1205 } 1206 1207 classMap.set(file.getFileSignature(), fileClass); 1208 // 第一轮遍历,加上每个namespace自己的class 1209 this.addNSClasses(namespaceStack, finalNamespaces, classMap, parentMap); 1210 1211 // 第二轮遍历,父节点加上子节点的export的class 1212 this.addNSExportedClasses(finalNamespaces, classMap, parentMap); 1213 } 1214 1215 for (const file of this.getFiles()) { 1216 // 文件加上import的class,包括ns的 1217 this.addFileImportedClasses(file, classMap); 1218 } 1219 return classMap; 1220 } 1221 1222 private addNSLocals( 1223 namespaceStack: ArkNamespace[], 1224 finalNamespaces: ArkNamespace[], 1225 parentMap: Map<ArkNamespace, ArkNamespace | ArkFile>, 1226 globalVariableMap: Map<FileSignature | NamespaceSignature, Local[]> 1227 ): void { 1228 while (namespaceStack.length > 0) { 1229 const ns = namespaceStack.shift()!; 1230 const nsGlobalLocals: Local[] = []; 1231 ns 1232 .getDefaultClass() 1233 .getDefaultArkMethod()! 1234 .getBody() 1235 ?.getLocals() 1236 .forEach(local => { 1237 if (local.getDeclaringStmt() && local.getName() !== 'this' && local.getName()[0] !== '$') { 1238 nsGlobalLocals.push(local); 1239 } 1240 }); 1241 globalVariableMap.set(ns.getNamespaceSignature(), nsGlobalLocals); 1242 if (ns.getNamespaces().length === 0) { 1243 finalNamespaces.push(ns); 1244 } else { 1245 for (const nsns of ns.getNamespaces()) { 1246 namespaceStack.push(nsns); 1247 parentMap.set(nsns, ns); 1248 } 1249 } 1250 } 1251 } 1252 1253 private addNSExportedLocals( 1254 finalNamespaces: ArkNamespace[], 1255 globalVariableMap: Map<FileSignature | NamespaceSignature, Local[]>, 1256 parentMap: Map<ArkNamespace, ArkNamespace | ArkFile> 1257 ): void { 1258 while (finalNamespaces.length > 0) { 1259 const finalNS = finalNamespaces.shift()!; 1260 const exportLocal = []; 1261 for (const exportInfo of finalNS.getExportInfos()) { 1262 if (exportInfo.getExportClauseType() === ExportType.LOCAL && exportInfo.getArkExport()) { 1263 exportLocal.push(exportInfo.getArkExport() as Local); 1264 } 1265 } 1266 const parent = parentMap.get(finalNS)!; 1267 if (parent instanceof ArkNamespace) { 1268 globalVariableMap.get(parent.getNamespaceSignature())?.push(...exportLocal); 1269 } else if (parent instanceof ArkFile) { 1270 globalVariableMap.get(parent.getFileSignature())?.push(...exportLocal); 1271 } 1272 let p = finalNS; 1273 while (!(parentMap.get(p) instanceof ArkFile) && p.isExported()) { 1274 const grandParent = parentMap.get(parentMap.get(p)! as ArkNamespace); 1275 if (grandParent instanceof ArkNamespace) { 1276 globalVariableMap.get(grandParent.getNamespaceSignature())?.push(...exportLocal); 1277 p = parentMap.get(p)! as ArkNamespace; 1278 } else if (grandParent instanceof ArkFile) { 1279 globalVariableMap.get(grandParent.getFileSignature())?.push(...exportLocal); 1280 break; 1281 } 1282 } 1283 if (parent instanceof ArkNamespace && !finalNamespaces.includes(parent)) { 1284 finalNamespaces.push(parent); 1285 } 1286 } 1287 } 1288 1289 private addFileImportLocals(file: ArkFile, globalVariableMap: Map<FileSignature | NamespaceSignature, Local[]>): void { 1290 const importLocals: Local[] = []; 1291 const importNameSpaces: ArkNamespace[] = []; 1292 for (const importInfo of file.getImportInfos()) { 1293 const importLocal = ModelUtils.getLocalInImportInfoWithName(importInfo.getImportClauseName(), file); 1294 if (importLocal && !importLocals.includes(importLocal)) { 1295 importLocals.push(importLocal); 1296 } 1297 const importNameSpace = ModelUtils.getNamespaceInImportInfoWithName(importInfo.getImportClauseName(), file); 1298 if (importNameSpace && !importNameSpaces.includes(importNameSpace)) { 1299 try { 1300 // 遗留问题:只统计了项目文件,没统计sdk文件内部的引入 1301 const importNameSpaceClasses = globalVariableMap.get(importNameSpace.getNamespaceSignature())!; 1302 importLocals.push(...importNameSpaceClasses.filter(c => !importLocals.includes(c) && c.getName() !== DEFAULT_ARK_CLASS_NAME)); 1303 } catch {} 1304 } 1305 } 1306 const fileLocals = globalVariableMap.get(file.getFileSignature())!; 1307 fileLocals.push(...importLocals.filter(c => !fileLocals.includes(c))); 1308 // 子节点加上父节点的local 1309 const namespaceStack = [...file.getNamespaces()]; 1310 for (const ns of namespaceStack) { 1311 const nsLocals = globalVariableMap.get(ns.getNamespaceSignature())!; 1312 const nsLocalNameSet = new Set<string>(nsLocals.map(item => item.getName())); 1313 for (const local of fileLocals) { 1314 if (!nsLocalNameSet.has(local.getName())) { 1315 nsLocals.push(local); 1316 } 1317 } 1318 } 1319 while (namespaceStack.length > 0) { 1320 const ns = namespaceStack.shift()!; 1321 const nsLocals = globalVariableMap.get(ns.getNamespaceSignature())!; 1322 for (const nsns of ns.getNamespaces()) { 1323 this.handleNestedNSLocals(nsns, nsLocals, globalVariableMap); 1324 namespaceStack.push(nsns); 1325 } 1326 } 1327 } 1328 1329 private handleNestedNSLocals(nsns: ArkNamespace, nsLocals: Local[], globalVariableMap: Map<FileSignature | NamespaceSignature, Local[]>): void { 1330 const nsnsLocals = globalVariableMap.get(nsns.getNamespaceSignature())!; 1331 const nsnsLocalNameSet = new Set<string>(nsnsLocals.map(item => item.getName())); 1332 for (const local of nsLocals) { 1333 if (!nsnsLocalNameSet.has(local.getName())) { 1334 nsnsLocals.push(local); 1335 } 1336 } 1337 } 1338 1339 public getGlobalVariableMap(): Map<FileSignature | NamespaceSignature, Local[]> { 1340 const globalVariableMap: Map<FileSignature | NamespaceSignature, Local[]> = new Map(); 1341 for (const file of this.getFiles()) { 1342 const namespaceStack: ArkNamespace[] = []; 1343 const parentMap: Map<ArkNamespace, ArkNamespace | ArkFile> = new Map(); 1344 const finalNamespaces: ArkNamespace[] = []; 1345 const globalLocals: Local[] = []; 1346 file 1347 .getDefaultClass() 1348 ?.getDefaultArkMethod()! 1349 .getBody() 1350 ?.getLocals() 1351 .forEach(local => { 1352 if (local.getDeclaringStmt() && local.getName() !== 'this' && local.getName()[0] !== '$') { 1353 globalLocals.push(local); 1354 } 1355 }); 1356 globalVariableMap.set(file.getFileSignature(), globalLocals); 1357 for (const ns of file.getNamespaces()) { 1358 namespaceStack.push(ns); 1359 parentMap.set(ns, file); 1360 } 1361 // 第一轮遍历,加上每个namespace自己的local 1362 this.addNSLocals(namespaceStack, finalNamespaces, parentMap, globalVariableMap); 1363 1364 // 第二轮遍历,父节点加上子节点的export的local 1365 this.addNSExportedLocals(finalNamespaces, globalVariableMap, parentMap); 1366 } 1367 1368 for (const file of this.getFiles()) { 1369 // 文件加上import的local,包括ns的 1370 this.addFileImportLocals(file, globalVariableMap); 1371 } 1372 return globalVariableMap; 1373 } 1374 1375 public getStaticInitMethods(): ArkMethod[] { 1376 const staticInitMethods: ArkMethod[] = []; 1377 for (const method of Array.from(this.getMethodsMap(true).values())) { 1378 if (method.getName() === STATIC_INIT_METHOD_NAME) { 1379 staticInitMethods.push(method); 1380 } 1381 } 1382 return staticInitMethods; 1383 } 1384 1385 public buildClassDone(): boolean { 1386 return this.buildStage >= SceneBuildStage.CLASS_DONE; 1387 } 1388 1389 public getModuleScene(moduleName: string): ModuleScene | undefined { 1390 return this.moduleScenesMap.get(moduleName); 1391 } 1392 1393 public getModuleSceneMap(): Map<string, ModuleScene> { 1394 return this.moduleScenesMap; 1395 } 1396 1397 public getGlobalModule2PathMapping(): { [k: string]: string[] } | undefined { 1398 return this.globalModule2PathMapping; 1399 } 1400 1401 public getbaseUrl(): string | undefined { 1402 return this.baseUrl; 1403 } 1404} 1405 1406export class ModuleScene { 1407 private projectScene: Scene; 1408 private moduleName: string = ''; 1409 private modulePath: string = ''; 1410 private moduleFileMap: Map<string, ArkFile> = new Map(); 1411 1412 private moduleOhPkgFilePath: string = ''; 1413 private ohPkgContent: { [k: string]: unknown } = {}; 1414 1415 constructor(projectScene: Scene) { 1416 this.projectScene = projectScene; 1417 } 1418 1419 public ModuleSceneBuilder(moduleName: string, modulePath: string, supportFileExts: string[], recursively: boolean = false): void { 1420 this.moduleName = moduleName; 1421 this.modulePath = modulePath; 1422 1423 this.getModuleOhPkgFilePath(); 1424 1425 if (this.moduleOhPkgFilePath) { 1426 this.ohPkgContent = fetchDependenciesFromFile(this.moduleOhPkgFilePath); 1427 } else { 1428 logger.warn('This module has no oh-package.json5!'); 1429 } 1430 this.genArkFiles(supportFileExts); 1431 } 1432 1433 public ModuleScenePartiallyBuilder(moduleName: string, modulePath: string): void { 1434 this.moduleName = moduleName; 1435 this.modulePath = modulePath; 1436 if (this.moduleOhPkgFilePath) { 1437 this.ohPkgContent = fetchDependenciesFromFile(this.moduleOhPkgFilePath); 1438 } else { 1439 logger.warn('This module has no oh-package.json5!'); 1440 } 1441 } 1442 1443 /** 1444 * get oh-package.json5 1445 */ 1446 private getModuleOhPkgFilePath(): void { 1447 const moduleOhPkgFilePath = path.resolve(this.projectScene.getRealProjectDir(), path.join(this.modulePath, OH_PACKAGE_JSON5)); 1448 if (fs.existsSync(moduleOhPkgFilePath)) { 1449 this.moduleOhPkgFilePath = moduleOhPkgFilePath; 1450 } 1451 } 1452 1453 /** 1454 * get nodule name 1455 * @returns return module name 1456 */ 1457 public getModuleName(): string { 1458 return this.moduleName; 1459 } 1460 1461 public getModulePath(): string { 1462 return this.modulePath; 1463 } 1464 1465 public getOhPkgFilePath(): string { 1466 return this.moduleOhPkgFilePath; 1467 } 1468 1469 public getOhPkgContent(): { [p: string]: unknown } { 1470 return this.ohPkgContent; 1471 } 1472 1473 public getModuleFilesMap(): Map<string, ArkFile> { 1474 return this.moduleFileMap; 1475 } 1476 1477 public addArkFile(arkFile: ArkFile): void { 1478 this.moduleFileMap.set(arkFile.getFileSignature().toMapKey(), arkFile); 1479 } 1480 1481 private genArkFiles(supportFileExts: string[]): void { 1482 getAllFiles(this.modulePath, supportFileExts, this.projectScene.getOptions().ignoreFileNames).forEach(file => { 1483 logger.trace('=== parse file:', file); 1484 try { 1485 const arkFile: ArkFile = new ArkFile(FileUtils.getFileLanguage(file, this.projectScene.getFileLanguages())); 1486 arkFile.setScene(this.projectScene); 1487 arkFile.setModuleScene(this); 1488 buildArkFileFromFile(file, this.projectScene.getRealProjectDir(), arkFile, this.projectScene.getProjectName()); 1489 this.projectScene.setFile(arkFile); 1490 } catch (error) { 1491 logger.error('Error parsing file:', file, error); 1492 this.projectScene.getUnhandledFilePaths().push(file); 1493 return; 1494 } 1495 }); 1496 } 1497} 1498