• 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 path from 'path';
17import { SyntaxKind } from 'typescript';
18import type { SourceFile } from 'typescript';
19import { firstCharacterToUppercase } from '../common/commonUtils';
20import type { ModuleBlockEntity } from '../declaration-node/moduleDeclaration';
21import {
22  getDefaultExportClassDeclaration, getSourceFileFunctions,
23  getSourceFileVariableStatements
24} from '../declaration-node/sourceFileElementsAssemply';
25import { generateClassDeclaration } from './generateClassDeclaration';
26import { generateCommonFunction } from './generateCommonFunction';
27import { generateEnumDeclaration } from './generateEnumDeclaration';
28import { generateImportEqual } from './generateImportEqual';
29import { addToIndexArray } from './generateIndex';
30import { generateInterfaceDeclaration } from './generateInterfaceDeclaration';
31import { generateStaticFunction } from './generateStaticFunction';
32import { addToSystemIndexArray } from './generateSystemIndex';
33import { generateTypeAliasDeclaration } from './generateTypeAlias';
34import { generateVariableStatementDelcatation } from './generateVariableStatementDeclaration';
35import type { ImportElementEntity } from '../declaration-node/importAndExportDeclaration';
36import { ClassEntity } from '../declaration-node/classDeclaration';
37
38interface ModuleExportEntity {
39  type: string,
40  name: string
41}
42
43interface DefaultExportClassProps {
44  moduleBody: string,
45  outBody: string,
46  filename: string,
47  sourceFile: SourceFile,
48  mockApi: string
49}
50
51interface DefaultExportClassBack {
52  moduleBody: string,
53  outBody: string,
54}
55
56interface JudgmentModuleEntityProps {
57  moduleEntity: ModuleBlockEntity,
58  moduleBody: string,
59  outBody: string,
60  enumBody: string,
61  sourceFile: SourceFile,
62  mockApi: string,
63  extraImport: string[],
64  moduleName: string,
65  importDeclarations: ImportElementEntity[]
66}
67
68interface JudgmentModuleEntityBack {
69  moduleBody: string,
70  outBody: string,
71  enumBody: string
72}
73
74interface ModuleEntityLoopProps {
75  moduleEntity: ModuleBlockEntity,
76  innerOutBody: string,
77  moduleBody: string,
78  sourceFile: SourceFile,
79  mockApi: string,
80  extraImport: string[],
81  innerModuleName: string,
82  importDeclarations: ImportElementEntity[]
83}
84
85interface ModuleEntityLoopBack {
86  innerOutBody: string,
87  moduleBody: string,
88}
89
90interface ModuleEntityNextProps {
91  moduleEntity: ModuleBlockEntity,
92  innerFunctionBody: string,
93  innerModuleBody: string,
94  filename: string,
95  moduleBody: string,
96  sourceFile: SourceFile,
97  mockApi: string,
98  extraImport: string[],
99  innerModuleName: string,
100  importDeclarations: ImportElementEntity[]
101}
102
103interface ModuleEntityNextBack {
104  innerModuleName: string,
105  moduleBody: string
106}
107
108/**
109 * generate declare
110 * @param moduleEntity
111 * @param sourceFile
112 * @param filename
113 * @param extraImport
114 * @returns
115 */
116export function generateModuleDeclaration(moduleEntity: ModuleBlockEntity, sourceFile: SourceFile,
117  filename: string, mockApi: string, extraImport: string[], importDeclarations: ImportElementEntity[]): string {
118  const innerModuleBody = '';
119  const moduleName = moduleEntity.moduleName.replace(/["']/g, '');
120  let moduleBody = `export function mock${firstCharacterToUppercase(moduleName)}() {\n`;
121  let enumBody = '';
122  if (!(moduleEntity.exportModifiers.includes(SyntaxKind.DeclareKeyword) &&
123    (moduleEntity.moduleName.startsWith('"') || moduleEntity.moduleName.startsWith('\''))) &&
124    path.basename(sourceFile.fileName).startsWith('@ohos')
125  ) {
126    addToIndexArray({ fileName: filename, mockFunctionName: `mock${firstCharacterToUppercase(moduleName)}` });
127  }
128  let outBody = '';
129  const defaultExportClassBack = defaultExportClassForEach({ moduleBody, outBody, filename, sourceFile, mockApi });
130  moduleBody = defaultExportClassBack.moduleBody;
131  outBody = defaultExportClassBack.outBody;
132  const judgmentModuleEntityProps = {
133    moduleEntity,
134    moduleBody: defaultExportClassBack.moduleBody,
135    outBody: defaultExportClassBack.outBody,
136    sourceFile,
137    mockApi,
138    enumBody,
139    extraImport,
140    moduleName,
141    importDeclarations
142  };
143  const judgmentModuleEntityBack = judgmentModuleEntity(judgmentModuleEntityProps);
144  moduleBody = judgmentModuleEntityBack.moduleBody;
145  outBody = judgmentModuleEntityBack.outBody;
146  enumBody = judgmentModuleEntityBack.enumBody;
147  moduleBody = moduleEntityForEach(judgmentModuleEntityProps, innerModuleBody, filename);
148  const exports = getModuleExportElements(moduleEntity);
149  let exportString = '';
150  exports.forEach(value => {
151    if (value.type === 'module' && !value.name.startsWith("'") && !value.name.startsWith('"')) {
152      exportString += `${value.name}: mock${value.name}(),\n`;
153    } else {
154      exportString += `${value.name}: ${value.name},\n`;
155    }
156  });
157  if (exportString !== '') {
158    moduleBody += '\t' + exportString;
159  }
160  moduleBody += '\t};';
161  moduleBody += `\n\treturn ${moduleName};}\n`;
162  moduleBody += outBody;
163  moduleBody = enumBody + moduleBody;
164  return moduleBody;
165}
166
167/**
168 * judgment ModuleEntityLength
169 * @param props
170 * @param innerModuleBody
171 * @param filename
172 * @returns
173 */
174function moduleEntityForEach(props: JudgmentModuleEntityProps, innerModuleBody: string, filename: string): string {
175  let functionBody = '';
176  if (props.moduleEntity.functionDeclarations.size > 0) {
177    props.moduleEntity.functionDeclarations.forEach(value => {
178      functionBody += '\t' + generateCommonFunction(props.moduleName, value, props.sourceFile,
179        props.mockApi, false) + '\n';
180    });
181  }
182  if (props.moduleEntity.moduleDeclarations.length > 0) {
183    props.moduleEntity.moduleDeclarations.forEach(value => {
184      if (!value.moduleName.startsWith("'") && !value.moduleName.startsWith('"')) {
185        innerModuleBody += generateInnerModuleDeclaration(value, props.sourceFile, filename, props.mockApi,
186          props.extraImport, props.importDeclarations);
187      }
188    });
189  }
190  if (innerModuleBody) {
191    props.moduleBody += innerModuleBody + '\n';
192  }
193  props.moduleBody += '\t' + `const ${props.moduleName} = {`;
194  if (props.moduleEntity.variableStatements.length > 0) {
195    props.moduleEntity.variableStatements.forEach(value => {
196      value.forEach(val => {
197        props.moduleBody += generateVariableStatementDelcatation(val, false) + '\n';
198      });
199    });
200  }
201  const sourceFileFunctions = getSourceFileFunctions(props.sourceFile);
202  let sourceFileFunctionBody = '';
203  if (sourceFileFunctions.size > 0) {
204    sourceFileFunctions.forEach(value => {
205      sourceFileFunctionBody += '\n' + generateCommonFunction(props.moduleName, value,
206        props.sourceFile, props.mockApi, false);
207    });
208  }
209  const sourceFileVariableStatements = getSourceFileVariableStatements(props.sourceFile);
210  let sourceFileStatementBody = '';
211  if (sourceFileVariableStatements.length > 0) {
212    sourceFileVariableStatements.forEach(value => {
213      value.forEach(val => {
214        sourceFileStatementBody += '\n' + generateVariableStatementDelcatation(val, false);
215      });
216    });
217  }
218  props.moduleBody += sourceFileFunctionBody + '\n';
219  props.moduleBody += sourceFileStatementBody + '\n';
220  props.moduleBody += functionBody + '\n';
221  return props.moduleBody;
222}
223
224/**
225 * handle extra class declaration body
226 * @param value
227 * @param fileName
228 * @returns
229 */
230function handleExtraClassDeclarationBody(value: ClassEntity, fileName: string): boolean {
231  if (fileName.includes('@ohos.util.stream.d.ts') && value.className === 'Transform') {
232    return true;
233  }
234  return false;
235}
236
237/**
238 * judgment ModuleEntity
239 * @param props
240 * @returns
241 */
242function judgmentModuleEntity(props: JudgmentModuleEntityProps): JudgmentModuleEntityBack {
243  if (props.moduleEntity.typeAliasDeclarations.length > 0) {
244    props.moduleEntity.typeAliasDeclarations.forEach(value => {
245      props.outBody += generateTypeAliasDeclaration(value, true, props.sourceFile,
246        props.extraImport, props.mockApi) + '\n';
247    });
248  }
249  if (props.moduleEntity.moduleImportEquaqls.length > 0) {
250    props.moduleEntity.moduleImportEquaqls.forEach(value => {
251      props.outBody += generateImportEqual(value) + '\n';
252    });
253  }
254  if (props.moduleEntity.classDeclarations.length > 0) {
255    props.outBody = generateClassDeclarations(props);
256  }
257  if (props.moduleEntity.interfaceDeclarations.length > 0) {
258    props.moduleEntity.interfaceDeclarations.forEach(value => {
259      props.outBody += generateInterfaceDeclaration(value, props.sourceFile, false, props.mockApi,
260        props.moduleEntity.interfaceDeclarations, props.importDeclarations, props.extraImport) + ';\n';
261    });
262  }
263  if (props.moduleEntity.enumDeclarations.length > 0) {
264    props.moduleEntity.enumDeclarations.forEach(value => {
265      props.enumBody += generateEnumDeclaration(props.moduleName, value) + '\n';
266    });
267  }
268  return {
269    outBody: props.outBody,
270    moduleBody: props.moduleBody,
271    enumBody: props.enumBody
272  };
273}
274
275/**
276 * generate classDeclarations
277 * @param props
278 * @returns
279 */
280function generateClassDeclarations(props: JudgmentModuleEntityProps): string {
281  let extraOutBody = '';
282  props.moduleEntity.classDeclarations.forEach(value => {
283    const body = generateClassDeclaration(props.moduleName, value, false, '', '',
284      props.sourceFile, false, props.mockApi) + '\n';
285    if (handleExtraClassDeclarationBody(value, props.sourceFile.fileName)) {
286      extraOutBody = body;
287    } else {
288      props.outBody += body;
289    }
290  });
291  props.outBody += extraOutBody;
292  return props.outBody;
293}
294
295/**
296 * defaultExportClass ForEach
297 * @param props
298 * @returns
299 */
300function defaultExportClassForEach(props: DefaultExportClassProps): DefaultExportClassBack {
301  const defaultExportClass = getDefaultExportClassDeclaration(props.sourceFile);
302
303  if (defaultExportClass.length > 0) {
304    defaultExportClass.forEach(value => {
305      if (value.exportModifiers.includes(SyntaxKind.DefaultKeyword) &&
306        value.exportModifiers.includes(SyntaxKind.ExportKeyword)) {
307        const moduleBodyAndOutBodyBack = getModuleBodyAndOutBody(props, value);
308        props.outBody = moduleBodyAndOutBodyBack.outBody;
309        props.moduleBody = moduleBodyAndOutBodyBack.moduleBody;
310      }
311    });
312  }
313  return {
314    outBody: props.outBody,
315    moduleBody: props.moduleBody
316  };
317}
318
319/**
320 * get ModuleBodyAndOutBody
321 * @param props
322 * @param value
323 * @returns
324 */
325function getModuleBodyAndOutBody(props: DefaultExportClassProps, value: ClassEntity): DefaultExportClassBack {
326  if (props.filename.startsWith('system_')) {
327    const mockNameArr = props.filename.split('_');
328    const mockName = mockNameArr[mockNameArr.length - 1];
329    addToSystemIndexArray({
330      filename: props.filename,
331      mockFunctionName: `mock${firstCharacterToUppercase(mockName)}`
332    });
333
334    props.moduleBody += `global.systemplugin.${mockName} = {`;
335    if (value.staticMethods.length > 0) {
336      let staticMethodBody = '';
337      value.staticMethods.forEach(val => {
338        staticMethodBody += generateStaticFunction(val, true, props.sourceFile, props.mockApi) + '\n';
339      });
340      props.moduleBody += staticMethodBody;
341    }
342    props.moduleBody += '}';
343  } else {
344    props.outBody += generateClassDeclaration('', value, false, '', props.filename,
345      props.sourceFile, false, props.mockApi);
346  }
347  return {
348    outBody: props.outBody,
349    moduleBody: props.moduleBody
350  };
351}
352
353function generateInnerModuleDeclaration(moduleEntity: ModuleBlockEntity, sourceFile: SourceFile,
354  filename: string, mockApi: string, extraImport: string[], importDeclarations: ImportElementEntity[]): string {
355  const innerModuleBody = '';
356  let innerModuleName = moduleEntity.moduleName.replace(/["']/g, '');
357  let moduleBody = `function mock${innerModuleName}() {\n`;
358  let innerOutBody = '';
359  const innerFunctionBody = '';
360  const moduleEntityLoopBack = moduleEntityLoop({
361    moduleEntity,
362    innerOutBody,
363    moduleBody,
364    sourceFile,
365    mockApi,
366    extraImport,
367    innerModuleName,
368    importDeclarations
369  });
370  innerOutBody = moduleEntityLoopBack.innerOutBody;
371  moduleBody = moduleEntityLoopBack.moduleBody;
372  const moduleEntityNextBack = moduleEntityNext({
373    moduleEntity, innerFunctionBody, innerModuleBody, filename,
374    moduleBody, sourceFile, mockApi, extraImport, innerModuleName, importDeclarations
375  });
376  innerModuleName = moduleEntityNextBack.innerModuleName;
377  moduleBody = moduleEntityNextBack.moduleBody;
378  moduleBody += '\t};';
379  moduleBody += `\n\treturn ${innerModuleName};}\n`;
380  moduleBody += innerOutBody;
381  return moduleBody;
382}
383
384/**
385 * moduleEntity judgment
386 * @param props
387 * @returns
388 */
389function moduleEntityLoop(props: ModuleEntityLoopProps): ModuleEntityLoopBack {
390  if (props.moduleEntity.typeAliasDeclarations.length) {
391    props.moduleEntity.typeAliasDeclarations.forEach(value => {
392      props.innerOutBody += generateTypeAliasDeclaration(value, true, props.sourceFile,
393        props.extraImport, props.mockApi) + '\n';
394    });
395  }
396  if (props.moduleEntity.moduleImportEquaqls.length) {
397    props.moduleEntity.moduleImportEquaqls.forEach(value => {
398      props.innerOutBody += generateImportEqual(value) + '\n';
399    });
400  }
401
402  if (props.moduleEntity.classDeclarations.length) {
403    props.moduleEntity.classDeclarations.forEach(value => {
404      if (value.exportModifiers.length && value.exportModifiers.includes(SyntaxKind.ExportKeyword)) {
405        props.innerOutBody += generateClassDeclaration(props.innerModuleName, value, false, '', '', props.sourceFile, false, props.mockApi) + '\n';
406      } else {
407        props.moduleBody += '\t' + generateClassDeclaration(props.innerModuleName, value, false, '', '', props.sourceFile, true, props.mockApi) + '\n';
408      }
409    });
410  }
411  if (props.moduleEntity.interfaceDeclarations.length) {
412    props.moduleEntity.interfaceDeclarations.forEach(value => {
413      if (value.exportModifiers.length) {
414        props.innerOutBody += generateInterfaceDeclaration(value, props.sourceFile, false, props.mockApi,
415          props.moduleEntity.interfaceDeclarations, props.importDeclarations, props.extraImport) + ';\n';
416      } else {
417        props.moduleBody += '\t' + generateInterfaceDeclaration(value, props.sourceFile, false, props.mockApi,
418          props.moduleEntity.interfaceDeclarations, props.importDeclarations, props.extraImport) + ';\n';
419      }
420    });
421  }
422  if (props.moduleEntity.enumDeclarations.length) {
423    props.moduleEntity.enumDeclarations.forEach(value => {
424      if (value.exportModifiers.length) {
425        props.innerOutBody += generateEnumDeclaration(props.innerModuleName, value) + '\n';
426      } else {
427        props.moduleBody += generateEnumDeclaration(props.innerModuleName, value);
428      }
429    });
430  }
431  return {
432    moduleBody: props.moduleBody,
433    innerOutBody: props.innerOutBody
434  };
435}
436
437/**
438 * Next moduleEntity judgment
439 * @param props
440 * @returns
441 */
442function moduleEntityNext(props: ModuleEntityNextProps): ModuleEntityNextBack {
443  if (props.moduleEntity.functionDeclarations.size) {
444    props.moduleEntity.functionDeclarations.forEach(value => {
445      props.innerFunctionBody += '\n' + generateCommonFunction(props.innerModuleName, value,
446        props.sourceFile, props.mockApi, false) + '\n';
447    });
448  }
449
450  if (props.moduleEntity.moduleDeclarations.length) {
451    props.moduleEntity.moduleDeclarations.forEach(value => {
452      if (!value.moduleName.startsWith("'") && !value.moduleName.startsWith('"')) {
453        props.innerModuleBody += generateInnerModuleDeclaration(value, props.sourceFile, props.filename,
454          props.mockApi, props.extraImport, props.importDeclarations);
455      }
456    });
457  }
458  if (props.innerModuleBody) {
459    props.moduleBody += props.innerModuleBody + '\n';
460  }
461
462  props.moduleBody += `const ${props.innerModuleName} = {\n`;
463  if (props.moduleEntity.variableStatements.length) {
464    props.moduleEntity.variableStatements.forEach(value => {
465      value.forEach(val => {
466        props.moduleBody += generateVariableStatementDelcatation(val, false) + '\n';
467      });
468    });
469  }
470
471  props.moduleBody += props.innerFunctionBody + '\n';
472
473  const exportArr = getModuleExportElements(props.moduleEntity);
474  let innerExportString = '';
475  exportArr.forEach(value => {
476    if (value.type === 'module' && !value.name.startsWith("'") && !value.name.startsWith('"')) {
477      innerExportString += `${value.name}: mock${value.name}(),\n`;
478    } else {
479      innerExportString += `${value.name}: ${value.name},\n`;
480    }
481  });
482  if (innerExportString !== '') {
483    props.moduleBody += '\t' + innerExportString;
484  }
485  return {
486    innerModuleName: props.innerModuleName,
487    moduleBody: props.moduleBody
488  };
489}
490
491/**
492 * generate inner module for declare module
493 * @param moduleEntity
494 * @returns
495 */
496function generateInnerDeclareModule(moduleEntity: ModuleBlockEntity): string {
497  const moduleName = '$' + moduleEntity.moduleName.replace(/["']/g, '');
498  let module = `\n\texport const ${moduleName} = `;
499  if (moduleEntity.exportDeclarations.length > 0) {
500    moduleEntity.exportDeclarations.forEach(value => {
501      module += value.match(/{[^{}]*}/g)[0] + '\n';
502    });
503  }
504  return module;
505}
506
507/**
508 * generate inner module
509 * @param moduleEntity
510 * @param sourceFile
511 * @param extraImport
512 * @returns
513 */
514function generateInnerModule(moduleEntity: ModuleBlockEntity, sourceFile: SourceFile, extraImport: string[], mockApi: string): string {
515  const moduleName = moduleEntity.moduleName;
516  let innerModuleBody = `const ${moduleName} = (()=> {`;
517
518  if (moduleEntity.enumDeclarations.length > 0) {
519    moduleEntity.enumDeclarations.forEach(value => {
520      innerModuleBody += generateEnumDeclaration(moduleName, value) + '\n';
521    });
522  }
523
524  if (moduleEntity.typeAliasDeclarations.length > 0) {
525    moduleEntity.typeAliasDeclarations.forEach(value => {
526      innerModuleBody += generateTypeAliasDeclaration(value, true, sourceFile, extraImport, mockApi) + '\n';
527    });
528  }
529
530  if (moduleEntity.moduleImportEquaqls.length > 0) {
531    moduleEntity.moduleImportEquaqls.forEach(value => {
532      innerModuleBody += generateImportEqual(value) + '\n';
533    });
534  }
535
536  if (moduleEntity.interfaceDeclarations.length > 0) {
537    moduleEntity.interfaceDeclarations.forEach(value => {
538      innerModuleBody += generateInterfaceDeclaration(value, sourceFile, false, '', moduleEntity.interfaceDeclarations) + '\n';
539    });
540  }
541
542  let functionBody = 'return {';
543  if (moduleEntity.functionDeclarations.size > 0) {
544    moduleEntity.functionDeclarations.forEach(value => {
545      functionBody += generateCommonFunction(moduleName, value, sourceFile, '', false) + '\n';
546    });
547  }
548
549  if (moduleEntity.variableStatements.length > 0) {
550    moduleEntity.variableStatements.forEach(value => {
551      value.forEach(val => {
552        innerModuleBody += generateVariableStatementDelcatation(val, true) + '\n';
553      });
554    });
555  }
556  innerModuleBody += functionBody + '\n';
557
558  const exports = getModuleExportElements(moduleEntity);
559  let exportString = '';
560  exports.forEach(value => {
561    exportString += `${value.name}: ${value.name},\n`;
562  });
563  if (exportString !== '') {
564    innerModuleBody += '\t' + exportString;
565  }
566  innerModuleBody += '\t};})();';
567  return innerModuleBody;
568}
569
570/**
571 * get all export elements
572 * @param moduleEntity
573 * @returns
574 */
575function getModuleExportElements(moduleEntity: ModuleBlockEntity): Array<ModuleExportEntity> {
576  const exportElements: Array<ModuleExportEntity> = [];
577  if (moduleEntity.moduleName.startsWith('"') && moduleEntity.moduleName.endsWith('"')) {
578    return exportElements;
579  }
580  if (moduleEntity.classDeclarations.length > 0) {
581    moduleEntity.classDeclarations.forEach(value => {
582      exportElements.push({ name: firstCharacterToUppercase(value.className), type: 'class' });
583    });
584  }
585
586  if (moduleEntity.interfaceDeclarations.length > 0) {
587    moduleEntity.interfaceDeclarations.forEach(value => {
588      exportElements.push({ name: value.interfaceName, type: 'interface' });
589    });
590  }
591
592  if (moduleEntity.enumDeclarations.length > 0) {
593    moduleEntity.enumDeclarations.forEach(value => {
594      exportElements.push({ name: value.enumName, type: 'enum' });
595    });
596  }
597
598  if (moduleEntity.moduleDeclarations.length > 0) {
599    moduleEntity.moduleDeclarations.forEach(value => {
600      exportElements.push({ name: value.moduleName, type: 'module' });
601    });
602  }
603
604  if (moduleEntity.typeAliasDeclarations.length > 0) {
605    moduleEntity.typeAliasDeclarations.forEach(value => {
606      exportElements.push({ name: value.typeAliasName, type: 'type' });
607    });
608  }
609  return exportElements;
610}
611