• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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