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).trim(); 141 } else { 142 propertyName = sourceFile.text.substring(node.pos, node.end).trim(); 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).trim(); 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).trim(); 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( 182 node: FunctionDeclaration | MethodDeclaration | MethodSignature | CallSignatureDeclaration, 183 sourceFile: SourceFile 184): ReturnTypeEntity { 185 const returnInfo = { returnKindName: '', returnKind: -1 }; 186 if (node.type !== undefined) { 187 const start = node.type.pos === undefined ? 0 : node.type.pos; 188 const end = node.type.end === undefined ? 0 : node.type.end; 189 returnInfo.returnKindName = sourceFile.text.substring(start, end).trim(); 190 returnInfo.returnKind = node.type.kind; 191 } 192 return returnInfo; 193} 194 195/** 196 * get export modifiers 197 * @param modifiers 198 * @returns 199 */ 200export function getExportKeyword(modifiers: ModifiersArray): Array<number> { 201 const modifiersArray: Array<number> = []; 202 modifiers.forEach(value => { 203 modifiersArray.push(value.kind); 204 }); 205 return modifiersArray; 206} 207 208/** 209 * 210 * @param str first letter capitalization 211 * @returns 212 */ 213export function firstCharacterToUppercase(str: string): string { 214 return str.slice(0, 1).toUpperCase() + str.slice(1); 215} 216 217/** 218 * parameters entity 219 */ 220export interface ParameterEntity { 221 paramName: string, 222 paramTypeString: string, 223 paramTypeKind: number 224} 225 226/** 227 * return type entity 228 */ 229export interface ReturnTypeEntity { 230 returnKindName: string, 231 returnKind: number 232} 233 234/** 235 * Get OpenHarmony project dir 236 * @return project dir 237 */ 238 239export function getProjectDir(): string { 240 const apiInputPath = process.argv[paramIndex]; 241 const privateInterface = path.join('vendor', 'huawei', 'interface', 'hmscore_sdk_js', 'api'); 242 const openInterface = path.join('interface', 'sdk-js', 'api'); 243 if (apiInputPath.indexOf(openInterface) > -1) { 244 return apiInputPath.replace(`${path.sep}${openInterface}`, ''); 245 } else { 246 return apiInputPath.replace(`${path.sep}${privateInterface}`, ''); 247 } 248} 249 250/** 251 * return interface api dir in OpenHarmony 252 */ 253export function getOhosInterfacesDir(): string { 254 return path.join(getProjectDir(), 'interface', 'sdk-js', 'api'); 255} 256 257/** 258 * return interface api root path 259 * @returns apiInputPath 260 */ 261export function getApiInputPath(): string { 262 return process.argv[paramIndex]; 263} 264 265/** 266 * return OpenHarmony file path dependent on by HarmonyOs 267 * @param importPath path of depend imported 268 * @param sourceFile sourceFile of current file 269 * @returns dependsFilePath 270 */ 271export function findOhosDependFile(importPath: string, sourceFile: SourceFile): string { 272 const interFaceDir = getOhosInterfacesDir(); 273 const tmpImportPath = importPath.replace(/'/g, '').replace('.d.ts', '').replace('.d.ets', ''); 274 const sourceFileDir = path.dirname(sourceFile.fileName); 275 let dependsFilePath: string; 276 if (tmpImportPath.startsWith('./')) { 277 const subIndex = 2; 278 dependsFilePath = path.join(sourceFileDir, tmpImportPath.substring(subIndex)); 279 } else if (tmpImportPath.startsWith('../')) { 280 const backSymbolList = tmpImportPath.split('/').filter(step => step === '..'); 281 dependsFilePath = [ 282 ...sourceFileDir.split(path.sep).slice(0, -backSymbolList.length), 283 ...tmpImportPath.split('/').filter(step => step !== '..') 284 ].join(path.sep); 285 } else if (tmpImportPath.startsWith('@ohos.inner.')) { 286 const pathSteps = tmpImportPath.replace(/@ohos\.inner\./g, '').split('.'); 287 for (let i = 0; i < pathSteps.length; i++) { 288 const tmpInterFaceDir = path.join(interFaceDir, ...pathSteps.slice(0, i), pathSteps.slice(i).join('.')); 289 if (fs.existsSync(tmpInterFaceDir + '.d.ts')) { 290 return tmpInterFaceDir + '.d.ts'; 291 } 292 293 if (fs.existsSync(tmpInterFaceDir + '.d.ets')) { 294 return tmpInterFaceDir + '.d.ets'; 295 } 296 } 297 } else if (tmpImportPath.startsWith('@ohos.')) { 298 dependsFilePath = path.join(getOhosInterfacesDir(), tmpImportPath); 299 } 300 301 if (fs.existsSync(dependsFilePath + '.d.ts')) { 302 return dependsFilePath + '.d.ts'; 303 } 304 305 if (fs.existsSync(dependsFilePath + '.d.ets')) { 306 return dependsFilePath + '.d.ets'; 307 } 308 309 console.warn(`Cannot find module '${importPath}'`); 310 return ''; 311} 312 313/** 314 * Determine if the file is a openHarmony interface file 315 * @param path: interface file path 316 * @returns 317 */ 318export function isOhosInterface(path: string): boolean { 319 return path.startsWith(getOhosInterfacesDir()); 320} 321 322/** 323 * reutn js-sdk root folder full path 324 * @returns 325 */ 326export function getJsSdkDir(): string { 327 let sdkJsDir = process.argv[paramIndex].split(path.sep).slice(0, -1).join(path.sep); 328 sdkJsDir += sdkJsDir.endsWith(path.sep) ? '' : path.sep; 329 return sdkJsDir; 330} 331 332/** 333 * Determine whether the object has been imported 334 * @param importDeclarations imported Declaration list in current file 335 * @param typeName Object being inspected 336 * @returns 337 */ 338export function hasBeenImported(importDeclarations: ImportElementEntity[], typeName: string): boolean { 339 if (!typeName.trim()) { 340 return true; 341 } 342 if (isFirstCharLowerCase(typeName)) { 343 return true; 344 } 345 return importDeclarations.some(importDeclaration => importDeclaration.importElements.includes(typeName)); 346} 347 348/** 349 * Determine whether the first character in a string is a lowercase letter 350 * @param str target string 351 * @returns 352 */ 353function isFirstCharLowerCase(str: string): boolean { 354 const lowerCaseFirstChar = str[0].toLowerCase(); 355 return str[0] === lowerCaseFirstChar; 356} 357 358export const specialFiles = [ 359 '@internal/component/ets/common.d.ts', 360 '@internal/component/ets/units.d.ts', 361 '@internal/component/ets/common_ts_ets_api.d.ts', 362 '@internal/component/ets/enums.d.ts', 363 '@internal/component/ets/alert_dialog.d.ts', 364 '@internal/component/ets/ability_component.d.ts', 365 '@internal/component/ets/rich_editor.d.ts', 366 '@internal/component/ets/symbolglyph.d.ts', 367]; 368