• 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 type { SourceFile } from 'typescript';
18import { SyntaxKind } from 'typescript';
19import { firstCharacterToUppercase } from '../common/commonUtils';
20import type { ClassEntity } from '../declaration-node/classDeclaration';
21import { generateCommonMethod } from './generateCommonMethod';
22import { getWarnConsole } from './generateCommonUtil';
23import { generatePropertyDeclaration } from './generatePropertyDeclaration';
24import { generateStaticFunction } from './generateStaticFunction';
25import { ImportElementEntity } from '../declaration-node/importAndExportDeclaration';
26import { HeritageClauseEntity } from '../declaration-node/heritageClauseDeclaration';
27
28interface AssemblyClassParams {
29  isSystem: boolean,
30  classEntity: ClassEntity,
31  classBody: string,
32  sourceFile: SourceFile,
33  mockApi: string,
34  isInnerMockFunction: boolean,
35  filename: string,
36  isExtend: boolean,
37  className: string,
38  extraImport?: string[],
39  importDeclarations?: ImportElementEntity[]
40}
41
42/**
43 * generate class
44 * @param rootName
45 * @param classEntity
46 * @param isSystem
47 * @param globalName
48 * @param filename
49 * @param sourceFile
50 * @param isInnerMockFunction
51 * @returns
52 */
53export function generateClassDeclaration(
54  rootName: string,
55  classEntity: ClassEntity,
56  isSystem: boolean,
57  globalName: string,
58  filename: string,
59  sourceFile: SourceFile,
60  isInnerMockFunction: boolean,
61  mockApi: string,
62  extraImport?: string[],
63  importDeclarations?: ImportElementEntity[]
64): string {
65  if (isSystem) {
66    return '';
67  }
68
69  const className = firstCharacterToUppercase(classEntity.className);
70  let classBody = '';
71  if ((classEntity.exportModifiers.includes(SyntaxKind.ExportKeyword) ||
72    classEntity.exportModifiers.includes(SyntaxKind.DeclareKeyword)) &&
73    !isInnerMockFunction) {
74    classBody += `export const ${className} = class ${className} `;
75  } else {
76    classBody += `const ${className} = class ${className} `;
77  }
78
79  const heritageClausesData = handleClassEntityHeritageClauses(rootName, classEntity, mockApi, sourceFile);
80  const isExtend = heritageClausesData.isExtend;
81  classBody = addCustomeClass(heritageClausesData, sourceFile, importDeclarations) + classBody;
82  classBody += heritageClausesData.classBody;
83  classBody = assemblyClassBody({
84    isSystem,
85    classEntity,
86    classBody,
87    className,
88    isExtend,
89    sourceFile,
90    mockApi,
91    isInnerMockFunction,
92    filename,
93    extraImport,
94    importDeclarations
95  });
96  return classBody;
97}
98
99/**
100 * generate some class
101 * @param porps
102 * @returns
103 */
104function assemblyClassBody(porps: AssemblyClassParams): string {
105  if (!porps.isSystem) {
106    porps.classBody += '{';
107    if (porps.classEntity.classConstructor.length > 1) {
108      porps.classBody += 'constructor(...arg) { ';
109    } else {
110      porps.classBody += 'constructor() { ';
111    }
112    if (porps.isExtend) {
113      porps.classBody += 'super();\n';
114    }
115    const warnCon = getWarnConsole(porps.className, 'constructor');
116    porps.classBody += porps.sourceFile.fileName.endsWith('PermissionRequestResult.d.ts') ? '' : warnCon;
117  }
118  if (porps.classEntity.classProperty.length > 0) {
119    porps.classEntity.classProperty.forEach(value => {
120      porps.classBody += generatePropertyDeclaration(porps.className, value,
121        porps.sourceFile, porps.extraImport, porps.importDeclarations) + '\n';
122    });
123  }
124
125  if (porps.classEntity.classMethod.size > 0) {
126    porps.classEntity.classMethod.forEach(value => {
127      porps.classBody += generateCommonMethod(porps.className, value, porps.sourceFile, porps.mockApi);
128    });
129  }
130
131  porps.classBody += '}\n};';
132  porps.classBody = assemblyGlobal(porps);
133
134  if (!porps.filename.startsWith('system_')) {
135    if (porps.classEntity.staticMethods.length > 0) {
136      let staticMethodBody = '';
137      porps.classEntity.staticMethods.forEach(value => {
138        staticMethodBody += generateStaticFunction(value, false, porps.sourceFile, porps.mockApi) + '\n';
139      });
140      porps.classBody += staticMethodBody;
141    }
142  }
143  if (porps.classEntity.exportModifiers.includes(SyntaxKind.DefaultKeyword)) {
144    porps.classBody += `\nexport default ${porps.className};`;
145  }
146  return porps.classBody;
147}
148
149/**
150 * generate some class
151 * @param porps
152 * @returns
153 */
154function assemblyGlobal(porps: AssemblyClassParams): string {
155  if (
156    (porps.classEntity.exportModifiers.includes(SyntaxKind.ExportKeyword) ||
157      porps.classEntity.exportModifiers.includes(SyntaxKind.DeclareKeyword)) &&
158    !porps.isInnerMockFunction
159  ) {
160    porps.classBody += `
161      if (!global.${porps.className}) {
162        global.${porps.className} = ${porps.className};\n
163      }
164    `;
165  }
166  return porps.classBody;
167}
168
169/**
170 * generate class
171 * @param rootName
172 * @param classEntity
173 * @returns
174 */
175function handleClassEntityHeritageClauses(
176  rootName: string,
177  classEntity: ClassEntity,
178  mockApi: string,
179  sourceFile: SourceFile
180): { isExtend: boolean, classBody: string } {
181  let isExtend = false;
182  let classBody = '';
183  if (classEntity.heritageClauses.length > 0) {
184    classEntity.heritageClauses.forEach(value => {
185      if (value.clauseToken === 'extends') {
186        isExtend = true;
187        classBody += `${value.clauseToken} `;
188        classBody = generateClassEntityHeritageClauses(classEntity, value, classBody, rootName, mockApi, sourceFile);
189      }
190    });
191  }
192  return {
193    isExtend,
194    classBody
195  };
196}
197
198/**
199 * generate classEntity heritageClauses
200 * @param classEntity
201 * @param value
202 * @param classBody
203 * @param rootName
204 * @param mockApi
205 * @param sourceFile
206 * @returns
207 */
208function generateClassEntityHeritageClauses(
209  classEntity: ClassEntity,
210  value: HeritageClauseEntity,
211  classBody: string,
212  rootName: string,
213  mockApi: string,
214  sourceFile: SourceFile
215): string {
216  value.types.forEach((val, index) => {
217    const extendClassName = val.trim().split('<')[0];
218    const moduleName = firstCharacterToUppercase(rootName);
219    if (val.startsWith('Array<')) {
220      val = 'Array';
221    } else {
222      if (classEntity.exportModifiers.includes(SyntaxKind.ExportKeyword) && rootName !== '') {
223        val = `mock${moduleName}().${val}`;
224      }
225    }
226    if (index !== value.types.length - 1) {
227      classBody += `${extendClassName},`;
228    } else if (val.includes('.')) {
229      const name = val.split('.')[0];
230      if (mockApi.includes(`import { mock${firstCharacterToUppercase(name)} }`) &&
231            path.basename(sourceFile.fileName).startsWith('@ohos.')) {
232        classBody += val.replace(name, `mock${firstCharacterToUppercase(name)}()`);
233      } else {
234        classBody += `${extendClassName}`;
235      }
236    } else {
237      classBody += `${extendClassName}`;
238    }
239  });
240  return classBody;
241}
242
243/**
244 * add custome class
245 * @param heritageClausesData
246 * @param sourceFile
247 * @returns
248 */
249function addCustomeClass(
250  heritageClausesData: {isExtend: boolean, classBody:string},
251  sourceFile: SourceFile,
252  importDeclarations?: ImportElementEntity[]
253): string {
254  if (!heritageClausesData.isExtend) {
255    return '';
256  }
257  if (
258    !path.resolve(sourceFile.fileName).includes(path.join('@internal', 'component', 'ets')) &&
259    path.basename(sourceFile.fileName).startsWith('@ohos.')
260  ) {
261    return '';
262  }
263  let mockClassBody = '';
264  if (!heritageClausesData.classBody.startsWith('extends ')) {
265    return mockClassBody;
266  }
267  const classArr = heritageClausesData.classBody.split('extends');
268  const className = classArr[classArr.length - 1].trim();
269  if (className === 'extends') {
270    return mockClassBody;
271  }
272  const removeNoteRegx = /\/\*[\s\S]*?\*\//g;
273  const fileContent = sourceFile.getText().replace(removeNoteRegx, '');
274  let hasImportType = false;
275  if (importDeclarations) {
276    importDeclarations.forEach(element => {
277      if (element.importElements.includes(className)) {
278        hasImportType = true;
279      }
280    });
281  }
282  const regex = new RegExp(`\\sclass\\s*${className}\\s*(<|{|extends|implements)`);
283  const results = fileContent.match(regex);
284  if (!results && !hasImportType) {
285    mockClassBody = `export class ${className} {};\n`;
286  }
287  return mockClassBody;
288}
289