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