• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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