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