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 type { ImportElementEntity } from '../declaration-node/importAndExportDeclaration'; 26 27 28const paramIndex = 2; 29const allLegalImports = new Set<string>(); 30const fileNameList = new Set<string>(); 31const allClassSet = new Set<string>(); 32 33export const dtsFileList: Array<string> = []; 34 35/** 36 * get all legal imports 37 * @returns 38 */ 39export function getAllLegalImports(): Set<string> { 40 return allLegalImports; 41} 42 43/** 44 * get all legal imports 45 * @param element 46 */ 47export function collectAllLegalImports(element: string): void { 48 allLegalImports.add(element); 49} 50 51/** 52 * collect all mock js file path 53 * @returns 54 */ 55export function getAllFileNameList(): Set<string> { 56 return fileNameList; 57} 58 59/** 60 * collect all file name 61 */ 62export function collectAllFileName(filePath: string): void { 63 const fullFileName = path.basename(filePath); 64 let fileName = ''; 65 if (fullFileName.endsWith('d.ts')) { 66 fileName = fullFileName.split('.d.ts')[0]; 67 } else if (fullFileName.endsWith('d.ets')) { 68 fileName = fullFileName.split('.d.ets')[0]; 69 } 70 71 let outputFileName = ''; 72 if (fileName.includes('@')) { 73 outputFileName = fileName.split('@')[1].replace(/\./g, '_'); 74 } else { 75 outputFileName = fileName; 76 } 77 fileNameList.add(outputFileName); 78} 79 80/** 81 * get all class name set 82 * @returns 83 */ 84export function getClassNameSet(): Set<string> { 85 return allClassSet; 86} 87 88/** 89 * get all class declaration 90 * @param sourceFile 91 * @returns 92 */ 93export function getAllClassDeclaration(sourceFile: SourceFile): Set<string> { 94 sourceFile.forEachChild(node => { 95 if (isClassDeclaration(node)) { 96 if (node.name !== undefined) { 97 allClassSet.add(node.name.escapedText.toString()); 98 } 99 } else if (isModuleDeclaration(node)) { 100 const moduleDeclaration = node as ModuleDeclaration; 101 const moduleBody = moduleDeclaration.body; 102 if (moduleBody !== undefined && isModuleBlock(moduleBody)) { 103 moduleBody.statements.forEach(value => { 104 if (isClassDeclaration(value)) { 105 if (value.name !== undefined) { 106 allClassSet.add(firstCharacterToUppercase(value.name?.escapedText.toString())); 107 } 108 } 109 }); 110 } 111 } 112 }); 113 return allClassSet; 114} 115 116/** 117 * get keywords 118 * @param modifiers 119 * @returns 120 */ 121export function getModifiers(modifiers: ModifiersArray): Array<number> { 122 const modifiersArray: Array<number> = []; 123 modifiers.forEach(value => modifiersArray.push(value.kind)); 124 return modifiersArray; 125} 126 127/** 128 * get property name 129 * @param node property node 130 * @param sourceFile 131 * @returns 132 */ 133export function getPropertyName(node: PropertyName, sourceFile: SourceFile): string { 134 let propertyName = ''; 135 if (isIdentifier(node) || isPrivateIdentifier(node)) { 136 const newNameNode = node as Identifier; 137 propertyName = newNameNode.escapedText.toString(); 138 } else if (isComputedPropertyName(node)) { 139 const newNameNode = node as ComputedPropertyName; 140 propertyName = sourceFile.text.substring(newNameNode.expression.pos, newNameNode.expression.end).trimStart().trimEnd(); 141 } else { 142 propertyName = sourceFile.text.substring(node.pos, node.end).trimStart().trimEnd(); 143 } 144 return propertyName; 145} 146 147/** 148 * get parameter declaration 149 * @param parameter 150 * @param sourceFile 151 * @returns 152 */ 153export function getParameter(parameter: ParameterDeclaration, sourceFile: SourceFile): ParameterEntity { 154 let paramName = ''; 155 let paramTypeString = ''; 156 const paramTypeKind = parameter.type?.kind === undefined ? -1 : parameter.type.kind; 157 if (isIdentifier(parameter.name)) { 158 paramName = parameter.name.escapedText === undefined ? '' : parameter.name.escapedText.toString(); 159 } else { 160 const start = parameter.name.pos === undefined ? 0 : parameter.name.pos; 161 const end = parameter.name.end === undefined ? 0 : parameter.name.end; 162 paramName = sourceFile.text.substring(start, end).trimStart().trimEnd(); 163 } 164 165 const start = parameter.type?.pos === undefined ? 0 : parameter.type.pos; 166 const end = parameter.type?.end === undefined ? 0 : parameter.type.end; 167 paramTypeString = sourceFile.text.substring(start, end).trimStart().trimEnd(); 168 return { 169 paramName: paramName, 170 paramTypeString: paramTypeString, 171 paramTypeKind: paramTypeKind 172 }; 173} 174 175/** 176 * get method or function return info 177 * @param node 178 * @param sourceFile 179 * @returns 180 */ 181export function getFunctionAndMethodReturnInfo(node: FunctionDeclaration | MethodDeclaration | 182 MethodSignature | CallSignatureDeclaration, sourceFile: SourceFile): ReturnTypeEntity { 183 const returnInfo = { returnKindName: '', returnKind: -1 }; 184 if (node.type !== undefined) { 185 const start = node.type.pos === undefined ? 0 : node.type.pos; 186 const end = node.type.end === undefined ? 0 : node.type.end; 187 returnInfo.returnKindName = sourceFile.text.substring(start, end).trimStart().trimEnd(); 188 returnInfo.returnKind = node.type.kind; 189 } 190 return returnInfo; 191} 192 193/** 194 * get export modifiers 195 * @param modifiers 196 * @returns 197 */ 198export function getExportKeyword(modifiers: ModifiersArray): Array<number> { 199 const modifiersArray: Array<number> = []; 200 modifiers.forEach(value => { 201 modifiersArray.push(value.kind); 202 }); 203 return modifiersArray; 204} 205 206/** 207 * 208 * @param str first letter capitalization 209 * @returns 210 */ 211export function firstCharacterToUppercase(str: string): string { 212 return str.slice(0, 1).toUpperCase() + str.slice(1); 213} 214 215/** 216 * parameters entity 217 */ 218export interface ParameterEntity { 219 paramName: string, 220 paramTypeString: string, 221 paramTypeKind: number 222} 223 224/** 225 * return type entity 226 */ 227export interface ReturnTypeEntity { 228 returnKindName: string, 229 returnKind: number 230} 231 232/** 233 * Get OpenHarmony project dir 234 * @return project dir 235 */ 236 237export function getProjectDir(): string { 238 const apiInputPath = process.argv[paramIndex]; 239 const privateInterface = path.join('vendor', 'huawei', 'interface', 'hmscore_sdk_js', 'api'); 240 const openInterface = path.join('interface', 'sdk-js', 'api'); 241 if (apiInputPath.indexOf(openInterface) > -1) { 242 return apiInputPath.replace(`${path.sep}${openInterface}`, ''); 243 } else { 244 return apiInputPath.replace(`${path.sep}${privateInterface}`, ''); 245 } 246} 247 248/** 249 * return interface api dir in OpenHarmony 250 */ 251export function getOhosInterfacesDir(): string { 252 return path.join(getProjectDir(), 'interface', 'sdk-js', 'api'); 253} 254 255/** 256 * return interface api root path 257 * @returns apiInputPath 258 */ 259export function getApiInputPath(): string { 260 return process.argv[paramIndex]; 261} 262 263/** 264 * return OpenHarmony file path dependent on by HarmonyOs 265 * @param importPath path of depend imported 266 * @param sourceFile sourceFile of current file 267 * @returns dependsFilePath 268 */ 269export function findOhosDependFile(importPath: string, sourceFile: SourceFile): string { 270 const interFaceDir = getOhosInterfacesDir(); 271 const tmpImportPath = importPath.replace(/'/g, '').replace('.d.ts', '').replace('.d.ets', ''); 272 const sourceFileDir = path.dirname(sourceFile.fileName); 273 let dependsFilePath: string; 274 if (tmpImportPath.startsWith('./')) { 275 const subIndex = 2; 276 dependsFilePath = path.join(sourceFileDir, tmpImportPath.substring(subIndex)); 277 } else if (tmpImportPath.startsWith('../')) { 278 const backSymbolList = tmpImportPath.split('/').filter(step => step === '..'); 279 dependsFilePath = [ 280 ...sourceFileDir.split(path.sep).slice(0, -backSymbolList.length), 281 ...tmpImportPath.split('/').filter(step => step !== '..') 282 ].join(path.sep); 283 } else if (tmpImportPath.startsWith('@ohos.inner.')) { 284 const pathSteps = tmpImportPath.replace(/@ohos\.inner\./g, '').split('.'); 285 for (let i = 0; i < pathSteps.length; i++) { 286 const tmpInterFaceDir = path.join(interFaceDir, ...pathSteps.slice(0, i), pathSteps.slice(i).join('.')); 287 if (fs.existsSync(tmpInterFaceDir + '.d.ts')) { 288 return tmpInterFaceDir + '.d.ts'; 289 } 290 291 if (fs.existsSync(tmpInterFaceDir + '.d.ets')) { 292 return tmpInterFaceDir + '.d.ets'; 293 } 294 } 295 } else if (tmpImportPath.startsWith('@ohos.')) { 296 dependsFilePath = path.join(getOhosInterfacesDir(), tmpImportPath); 297 } 298 299 if (fs.existsSync(dependsFilePath + '.d.ts')) { 300 return dependsFilePath + '.d.ts'; 301 } 302 303 if (fs.existsSync(dependsFilePath + '.d.ets')) { 304 return dependsFilePath + '.d.ets'; 305 } 306 307 console.warn(`Cannot find module '${importPath}'`); 308 return ''; 309} 310 311/** 312 * Determine if the file is a openHarmony interface file 313 * @param path: interface file path 314 * @returns 315 */ 316export function isOhosInterface(path: string): boolean { 317 return path.startsWith(getOhosInterfacesDir()); 318} 319 320/** 321 * reutn js-sdk root folder full path 322 * @returns 323 */ 324export function getJsSdkDir(): string { 325 let sdkJsDir = process.argv[paramIndex].split(path.sep).slice(0, -1).join(path.sep); 326 sdkJsDir += sdkJsDir.endsWith(path.sep) ? '' : path.sep; 327 return sdkJsDir; 328} 329 330/** 331 * Determine whether the object has been imported 332 * @param importDeclarations imported Declaration list in current file 333 * @param typeName Object being inspected 334 * @returns 335 */ 336export function hasBeenImported(importDeclarations: ImportElementEntity[], typeName: string): boolean { 337 if (!typeName.trim()) { 338 return true; 339 } 340 if (isFirstCharLowerCase(typeName)) { 341 return true; 342 } 343 return importDeclarations.some(importDeclaration => importDeclaration.importElements.includes(typeName)); 344} 345 346/** 347 * Determine whether the first character in a string is a lowercase letter 348 * @param str target string 349 * @returns 350 */ 351function isFirstCharLowerCase(str: string): boolean { 352 const lowerCaseFirstChar = str[0].toLowerCase(); 353 return str[0] === lowerCaseFirstChar; 354} 355 356export const specialFiles = [ 357 '@internal/component/ets/common.d.ts', 358 '@internal/component/ets/units.d.ts', 359 '@internal/component/ets/common_ts_ets_api.d.ts', 360 '@internal/component/ets/enums.d.ts', 361 '@internal/component/ets/alert_dialog.d.ts', 362 '@internal/component/ets/ability_component.d.ts' 363];