1/* 2 * Copyright (c) 2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use 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 path from 'path'; 17import type { 18 CallSignatureDeclaration, ComputedPropertyName, FunctionDeclaration, Identifier, MethodDeclaration, 19 MethodSignature, ModifiersArray, ModuleDeclaration, NodeArray, ParameterDeclaration, PropertyName, SourceFile 20} from 'typescript'; 21import { 22 isClassDeclaration, isComputedPropertyName, isIdentifier, isModuleBlock, isModuleDeclaration, isPrivateIdentifier 23} from 'typescript'; 24import fs from 'fs'; 25import ts from 'typescript'; 26import type { ImportElementEntity } from '../declaration-node/importAndExportDeclaration'; 27import { collectAllKitFiles } from './kitUtils'; 28 29const paramIndex = 2; 30const allLegalImports = new Set<string>(); 31const fileNameList = new Set<string>(); 32const allClassSet = new Set<string>(); 33 34export const dtsFileList: Array<string> = []; 35 36/** 37 * get all legal imports 38 * @returns 39 */ 40export function getAllLegalImports(): Set<string> { 41 return allLegalImports; 42} 43 44/** 45 * get all legal imports 46 * @param element 47 */ 48export function collectAllLegalImports(element: string): void { 49 allLegalImports.add(element); 50} 51 52/** 53 * collect all mock js file path 54 * @returns 55 */ 56export function getAllFileNameList(): Set<string> { 57 return fileNameList; 58} 59 60/** 61 * collect all file name 62 */ 63export function collectAllFileName(filePath: string): void { 64 const fullFileName = path.basename(filePath); 65 let fileName = ''; 66 if (fullFileName.endsWith('d.ts')) { 67 fileName = fullFileName.split('.d.ts')[0]; 68 } else if (fullFileName.endsWith('d.ets')) { 69 fileName = fullFileName.split('.d.ets')[0]; 70 } 71 72 let outputFileName = ''; 73 if (fileName.includes('@')) { 74 outputFileName = fileName.split('@')[1].replace(/\./g, '_'); 75 } else { 76 outputFileName = fileName; 77 } 78 fileNameList.add(outputFileName); 79} 80 81/** 82 * get all class name set 83 * @returns 84 */ 85export function getClassNameSet(): Set<string> { 86 return allClassSet; 87} 88 89/** 90 * get all class declaration 91 * @param sourceFile 92 * @returns 93 */ 94export function getAllClassDeclaration(sourceFile: SourceFile): Set<string> { 95 sourceFile.forEachChild(node => { 96 if (isClassDeclaration(node)) { 97 if (node.name !== undefined) { 98 allClassSet.add(node.name.escapedText.toString()); 99 } 100 } else if (isModuleDeclaration(node)) { 101 const moduleDeclaration = node as ModuleDeclaration; 102 const moduleBody = moduleDeclaration.body; 103 getIsModuleDeclaration(moduleBody); 104 } 105 }); 106 return allClassSet; 107} 108 109/** 110 * get module class declaration 111 * @param moduleBody 112 * @returns 113 */ 114function getIsModuleDeclaration(moduleBody: ts.ModuleBody): void { 115 if (moduleBody !== undefined && isModuleBlock(moduleBody)) { 116 moduleBody.statements.forEach(value => { 117 if (isClassDeclaration(value) && value.name !== undefined) { 118 allClassSet.add(firstCharacterToUppercase(value.name?.escapedText.toString())); 119 } 120 return allClassSet; 121 }); 122 } 123} 124 125/** 126 * get keywords 127 * @param modifiers 128 * @returns 129 */ 130export function getModifiers(modifiers: ModifiersArray): Array<number> { 131 const modifiersArray: Array<number> = []; 132 modifiers.forEach(value => modifiersArray.push(value.kind)); 133 return modifiersArray; 134} 135 136/** 137 * get property name 138 * @param node property node 139 * @param sourceFile 140 * @returns 141 */ 142export function getPropertyName(node: PropertyName, sourceFile: SourceFile): string { 143 let propertyName = ''; 144 if (isIdentifier(node) || isPrivateIdentifier(node)) { 145 const newNameNode = node as Identifier; 146 propertyName = newNameNode.escapedText.toString(); 147 } else if (isComputedPropertyName(node)) { 148 const newNameNode = node as ComputedPropertyName; 149 propertyName = sourceFile.text.substring(newNameNode.expression.pos, newNameNode.expression.end).trim(); 150 } else { 151 propertyName = sourceFile.text.substring(node.pos, node.end).trim(); 152 } 153 return propertyName; 154} 155 156/** 157 * get parameter declaration 158 * @param parameter 159 * @param sourceFile 160 * @returns 161 */ 162export function getParameter(parameter: ParameterDeclaration, sourceFile: SourceFile): ParameterEntity { 163 let paramName = ''; 164 let paramTypeString = ''; 165 const paramTypeKind = parameter.type?.kind === undefined ? -1 : parameter.type.kind; 166 if (isIdentifier(parameter.name)) { 167 paramName = parameter.name.escapedText === undefined ? '' : parameter.name.escapedText.toString(); 168 } else { 169 const start = parameter.name.pos === undefined ? 0 : parameter.name.pos; 170 const end = parameter.name.end === undefined ? 0 : parameter.name.end; 171 paramName = sourceFile.text.substring(start, end).trim(); 172 } 173 174 const start = parameter.type?.pos === undefined ? 0 : parameter.type.pos; 175 const end = parameter.type?.end === undefined ? 0 : parameter.type.end; 176 paramTypeString = sourceFile.text.substring(start, end).trim(); 177 return { 178 paramName: paramName, 179 paramTypeString: paramTypeString, 180 paramTypeKind: paramTypeKind 181 }; 182} 183 184/** 185 * get method or function return info 186 * @param node 187 * @param sourceFile 188 * @returns 189 */ 190export function getFunctionAndMethodReturnInfo( 191 node: FunctionDeclaration | MethodDeclaration | MethodSignature | CallSignatureDeclaration, 192 sourceFile: SourceFile 193): ReturnTypeEntity { 194 const returnInfo = { returnKindName: '', returnKind: -1 }; 195 if (node.type !== undefined) { 196 const start = node.type.pos === undefined ? 0 : node.type.pos; 197 const end = node.type.end === undefined ? 0 : node.type.end; 198 returnInfo.returnKindName = sourceFile.text.substring(start, end).trim(); 199 returnInfo.returnKind = node.type.kind; 200 } 201 return returnInfo; 202} 203 204/** 205 * get export modifiers 206 * @param modifiers 207 * @returns 208 */ 209export function getExportKeyword(modifiers: ModifiersArray): Array<number> { 210 const modifiersArray: Array<number> = []; 211 modifiers.forEach(value => { 212 modifiersArray.push(value.kind); 213 }); 214 return modifiersArray; 215} 216 217/** 218 * 219 * @param str first letter capitalization 220 * @returns 221 */ 222export function firstCharacterToUppercase(str: string): string { 223 return str.slice(0, 1).toUpperCase() + str.slice(1); 224} 225 226/** 227 * parameters entity 228 */ 229export interface ParameterEntity { 230 paramName: string, 231 paramTypeString: string, 232 paramTypeKind: number 233} 234 235/** 236 * return type entity 237 */ 238export interface ReturnTypeEntity { 239 returnKindName: string, 240 returnKind: number 241} 242 243/** 244 * Get OpenHarmony project dir 245 * @return project dir 246 */ 247 248export function getProjectDir(): string { 249 const apiInputPath = process.argv[paramIndex]; 250 const privateInterface = path.join('vendor', 'huawei', 'interface', 'hmscore_sdk_js', 'api'); 251 const openInterface = path.join('interface', 'sdk-js', 'api'); 252 if (apiInputPath.indexOf(openInterface) > -1) { 253 return apiInputPath.replace(`${path.sep}${openInterface}`, ''); 254 } else { 255 return apiInputPath.replace(`${path.sep}${privateInterface}`, ''); 256 } 257} 258 259/** 260 * return interface api dir in OpenHarmony 261 */ 262export function getOhosInterfacesDir(): string { 263 return path.join(getProjectDir(), 'interface', 'sdk-js', 'api'); 264} 265 266/** 267 * return interface api root path 268 * @returns apiInputPath 269 */ 270export function getApiInputPath(): string { 271 return process.argv[paramIndex]; 272} 273 274/** 275 * return OpenHarmony file path dependent on by HarmonyOs 276 * @param importPath path of depend imported 277 * @param sourceFile sourceFile of current file 278 * @returns dependsFilePath 279 */ 280export function findOhosDependFile(importPath: string, sourceFile: SourceFile): string { 281 const interFaceDir = getOhosInterfacesDir(); 282 const tmpImportPath = importPath.replace(/'/g, '').replace('.d.ts', '').replace('.d.ets', ''); 283 const sourceFileDir = path.dirname(sourceFile.fileName); 284 let dependsFilePath: string; 285 if (tmpImportPath.startsWith('./')) { 286 const subIndex = 2; 287 dependsFilePath = path.join(sourceFileDir, tmpImportPath.substring(subIndex)); 288 } else if (tmpImportPath.startsWith('../')) { 289 const backSymbolList = tmpImportPath.split('/').filter(step => step === '..'); 290 dependsFilePath = [ 291 ...sourceFileDir.split(path.sep).slice(0, -backSymbolList.length), 292 ...tmpImportPath.split('/').filter(step => step !== '..') 293 ].join(path.sep); 294 } else if (tmpImportPath.startsWith('@ohos.inner.')) { 295 const pathSteps = tmpImportPath.replace(/@ohos\.inner\./g, '').split('.'); 296 for (let i = 0; i < pathSteps.length; i++) { 297 const tmpInterFaceDir = path.join(interFaceDir, ...pathSteps.slice(0, i), pathSteps.slice(i).join('.')); 298 if (fs.existsSync(tmpInterFaceDir + '.d.ts')) { 299 return tmpInterFaceDir + '.d.ts'; 300 } 301 302 if (fs.existsSync(tmpInterFaceDir + '.d.ets')) { 303 return tmpInterFaceDir + '.d.ets'; 304 } 305 } 306 } else if (tmpImportPath.startsWith('@ohos.')) { 307 dependsFilePath = path.join(getOhosInterfacesDir(), tmpImportPath); 308 } 309 310 if (fs.existsSync(dependsFilePath + '.d.ts')) { 311 return dependsFilePath + '.d.ts'; 312 } 313 314 if (fs.existsSync(dependsFilePath + '.d.ets')) { 315 return dependsFilePath + '.d.ets'; 316 } 317 318 console.warn(`Cannot find module '${importPath}'`); 319 return ''; 320} 321 322/** 323 * Determine if the file is a openHarmony interface file 324 * @param path: interface file path 325 * @returns 326 */ 327export function isOhosInterface(path: string): boolean { 328 return path.startsWith(getOhosInterfacesDir()); 329} 330 331/** 332 * reutn js-sdk root folder full path 333 * @returns 334 */ 335export function getJsSdkDir(): string { 336 let sdkJsDir = process.argv[paramIndex].split(path.sep).slice(0, -1).join(path.sep); 337 sdkJsDir += sdkJsDir.endsWith(path.sep) ? '' : path.sep; 338 return sdkJsDir; 339} 340 341/** 342 * Determine whether the object has been imported 343 * @param importDeclarations imported Declaration list in current file 344 * @param typeName Object being inspected 345 * @returns 346 */ 347export function hasBeenImported(importDeclarations: ImportElementEntity[], typeName: string): boolean { 348 if (!typeName.trim()) { 349 return true; 350 } 351 if (isFirstCharLowerCase(typeName)) { 352 return true; 353 } 354 return importDeclarations.some(importDeclaration => { 355 if (importDeclaration.importElements.includes(typeName) && importDeclaration.importPath.includes('./')) { 356 return true; 357 } 358 return false; 359 }); 360} 361 362/** 363 * Determine whether the first character in a string is a lowercase letter 364 * @param str target string 365 * @returns 366 */ 367function isFirstCharLowerCase(str: string): boolean { 368 const lowerCaseFirstChar = str[0].toLowerCase(); 369 return str[0] === lowerCaseFirstChar; 370} 371 372export const specialFiles = [ 373 '@internal/component/ets/common.d.ts', 374 '@internal/component/ets/units.d.ts', 375 '@internal/component/ets/common_ts_ets_api.d.ts', 376 '@internal/component/ets/enums.d.ts', 377 '@internal/component/ets/alert_dialog.d.ts', 378 '@internal/component/ets/ability_component.d.ts', 379 '@internal/component/ets/rich_editor.d.ts', 380 '@internal/component/ets/symbolglyph.d.ts', 381 '@internal/component/ets/button.d.ts', 382 '@internal/component/ets/nav_destination.d.ts', 383 '@internal/component/ets/navigation.d.ts', 384 '@internal/component/ets/text_common.d.ts', 385 '@internal/component/ets/styled_string.d.ts' 386]; 387 388export const specialType = [ 389 'Storage', 390 'File', 391 'ChildProcess', 392 'Cipher', 393 'Sensor', 394 'Authenticator' 395]; 396 397export const specialClassName = [ 398 'Want', 399 'Configuration', 400 'InputMethodExtensionContext' 401]; 402 403/** 404 * get add kit file map 405 * @param apiInputPath api input path 406 * @returns 407 */ 408export function generateKitMap(apiInputPath: string): void { 409 const kitPath = path.join(apiInputPath, '../', 'kits'); 410 if (!fs.existsSync(kitPath)) { 411 throw new Error(`${kitPath} does not exist.`); 412 } 413 collectAllKitFiles(kitPath); 414} 415 416export interface DependencyListParams { 417 dependency: Array<string>, 418 export: string 419} 420 421export interface DependencyParams { 422 [key: string]: DependencyListParams 423} 424 425// dependence on collecting files 426export const DEPENDENCY_LIST: DependencyParams = {}; 427 428/** 429 * generated depend.json 430 */ 431export function generateDependJsonFile(): void { 432 const dependInfoPath = path.join(__dirname, '../../../runtime/main/extend/systemplugin/depend.json'); 433 fs.writeFileSync(dependInfoPath, JSON.stringify(DEPENDENCY_LIST, null, 2), 'utf-8'); 434} 435 436/** 437 * generated MyComponent.js 438 * 439 * @param outDir generated file root directory 440 */ 441export function generateMyComponent(outDir: string): void { 442 fs.writeFileSync( 443 path.join(outDir, 'MyComponent.js'), 444 'class MyComponent {}\nexport { MyComponent };' 445 ); 446} 447 448// initialize all variables in the file 449export let INITVARIABLE = ''; 450 451/** 452 * set initialize variable 453 * 454 * @param value variable name 455 */ 456export function setInitVariable(value?: string): void { 457 if (value) { 458 if (!INITVARIABLE.includes(`let ${value} = {};`)) { 459 INITVARIABLE += `let ${value} = {};\n`; 460 } 461 } else { 462 INITVARIABLE = ''; 463 } 464} 465 466/** 467 * get all initialize variable 468 * @returns string 469 */ 470export function getInitVariable(): string { 471 return INITVARIABLE; 472}