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