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