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 type { SourceFile } from 'typescript'; 17import { SyntaxKind } from 'typescript'; 18import { firstCharacterToUppercase, getClassNameSet } from '../common/commonUtils'; 19import type { ReturnTypeEntity } from '../common/commonUtils'; 20import { getImportDeclarationArray } from '../declaration-node/importAndExportDeclaration'; 21import type { ImportElementEntity } from '../declaration-node/importAndExportDeclaration'; 22import type { MethodEntity } from '../declaration-node/methodDeclaration'; 23 24/** 25 * get warn console template 26 * @param interfaceNameOrClassName 27 * @param functionNameOrPropertyName 28 * @returns 29 */ 30export function getWarnConsole(interfaceNameOrClassName: string, functionNameOrPropertyName: string): string { 31 return `console.warn('The ${interfaceNameOrClassName}.${functionNameOrPropertyName} interface in the Previewer is a mocked implementation and may behave differently than on a real device.');\n`; 32} 33 34/** 35 * generate return statement; 36 * @param returnType 37 * @param sourceFile 38 * @returns 39 */ 40export function getReturnStatement(returnType: ReturnTypeEntity, sourceFile: SourceFile): string { 41 if (returnType.returnKind === SyntaxKind.TypeReference) { 42 if (returnType.returnKindName.startsWith('Promise')) { 43 return `return new Promise((resolve, reject) => { 44 resolve('[PC Preview] unknown type'); 45 })`; 46 } else if (returnType.returnKindName === 'T') { 47 return 'return \'[PC Preview] unknown type\''; 48 } else if (returnType.returnKindName === 'String') { 49 return `return ${returnType.returnKindName}(...args)`; 50 } else if (returnType.returnKindName === 'ArrayBuffer') { 51 return `return new ${returnType.returnKindName}(0)`; 52 } else if (returnType.returnKindName.startsWith('Array')) { 53 if (returnType.returnKindName.includes('<') && returnType.returnKindName.includes('>')) { 54 return `return [${generateGenericTypeToMockValue(returnType.returnKindName)}]`; 55 } else { 56 return `return new ${returnType.returnKindName}()`; 57 } 58 } else if (returnType.returnKindName.startsWith('Readonly')) { 59 return `return ${returnType.returnKindName.split('<')[1].split('>')[0]}`; 60 } else if (checkIsGenericSymbol(returnType.returnKindName)) { 61 return `return '[PC Preview] unknown iterableiterator_${returnType.returnKindName}'`; 62 } else if (returnType.returnKindName.startsWith('Uint8Array')) { 63 return `return new ${returnType.returnKindName}()`; 64 } else if (returnType.returnKindName.startsWith('IterableIterator')) { 65 if (returnType.returnKindName.includes(',')) { 66 return `let index = 0; 67 const IteratorEntriesMock = { 68 *[Symbol.iterator]() { 69 yield ['[PC Preview] unknown paramIterMock_K', '[PC Preview] unknown paramIterMock_V']; 70 }, 71 next: () => { 72 if (index < 1) { 73 const returnValue = ['[PC Previwe] unknown paramIterMock_K', '[PC Previwe] unknown paramIterMock_V']; 74 index++; 75 return { 76 value: returnValue, 77 done: false 78 }; 79 } else { 80 return { 81 done: true 82 }; 83 } 84 } 85 }; 86 return IteratorEntriesMock;`; 87 } else { 88 return `let index = 0; 89 const IteratorStringMock = { 90 *[Symbol.iterator]() { 91 yield '[PC Preview] unknown string'; 92 }, 93 next: () => { 94 if (index < 1) { 95 const returnValue = '[PC Previwe] unknown string'; 96 index++; 97 return { 98 value: returnValue, 99 done: false 100 }; 101 } else { 102 return { 103 done: true 104 }; 105 } 106 } 107 }; 108 return IteratorStringMock;`; 109 } 110 } else if (returnType.returnKindName.includes('<T>')) { 111 const tmpReturn = returnType.returnKindName.split('<')[0]; 112 if (tmpReturn.startsWith('Array')) { 113 return 'return []'; 114 } else { 115 `return new ${tmpReturn}()`; 116 } 117 } else if (returnType.returnKindName.includes('<')) { 118 return `return new ${returnType.returnKindName.split('<')[0]}()`; 119 } else { 120 if (getClassNameSet().has(returnType.returnKindName)) { 121 if (returnType.returnKindName === 'Want') { 122 return 'return mockWant().Want'; 123 } else { 124 return `return new ${returnType.returnKindName}()`; 125 } 126 } else if (propertyTypeWhiteList(returnType.returnKindName) === returnType.returnKindName) { 127 return `return ${getTheRealReferenceFromImport(sourceFile, returnType.returnKindName)}`; 128 } else { 129 return `return ${propertyTypeWhiteList(returnType.returnKindName)}`; 130 } 131 } 132 } else if (returnType.returnKind === SyntaxKind.UnionType) { 133 const returnNames = returnType.returnKindName.split('|'); 134 let returnName = returnNames[0]; 135 for (let i = 0; i < returnNames.length; i++) { 136 if (!returnNames[i].includes('[]') && !returnNames[i].includes('<')) { 137 returnName = returnNames[i]; 138 break; 139 } 140 } 141 if (returnName.trimStart().trimEnd() === 'void') { 142 return ''; 143 } 144 if (getClassNameSet().has(returnName)) { 145 return `return new ${returnName}()`; 146 } else { 147 return `return ${getBaseReturnValue(returnName.trimStart().trimEnd())}`; 148 } 149 } else { 150 return 'return \'[PC Preview] unknown type\''; 151 } 152 return 'return \'[PC Preview] unknown type\''; 153} 154 155/** 156 * special property whitelist 157 * @param propertyTypeName 158 * @returns 159 */ 160export function propertyTypeWhiteList(propertyTypeName: string): boolean | number | string { 161 const whiteList = ['GLboolean', 'GLuint', 'GLenum', 'GLint', 'NotificationFlags']; 162 if (whiteList.includes(propertyTypeName)) { 163 if (propertyTypeName === 'NotificationFlags' || propertyTypeName === 'GLenum') { 164 return `'[PC Preview] unknown ${propertyTypeName}'`; 165 } else if (propertyTypeName === 'GLboolean') { 166 return true; 167 } else { 168 return 0; 169 } 170 } else { 171 return propertyTypeName; 172 } 173} 174 175/** 176 * get basic return value 177 * @param value 178 * @returns 179 */ 180export function getBaseReturnValue(value: string): string | number | boolean { 181 if (value === 'string') { 182 return '\'\''; 183 } else if (value === 'number') { 184 return 0; 185 } else if (value === 'boolean') { 186 return true; 187 } else if (value === 'Object' || value === 'object') { 188 return '{}'; 189 } else if (checkIsGenericSymbol(value)) { 190 return '\'[PC Preview] unknown type\''; 191 } else if (value === 'WebGLActiveInfo') { 192 return '{size: \'[PC Preview] unknown GLint\', type: 0, name: \'[PC Preview] unknown name\'}'; 193 } else { 194 return value; 195 } 196} 197 198/** 199 * get current sourceFile import data 200 * @param sourceFile 201 * @param typeName 202 * @returns 203 */ 204export function getTheRealReferenceFromImport(sourceFile: SourceFile, typeName: string): string { 205 const importArray = getImportDeclarationArray(sourceFile); 206 let returnName = ''; 207 let isFromImport = false; 208 let isOhos = false; 209 let mockMockName = ''; 210 importArray.forEach(value => { 211 if (typeName.includes('.') && typeName.split('.')[0] === value.importElements) { 212 isFromImport = true; 213 if (value.importPath.includes('@ohos')) { 214 isOhos = true; 215 } 216 if (value.importElements.trimStart().trimEnd() === typeName.split('.')[0]) { 217 const tmpArr = value.importPath.split('.'); 218 mockMockName = tmpArr[tmpArr.length - 1].replace(/'/g, '').replace(/"/g, ''); 219 } 220 } 221 }); 222 if (isFromImport) { 223 const splitReturnKindName = typeName.split('.'); 224 let left = ''; 225 for (let i = 1; i < splitReturnKindName.length; i++) { 226 left += `.${splitReturnKindName[i]}`; 227 } 228 if (isOhos) { 229 returnName = `mock${firstCharacterToUppercase(mockMockName)}()${left}`; 230 } 231 } else { 232 returnName = getImportTypeAliasNameFromImportElements(importArray, typeName); 233 } 234 return returnName.split('<')[0]; 235} 236 237/** 238 * get return type alias, for example: {Context as _Context} return _Context 239 * @param importElementEntity 240 * @param typeName 241 * @returns 242 */ 243function getImportTypeAliasNameFromImportElements(importElementEntity: ImportElementEntity[], typeName: string): string { 244 for (let i = 0; i < importElementEntity.length; i++) { 245 if (importElementEntity[i].importElements.includes('_')) { 246 const importElements = importElementEntity[i].importElements.replace('{', '').replace('}', '').split(','); 247 for (let j = 0; j < importElements.length; j++) { 248 const element = importElements[j].trimStart().trimEnd(); 249 if (!element) { 250 continue; 251 } 252 if (`_${typeName}` === element.trim()) { 253 return `_${typeName}`; 254 } 255 if (element.includes(' as ') && `_${typeName}` === element.split('as')[1].trim()) { 256 return `_${typeName}`; 257 } 258 } 259 } 260 } 261 if (typeName === 'Want') { 262 typeName = 'mockWant().Want'; 263 } else if (typeName === 'InputMethodExtensionContext') { 264 typeName = 'mockInputMethodExtensionContext().InputMethodExtensionContext'; 265 } 266 return typeName; 267} 268 269/** 270 * check is generic symbol 271 * @param type 272 * @returns 273 */ 274export function checkIsGenericSymbol(type: string): boolean { 275 const words = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 276 return words.includes(type); 277} 278 279/** 280 * generate basic type default value 281 * @param kindName 282 * @returns 283 */ 284export function generateGenericTypeToMockValue(kindName: string): string | number | boolean { 285 const genericTypeName = kindName.split('<')[1].split('>')[0]; 286 if (genericTypeName === 'string') { 287 return '\'\''; 288 } else if (genericTypeName === 'number') { 289 return 0; 290 } else if (genericTypeName === 'boolean') { 291 return true; 292 } else if (genericTypeName === 'Object' || genericTypeName === 'object') { 293 return '{}'; 294 } else { 295 return ''; 296 } 297} 298 299const paramsTypeStart = { 300 'void': '[PC Preview] unknown type', 301 'Array': '[]', 302 'Object': '{}', 303 '{': '{}', 304 'string': '""', 305 'number': 0, 306 'boolean': false 307}; 308 309const removeCallback = (str: string) => { 310 const callbackParams = { 311 type: 'Callback', 312 value: '' 313 }; 314 if (str.startsWith('Callback')) { 315 const reg = /callback<(.*?)>/; 316 const matchValue = str.match(reg); 317 callbackParams.value = matchValue ? matchValue[1] : ''; 318 callbackParams.type = 'Callback'; 319 } else if (str.startsWith('AsyncCallback')) { 320 const reg = /AsyncCallback<(.*?)>/; 321 const matchValue = str.match(reg); 322 callbackParams.value = matchValue ? matchValue[1] : ''; 323 callbackParams.type = 'AsyncCallback'; 324 } 325 if (callbackParams.value.includes(',')) { 326 callbackParams.value = callbackParams.value.split(',')[0]; 327 } 328 return callbackParams; 329}; 330 331const isInImportType = (mockApi: string, value: string) => { 332 let hasDotFirstWorld = ''; 333 if (value.includes('.')) { 334 hasDotFirstWorld = value.split('.')[0]; 335 } 336 if (hasDotFirstWorld && mockApi.includes(`import { mock${firstLetterWord(hasDotFirstWorld)} `)) { 337 return 'isHasDotImportMock'; 338 } 339 if (hasDotFirstWorld && mockApi.includes(`import { ${firstLetterWord(hasDotFirstWorld)} `)) { 340 return 'isNoHasDotImportMock'; 341 } 342 if (mockApi.includes(`import { mock${firstLetterWord(value)} `)) { 343 return 'isImportMock'; 344 } 345 if (mockApi.includes(`import { ${value} `)) { 346 return 'isImport'; 347 } 348 return 'noImport'; 349}; 350 351const firstLetterWord = (word: string) => { 352 return word.slice(0, 1).toUpperCase() + word.slice(1); 353}; 354 355const hasDotFirstWord = (str: string) => { 356 return str.includes('.') ? str.split('.')[0] : str; 357}; 358 359/** 360 * get callback parameters data 361 * @returns data: parameters data: type: AsyncCallback or Callback 362 */ 363const setCallbackData = (mockApi: string, paramTypeString: string): {data: string, type: string} => { 364 const callbackParams = removeCallback(paramTypeString); 365 let callbackData = ''; 366 let importType = ''; 367 if (callbackParams.value) { 368 importType = isInImportType(mockApi, callbackParams.value); 369 } 370 if (importType === 'isHasDotImportMock') { 371 const upperWord = firstLetterWord(callbackParams.value); // Image.PixelMap 372 const firstWord = hasDotFirstWord(upperWord); // Image 373 callbackData = `mock${firstWord}()${upperWord.slice(firstWord.length)}`; 374 } else if (importType === 'isNoHasDotImportMock') { 375 callbackData = callbackParams.value; 376 } else if (importType === 'isImportMock') { 377 callbackData = `mock${firstLetterWord(callbackParams.value)}()`; 378 } else if (importType === 'isImport') { 379 callbackData = callbackParams.value; 380 } else if (importType === 'noImport') { 381 let paramsTypeNoHas = true; 382 if (callbackParams.value.endsWith(']')) { 383 callbackData = '[]'; 384 } else { 385 Object.keys(paramsTypeStart).forEach(item => { 386 if (callbackParams.value.startsWith(item)) { 387 callbackData = paramsTypeStart[item]; 388 paramsTypeNoHas = false; 389 } 390 }); 391 if (paramsTypeNoHas) { 392 callbackData = callbackParams.value; 393 } 394 if (callbackParams.value === 'Date') { 395 callbackData = 'new Date()'; 396 } 397 if (callbackParams.value === 'Uint8Array') { 398 callbackData = 'new Uint8Array()'; 399 } 400 if (callbackParams.value === 'T') { 401 callbackData = '[PC Preview] unknown type'; 402 } 403 } 404 } else { 405 callbackData = '[PC Preview] unknown type'; 406 } 407 return { 408 data: callbackData, 409 type: callbackParams.type 410 }; 411}; 412 413/** 414 * get callback statement 415 * @returns callback statement 416 */ 417export function getCallbackStatement(mockApi: string, paramTypeString?: string): string { 418 let outPut = `if (args && typeof args[args.length - 1] === 'function') { 419 args[args.length - 1].call(this,`; 420 const callbackError = "{'code': '','data': '','name': '','message': '','stack': ''}"; 421 let callbackDataParams = { 422 type: '', 423 data: '[PC Preview] unknown type' 424 }; 425 if (paramTypeString) { 426 callbackDataParams = setCallbackData(mockApi, paramTypeString); 427 } 428 if (callbackDataParams?.type === 'AsyncCallback') { 429 outPut += ` ${callbackError},`; 430 } 431 outPut += callbackDataParams.data === '[PC Preview] unknown type' ? ` '${callbackDataParams.data}');\n}` : ` ${callbackDataParams.data});\n}`; 432 return outPut; 433} 434 435/** 436 * get iterator template string 437 * @param methodEntity 438 * @returns 439 */ 440export function generateSymbolIterator(methodEntity: MethodEntity): string { 441 let iteratorMethod = ''; 442 if (methodEntity.returnType.returnKindName.includes('<[')) { 443 iteratorMethod += `let index = 0; 444 const IteratorMock = { 445 next: () => { 446 if (index < 1) { 447 const returnValue = ['[PC Previwe] unknown iterableiterator_k', '[PC Previwe] unknown iterableiterator_v']; 448 index++; 449 return { 450 value: returnValue, 451 done: false 452 }; 453 } else { 454 return { 455 done: true 456 }; 457 } 458 } 459 }; 460 return IteratorMock;`; 461 } else { 462 iteratorMethod += `let index = 0; 463 const IteratorMock = { 464 next: () => { 465 if (index < 1) { 466 index++; 467 return { 468 value: '[PC Preview] unknown any', 469 done: false 470 }; 471 } else { 472 return { 473 done: true 474 }; 475 } 476 } 477 }; 478 return IteratorMock;`; 479 } 480 481 return iteratorMethod; 482} 483