• 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';
18import Logger, { LOG_MODULE_TYPE } from './utils/logger';
19import { getAllFiles } from './utils/getAllFiles';
20import { Language } from './core/model/ArkFile';
21
22const logger = Logger.getLogger(LOG_MODULE_TYPE.ARKANALYZER, 'Config');
23
24export interface Sdk {
25    name: string;
26    path: string;
27    moduleName: string;
28}
29
30export interface TsConfig {
31    extends?: string;
32    compilerOptions?: {
33        baseUrl?: string;
34        paths?: {
35            [key: string]: string[];
36        };
37    };
38}
39
40export type SceneOptionsValue = string | number | boolean | (string | number)[] | string[] | null | undefined;
41export interface SceneOptions {
42    supportFileExts?: string[];
43    ignoreFileNames?: string[];
44    enableLeadingComments?: boolean;
45    enableTrailingComments?: boolean;
46    enableBuiltIn?: boolean;
47    tsconfig?: string;
48    isScanAbc?: boolean;
49    sdkGlobalFolders?: string[];
50    [option: string]: SceneOptionsValue;
51}
52const CONFIG_FILENAME = 'arkanalyzer.json';
53const DEFAULT_CONFIG_FILE = path.join(__dirname, '../config', CONFIG_FILENAME);
54
55export class SceneConfig {
56    private targetProjectName: string = '';
57    private targetProjectDirectory: string = '';
58
59    private etsSdkPath: string = '';
60    private sdksObj: Sdk[] = [];
61
62    private sdkFiles: string[] = [];
63    private sdkFilesMap: Map<string[], string> = new Map<string[], string>();
64
65    private projectFiles: string[] = [];
66    private fileLanguages: Map<string, Language> = new Map();
67
68    private options: SceneOptions;
69
70    constructor(options?: SceneOptions) {
71        this.options = { supportFileExts: ['.ets', '.ts'] };
72        this.loadDefaultConfig(options);
73    }
74
75    public getOptions(): SceneOptions {
76        return this.options;
77    }
78
79    /**
80     * Set the scene's config,
81     * such as  the target project's name, the used sdks and the full path.
82     * @param targetProjectName - the target project's name.
83     * @param targetProjectDirectory - the target project's directory.
84     * @param sdks - sdks used in this scene.
85     * @param fullFilePath - the full file path.
86     */
87    public buildConfig(targetProjectName: string, targetProjectDirectory: string, sdks: Sdk[], fullFilePath?: string[]): void {
88        this.targetProjectName = targetProjectName;
89        this.targetProjectDirectory = targetProjectDirectory;
90        this.projectFiles = getAllFiles(targetProjectDirectory, this.options.supportFileExts!, this.options.ignoreFileNames);
91        this.sdksObj = sdks;
92        if (fullFilePath) {
93            this.projectFiles.push(...fullFilePath);
94        }
95    }
96
97    /**
98     * Create a sceneConfig object for a specified project path and set the target project directory to the
99     * targetProjectDirectory property of the sceneConfig object.
100     * @param targetProjectDirectory - the target project directory, such as xxx/xxx/xxx, started from project
101     *     directory.
102     * @example
103     * 1. build a sceneConfig object.
104    ```typescript
105    const projectDir = 'xxx/xxx/xxx';
106    const sceneConfig: SceneConfig = new SceneConfig();
107    sceneConfig.buildFromProjectDir(projectDir);
108    ```
109     */
110    public buildFromProjectDir(targetProjectDirectory: string): void {
111        this.targetProjectDirectory = targetProjectDirectory;
112        this.targetProjectName = path.basename(targetProjectDirectory);
113        this.projectFiles = getAllFiles(targetProjectDirectory, this.options.supportFileExts!, this.options.ignoreFileNames);
114    }
115
116    public buildFromProjectFiles(
117        projectName: string,
118        projectDir: string,
119        filesAndDirectorys: string[],
120        sdks?: Sdk[],
121        languageTags?: Map<string, Language>
122    ): void {
123        if (sdks) {
124            this.sdksObj = sdks;
125        }
126        this.targetProjectDirectory = projectDir;
127        this.targetProjectName = projectName;
128        if (filesAndDirectorys.length === 0) {
129            logger.error('no files for build scene!');
130            return;
131        }
132        filesAndDirectorys.forEach(fileOrDirectory => this.processFilePaths(fileOrDirectory, projectDir));
133        languageTags?.forEach((languageTag, fileOrDirectory) => {
134            this.setLanguageTagForFiles(fileOrDirectory, projectDir, languageTag);
135        });
136    }
137
138    private processFilePaths(fileOrDirectory: string, projectDir: string): void {
139        let absoluteFilePath = '';
140        if (path.isAbsolute(fileOrDirectory)) {
141            absoluteFilePath = fileOrDirectory;
142        } else {
143            absoluteFilePath = path.join(projectDir, fileOrDirectory);
144        }
145        if (fs.statSync(absoluteFilePath).isDirectory()) {
146            getAllFiles(absoluteFilePath, this.getOptions().supportFileExts!, this.options.ignoreFileNames).forEach(filePath => {
147                if (!this.projectFiles.includes(filePath)) {
148                    this.projectFiles.push(filePath);
149                }
150            });
151        } else {
152            this.projectFiles.push(absoluteFilePath);
153        }
154    }
155
156    private setLanguageTagForFiles(fileOrDirectory: string, projectDir: string, languageTag: Language): void {
157        let absoluteFilePath = '';
158        if (path.isAbsolute(fileOrDirectory)) {
159            absoluteFilePath = fileOrDirectory;
160        } else {
161            absoluteFilePath = path.join(projectDir, fileOrDirectory);
162        }
163        if (fs.statSync(absoluteFilePath).isDirectory()) {
164            getAllFiles(absoluteFilePath, this.getOptions().supportFileExts!, this.options.ignoreFileNames).forEach(filePath => {
165                this.fileLanguages.set(filePath, languageTag);
166            });
167        } else {
168            this.fileLanguages.set(absoluteFilePath, languageTag);
169        }
170    }
171
172    public buildFromJson(configJsonPath: string): void {
173        if (fs.existsSync(configJsonPath)) {
174            let configurationsText: string;
175            try {
176                configurationsText = fs.readFileSync(configJsonPath, 'utf-8');
177            } catch (error) {
178                logger.error(`Error reading file: ${error}`);
179                return;
180            }
181
182            logger.info(configurationsText);
183            let configurations: any;
184            try {
185                configurations = JSON.parse(configurationsText);
186            } catch (error) {
187                logger.error(`Error parsing JSON: ${error}`);
188                return;
189            }
190
191            const targetProjectName: string = configurations.targetProjectName ? configurations.targetProjectName : '';
192            const targetProjectDirectory: string = configurations.targetProjectDirectory ? configurations.targetProjectDirectory : '';
193            const sdks: Sdk[] = configurations.sdks ? configurations.sdks : [];
194
195            if (configurations.options) {
196                this.options = { ...this.options, ...configurations.options };
197            }
198
199            this.buildConfig(targetProjectName, targetProjectDirectory, sdks);
200        } else {
201            logger.error(`Your configJsonPath: "${configJsonPath}" is not exist.`);
202        }
203    }
204
205    public getTargetProjectName(): string {
206        return this.targetProjectName;
207    }
208
209    public getTargetProjectDirectory(): string {
210        return this.targetProjectDirectory;
211    }
212
213    public getProjectFiles(): string[] {
214        return this.projectFiles;
215    }
216
217    public getFileLanguages(): Map<string, Language> {
218        return this.fileLanguages;
219    }
220
221    public getSdkFiles(): string[] {
222        return this.sdkFiles;
223    }
224
225    public getSdkFilesMap(): Map<string[], string> {
226        return this.sdkFilesMap;
227    }
228
229    public getEtsSdkPath(): string {
230        return this.etsSdkPath;
231    }
232
233    public getSdksObj(): Sdk[] {
234        return this.sdksObj;
235    }
236
237    private getDefaultConfigPath(): string {
238        try {
239            const moduleRoot = path.dirname(path.dirname(require.resolve('arkanalyzer')));
240            return path.join(moduleRoot, 'config', CONFIG_FILENAME);
241        } catch (e) {
242            logger.info(`Failed to resolve default config file from dependency path with error: ${e}`);
243            let configFile = DEFAULT_CONFIG_FILE;
244            if (!fs.existsSync(configFile)) {
245                logger.debug(`default config file '${DEFAULT_CONFIG_FILE}' not found.`);
246                configFile = path.join(__dirname, 'config', CONFIG_FILENAME);
247                logger.debug(`use new config file '${configFile}'.`);
248            } else {
249                logger.debug(`default config file '${DEFAULT_CONFIG_FILE}' found, use it.`);
250            }
251            return configFile;
252        }
253    }
254
255    private loadDefaultConfig(options?: SceneOptions): void {
256        const configFile = this.getDefaultConfigPath();
257        logger.debug(`try to parse config file ${configFile}`);
258        try {
259            this.options = { ...this.options, ...JSON.parse(fs.readFileSync(configFile, 'utf-8')) };
260        } catch (error) {
261            logger.error(`Failed to parse config file with error: ${error}`);
262        }
263        if (options) {
264            this.options = { ...this.options, ...options };
265        }
266    }
267}
268