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