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