1/* 2 * Copyright (c) 2022-2025 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 * as ts from 'typescript'; 17 18import { FaultID } from '../utils/lib/FaultId'; 19import { visitVisitResult } from './utils/ASTHelpers'; 20import { 21 ETSKeyword, 22 FINAL_CLASS, 23 JSValue, 24 ESObject, 25 KitPrefix, 26 LIMIT_DECORATOR, 27 UtilityTypes, 28 SpecificTypes, 29 BuiltInType 30} from '../utils/lib/TypeUtils'; 31 32export class Autofixer { 33 private readonly typeChecker: ts.TypeChecker; 34 private readonly context: ts.TransformationContext; 35 36 constructor(typeChecker: ts.TypeChecker, context: ts.TransformationContext) { 37 this.typeChecker = typeChecker; 38 this.context = context; 39 } 40 41 private readonly autofixes = new Map<ts.SyntaxKind, ts.Visitor[]>([ 42 [ 43 ts.SyntaxKind.VariableDeclarationList, 44 [this[FaultID.VarDeclaration].bind(this), this[FaultID.VarDeclarationAssignment].bind(this)] 45 ], 46 [ 47 ts.SyntaxKind.PropertyDeclaration, 48 [ 49 this[FaultID.PrivateIdentifier].bind(this), 50 this[FaultID.NoInitializer].bind(this), 51 this[FaultID.NoETSKeyword].bind(this), 52 this[FaultID.PropertyAccessExpression].bind(this) 53 ] 54 ], 55 [ 56 ts.SyntaxKind.SourceFile, 57 [ 58 this[FaultID.ImportAfterStatement].bind(this), 59 this[FaultID.LimitImport].bind(this), 60 this[FaultID.DuplicatedDeclaration].bind(this), 61 this[FaultID.DuplicatedEnum].bind(this), 62 this[FaultID.EnumWithMixedType].bind(this), 63 this[FaultID.LimitExtends].bind(this), 64 this[FaultID.AddDeclareToTopLevelInterfaces].bind(this) 65 ] 66 ], 67 [ts.SyntaxKind.LiteralType, [this[FaultID.NumbericLiteral].bind(this)]], 68 [ 69 ts.SyntaxKind.TypeAliasDeclaration, 70 [ 71 this[FaultID.StringTypeAlias].bind(this), 72 this[FaultID.IndexAccessType].bind(this), 73 this[FaultID.ConditionalTypes].bind(this), 74 this[FaultID.TypeQuery].bind(this), 75 this[FaultID.TypeGeneric].bind(this) 76 ] 77 ], 78 [ts.SyntaxKind.ModuleDeclaration, [this[FaultID.Module].bind(this)]], 79 [ 80 ts.SyntaxKind.ModuleBlock, 81 [ 82 this[FaultID.ExportNamespace].bind(this), 83 this[FaultID.DuplicatedDeclaration].bind(this), 84 this[FaultID.DuplicatedEnum].bind(this) 85 ] 86 ], 87 [ts.SyntaxKind.TypeOperator, [this[FaultID.KeyofType].bind(this)]], 88 [ts.SyntaxKind.TypeLiteral, [this[FaultID.TypeLiteral].bind(this)]], 89 [ts.SyntaxKind.AnyKeyword, [this[FaultID.AnyToJSValue].bind(this)]], 90 [ts.SyntaxKind.UnknownKeyword, [this[FaultID.UnknownToJSValue].bind(this)]], 91 [ 92 ts.SyntaxKind.InterfaceDeclaration, 93 [ 94 this[FaultID.NoETSKeyword].bind(this), 95 this[FaultID.CallorOptionFuncs].bind(this) 96 ] 97 ], 98 [ts.SyntaxKind.Identifier, [this[FaultID.WrapperToPrimitive].bind(this)]], 99 [ts.SyntaxKind.SymbolKeyword, [this[FaultID.SymbolToJSValue].bind(this)]], 100 [ts.SyntaxKind.IntersectionType, [this[FaultID.IntersectionTypeJSValue].bind(this)]], 101 [ 102 ts.SyntaxKind.TypeReference, 103 [ 104 this[FaultID.ObjectParametersToJSValue].bind(this), 105 this[FaultID.InstanceType].bind(this), 106 this[FaultID.NoBuiltInType].bind(this), 107 this[FaultID.ESObjectType].bind(this) 108 ] 109 ], 110 [ 111 ts.SyntaxKind.FunctionDeclaration, 112 [ 113 this[FaultID.GeneratorFunction].bind(this), 114 this[FaultID.ObjectBindingParams].bind(this), 115 this[FaultID.DefaultExport].bind(this), 116 this[FaultID.RemoveLimitDecorator].bind(this) 117 ] 118 ], 119 [ts.SyntaxKind.TypeQuery, [this[FaultID.TypeQuery].bind(this)]], 120 [ts.SyntaxKind.TypeParameter, [this[FaultID.LiteralType].bind(this)]], 121 [ 122 ts.SyntaxKind.ClassDeclaration, 123 [ 124 this[FaultID.NoPrivateMember].bind(this), 125 this[FaultID.DefaultExport].bind(this), 126 this[FaultID.NoETSKeyword].bind(this), 127 this[FaultID.RemoveLimitDecorator].bind(this), 128 this[FaultID.NoOptionalMemberFunction].bind(this) 129 ] 130 ], 131 [ts.SyntaxKind.MethodDeclaration, [this[FaultID.NoETSKeyword].bind(this)]], 132 [ts.SyntaxKind.ImportDeclaration, [this[FaultID.NoEmptyImport].bind(this)]], 133 [ts.SyntaxKind.ImportSpecifier, [this[FaultID.NoETSKeyword].bind(this)]], 134 [ts.SyntaxKind.ExportDeclaration, [this[FaultID.NoEmptyExport].bind(this)]], 135 [ts.SyntaxKind.ExportSpecifier, [this[FaultID.NoETSKeyword].bind(this)]], 136 [ts.SyntaxKind.MappedType, [this[FaultID.MappedType].bind(this)]], 137 [ts.SyntaxKind.TupleType, [this[FaultID.TupleTypeToArray].bind(this)]], 138 [ts.SyntaxKind.StructDeclaration, [this[FaultID.StructDeclaration].bind(this)]], 139 [ts.SyntaxKind.UnionType, [this[FaultID.NoVoidUnionType].bind(this)]], 140 [ts.SyntaxKind.VariableDeclaration, [this[FaultID.ConstLiteralToType].bind(this)]] 141 ]); 142 143 fixNode(node: ts.Node): ts.VisitResult<ts.Node> { 144 const autofixes = this.autofixes.get(node.kind); 145 146 if (autofixes === undefined) { 147 return node; 148 } 149 150 let result: ts.VisitResult<ts.Node> = node; 151 152 for (const autofix of autofixes) { 153 result = visitVisitResult(result, autofix); 154 } 155 156 return result; 157 } 158 159 /** 160 * Rule: `arkts-no-var` 161 */ 162 private [FaultID.VarDeclaration](node: ts.Node): ts.VisitResult<ts.Node> { 163 /** 164 * Ensure that all variable declarations are using `let` or `const`. 165 */ 166 167 if (ts.isVariableDeclarationList(node)) { 168 const isLetDeclaration = node.flags & ts.NodeFlags.Let; 169 const isConstDeclaration = node.flags & ts.NodeFlags.Const; 170 171 if (!isLetDeclaration && !isConstDeclaration) { 172 const newFlags = node.flags | ts.NodeFlags.Let; 173 return this.context.factory.createVariableDeclarationList(node.declarations, newFlags); 174 } 175 } 176 177 return node; 178 } 179 180 /** 181 * Rule: `arkts-no-bigint-binaryExpression` 182 */ 183 private [FaultID.VarDeclarationAssignment](node: ts.Node): ts.VisitResult<ts.Node> { 184 /* 185 * bigint literal map to number,and binary experssion map to boolean in arkts1.2 186 */ 187 188 if (ts.isVariableDeclarationList(node)) { 189 const isLetDeclaration = node.flags & ts.NodeFlags.Let; 190 const isConstDeclaration = node.flags & ts.NodeFlags.Const; 191 192 // update boolean type declaration function 193 if (isConstDeclaration || isLetDeclaration) { 194 return transformLogicalOperators( 195 node, 196 this.context, 197 isConstDeclaration ? ts.NodeFlags.Const : ts.NodeFlags.Let 198 ); 199 } 200 } 201 202 return node; 203 } 204 205 /** 206 * Rule: `arkts-ESObject-is-Any` 207 */ 208 private [FaultID.ESObjectType](node: ts.Node): ts.VisitResult<ts.Node> { 209 /* 210 * Replace `ESObject` type with `Any` in declarations. 211 */ 212 213 if (ts.isTypeReferenceNode(node) && 214 ts.isIdentifier(node.typeName) && 215 node.typeName.escapedText === ESObject) { 216 return replaceEsObjectTypeName(node, this.context.factory); 217 } 218 219 return node; 220 } 221 222 /** 223 * Rule: `arkts-no-private-identifiers` 224 */ 225 private [FaultID.PrivateIdentifier](node: ts.Node): ts.VisitResult<ts.Node> { 226 void this; 227 228 /* 229 * Since we can access only public members of the imported dynamic class, 230 * there is no need to fix private fields, we just do not emit them 231 */ 232 233 if (ts.isPropertyDeclaration(node)) { 234 if ( 235 ts.isPrivateIdentifier(node.name) || 236 node.modifiers?.find((v) => { 237 return v.kind === ts.SyntaxKind.PrivateKeyword; 238 }) 239 ) { 240 return undefined; 241 } 242 } 243 244 return node; 245 } 246 247 /** 248 * Rule: `arkts-no-misplaced-imports` 249 */ 250 private [FaultID.ImportAfterStatement](node: ts.Node): ts.VisitResult<ts.Node> { 251 /** 252 * This algorithm is very very bad for both memory and performance. 253 * Redo later, when implementing other autofixes 254 */ 255 256 if (ts.isSourceFile(node)) { 257 const importDeclarations: ts.Statement[] = []; 258 const statements: ts.Statement[] = []; 259 260 for (const stmt of node.statements) { 261 if (ts.isImportDeclaration(stmt)) { 262 importDeclarations.push(stmt); 263 } else { 264 statements.push(stmt); 265 } 266 } 267 268 return this.context.factory.updateSourceFile(node, [...importDeclarations, ...statements]); 269 } 270 271 return node; 272 } 273 274 /** 275 * Rule: `arkts-no-limit-imports` 276 */ 277 private [FaultID.LimitImport](node: ts.Node): ts.VisitResult<ts.Node> { 278 /** 279 * Ensure the order of import statements and remove restricted import statements. 280 */ 281 282 if (ts.isSourceFile(node)) { 283 const { importDeclarations, statements } = processSourceFileStatements(node.statements, this.context); 284 return this.context.factory.updateSourceFile(node, [...importDeclarations, ...statements]); 285 } 286 287 return node; 288 } 289 290 /** 291 * Rule: `arkts-no-number-literal` 292 */ 293 private [FaultID.NumbericLiteral](node: ts.Node): ts.VisitResult<ts.Node> { 294 /** 295 * NumbericLiteral mapped to number in arkts1.2 296 */ 297 298 if (ts.isLiteralTypeNode(node)) { 299 const typeNode = node.literal; 300 if ( 301 (ts.isPrefixUnaryExpression(typeNode) && typeNode.operand.kind === ts.SyntaxKind.NumericLiteral) || 302 ts.isNumericLiteral(typeNode) 303 ) { 304 return this.context.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); 305 } 306 } 307 308 return node; 309 } 310 311 /** 312 * Rule: `arkts-no-conditional-types` 313 */ 314 private [FaultID.ConditionalTypes](node: ts.Node): ts.VisitResult<ts.Node> { 315 void this; 316 317 /** 318 * ConditionalTypes mapped to JSValue in arkts1.2 319 */ 320 321 if (ts.isTypeAliasDeclaration(node)) { 322 if (ts.isConditionalTypeNode(node.type)) { 323 const newType = ts.factory.createTypeReferenceNode(JSValue, undefined); 324 return ts.factory.createTypeAliasDeclaration(node.modifiers, node.name, node.typeParameters, newType); 325 } 326 } 327 return node; 328 } 329 330 /** 331 * Rule: `arkts-no-indexed-access-type` 332 */ 333 private [FaultID.IndexAccessType](node: ts.Node): ts.VisitResult<ts.Node> { 334 void this; 335 336 /** 337 * IndexedAccessType mapped to JSValue in arkts1.2 338 */ 339 340 if (ts.isTypeAliasDeclaration(node) && ts.isIndexedAccessTypeNode(node.type)) { 341 const type = this.typeChecker.getTypeAtLocation(node.type); 342 const newType = this.typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.NoTruncation); 343 if (newType) { 344 // Create a new type alias declaration node 345 return ts.factory.createTypeAliasDeclaration(node.modifiers, node.name, node.typeParameters, newType); 346 } 347 } 348 349 return node; 350 } 351 352 /** 353 * Rule: `arkts-no-module` 354 */ 355 private [FaultID.Module](node: ts.Node): ts.VisitResult<ts.Node> { 356 /** 357 * Module mapped to namespace in arkts1.2 358 */ 359 360 if (ts.isModuleDeclaration(node)) { 361 const newFlags = node.flags | ts.NodeFlags.Namespace; 362 return this.context.factory.createModuleDeclaration(node.modifiers, node.name, node.body, newFlags); 363 } 364 365 return node; 366 } 367 368 /** 369 * `arkts-no-type-literal` 370 */ 371 private [FaultID.TypeLiteral](node: ts.Node): ts.VisitResult<ts.Node> { 372 /** 373 * TypeLiteral mapped to JSValue in arkts1.2 374 */ 375 376 if (ts.isTypeLiteralNode(node)) { 377 const createNode = this.context.factory.createIdentifier(JSValue); 378 return this.context.factory.createTypeReferenceNode(createNode, undefined); 379 } 380 return node; 381 } 382 383 /** 384 * return keyof result in TS function to JSValue 385 */ 386 private [FaultID.KeyofType](node: ts.Node): ts.VisitResult<ts.Node> { 387 /** 388 * keyof result in TS function mapped to JSValue in arkts1.2 389 */ 390 391 if (ts.isTypeOperatorNode(node)) { 392 const createNode = this.context.factory.createIdentifier(JSValue); 393 return this.context.factory.createTypeReferenceNode(createNode, undefined); 394 } 395 return node; 396 } 397 398 /** 399 * Rule: `arkts-no-export-namespace` 400 */ 401 private [FaultID.ExportNamespace](node: ts.Node): ts.VisitResult<ts.Node> { 402 /** 403 * In ArkTS 1.2, it is required to explicitly export namespaces in declaration files. 404 */ 405 406 const statements: ts.Statement[] = []; 407 let shouldChange: boolean = true; 408 409 if (ts.isModuleBlock(node)) { 410 for (const stmt of node.statements) { 411 stmt.forEachChild((child) => { 412 if (child.kind === ts.SyntaxKind.ExportKeyword) { 413 shouldChange = false; 414 return; 415 } 416 }); 417 if (!shouldChange) { 418 // reset initial value 419 shouldChange = true; 420 statements.push(stmt); 421 } else { 422 let newDeclaration = transModuleBlockformation(stmt, this.context, stmt.kind); 423 statements.push(newDeclaration); 424 } 425 } 426 return this.context.factory.updateModuleBlock(node, [...statements]); 427 } 428 return node; 429 } 430 431 /** 432 * Rule: `arkts-no-intersection-type` 433 */ 434 private [FaultID.IntersectionTypeJSValue](node: ts.Node): ts.VisitResult<ts.Node> { 435 /* 436 * Intersection type mapped to JSValue in arkts1.2 437 */ 438 439 if (ts.isIntersectionTypeNode(node)) { 440 return this.context.factory.createTypeReferenceNode(JSValue); 441 } 442 443 return node; 444 } 445 446 /** 447 * Rule: `arkts-no-any` 448 */ 449 private [FaultID.AnyToJSValue](node: ts.Node): ts.VisitResult<ts.Node> { 450 /** 451 * Keyword 'any' mapped to JSValue in arkts1.2 452 */ 453 454 if (node.kind === ts.SyntaxKind.AnyKeyword) { 455 return this.context.factory.createTypeReferenceNode(JSValue); 456 } 457 458 return node; 459 } 460 461 /** 462 * Rule: `arkts-no-unknown` 463 */ 464 private [FaultID.UnknownToJSValue](node: ts.Node): ts.VisitResult<ts.Node> { 465 /** 466 * Keyword 'unknown' mapped to JSValue in arkts1.2 467 */ 468 469 if (node.kind === ts.SyntaxKind.UnknownKeyword) { 470 return this.context.factory.createTypeReferenceNode(JSValue); 471 } 472 473 return node; 474 } 475 476 /** 477 * Rule: `arkts-no-symbol` 478 */ 479 private [FaultID.SymbolToJSValue](node: ts.Node): ts.VisitResult<ts.Node> { 480 /** 481 * Keyword 'symbol' mapped to JSValue in arkts1.2 482 */ 483 484 if (node.kind === ts.SyntaxKind.SymbolKeyword) { 485 return this.context.factory.createTypeReferenceNode(JSValue); 486 } 487 488 return node; 489 } 490 491 /** 492 * Rule: `arkts-no-duplicated-declaration` 493 */ 494 private [FaultID.DuplicatedDeclaration](node: ts.Node): ts.VisitResult<ts.Node> { 495 /** 496 * Duplicate interface and class declarations will be merged into one. 497 */ 498 499 if (!ts.isSourceFile(node) && !ts.isModuleBlock(node)) { 500 return node; 501 } 502 const statements: ts.Statement[] = []; 503 const interfaceDeclarations: ts.InterfaceDeclaration[] = []; 504 const classDeclarations: ts.ClassDeclaration[] = []; 505 for (const stmt of node.statements) { 506 if (ts.isInterfaceDeclaration(stmt)) { 507 interfaceDeclarations.push(stmt); 508 } else if (ts.isClassDeclaration(stmt)) { 509 classDeclarations.push(stmt); 510 } else { 511 statements.push(stmt); 512 } 513 } 514 if (ts.isModuleBlock(node)) { 515 const moduleBlockNode = this.context.factory.updateModuleBlock(node, [ 516 ...statements, 517 ...findSameInterfaceAndClassList(interfaceDeclarations, this.context, ts.SyntaxKind.InterfaceDeclaration), 518 ...findSameInterfaceAndClassList(classDeclarations, this.context, ts.SyntaxKind.ClassDeclaration) 519 ]); 520 return moduleBlockNode; 521 } else { 522 return this.context.factory.updateSourceFile(node, [ 523 ...statements, 524 ...findSameInterfaceAndClassList(interfaceDeclarations, this.context, ts.SyntaxKind.InterfaceDeclaration), 525 ...findSameInterfaceAndClassList(classDeclarations, this.context, ts.SyntaxKind.ClassDeclaration) 526 ]); 527 } 528 529 return node; 530 } 531 532 /** 533 * Rule: `arkts-no-funtion-objec-JSValue` 534 */ 535 private [FaultID.ObjectParametersToJSValue](node: ts.Node): ts.VisitResult<ts.Node> { 536 void this; 537 538 /** 539 * Object parameters mapped to JSValue in arkts1.2 540 */ 541 542 if (ts.isTypeReferenceNode(node)) { 543 const typeName = node.typeName; 544 if (ts.isIdentifier(typeName)) { 545 switch (typeName.text) { 546 case 'Object': 547 case 'Function': 548 case 'Resource': 549 return ts.factory.createTypeReferenceNode(JSValue, undefined); 550 default: 551 return node; 552 } 553 } 554 } 555 556 return node; 557 } 558 559 /** 560 * Rule: `arkts-no-wrapperTypes` 561 */ 562 private [FaultID.WrapperToPrimitive](node: ts.Node): ts.VisitResult<ts.Node> { 563 /** 564 * Wrapper types mapped to primitive type in arkts1.2 565 */ 566 567 if (ts.isIdentifier(node)) { 568 if (node.text !== undefined && node.text === 'Wrapper') { 569 return ts.factory.createTypeReferenceNode(JSValue, undefined); 570 } 571 switch (node.text) { 572 case 'Number': 573 return this.context.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); 574 case 'String': 575 return this.context.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); 576 case 'Boolean': 577 return this.context.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword); 578 case 'BigInt': 579 return this.context.factory.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword); 580 default: 581 return node; 582 } 583 } 584 585 return node; 586 } 587 588 /** 589 * Rule: `arkts-no-string-type-alias` 590 */ 591 private [FaultID.StringTypeAlias](node: ts.Node): ts.VisitResult<ts.Node> { 592 void this; 593 594 /** 595 * StringTypeAlias mapped to string in arkts1.2 596 */ 597 598 if (ts.isTypeAliasDeclaration(node)) { 599 if (ts.isLiteralTypeNode(node.type)) { 600 let isStringLiteral = false; 601 node.type.forEachChild((child: ts.Node) => { 602 if (child.kind === ts.SyntaxKind.StringLiteral) { 603 isStringLiteral = true; 604 return; 605 } 606 }); 607 if (isStringLiteral) { 608 return ts.factory.createTypeAliasDeclaration( 609 node.modifiers, 610 node.name, 611 node.typeParameters, 612 ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) 613 ); 614 } 615 } 616 } 617 return node; 618 } 619 620 /** 621 * Rule: `arkts-no-generator-function` 622 */ 623 private [FaultID.GeneratorFunction](node: ts.Node): ts.VisitResult<ts.Node> { 624 void this; 625 626 /** 627 * GeneratorFunction mapped to JSValue in arkts1.2 628 */ 629 630 if (ts.isFunctionDeclaration(node) && node.type) { 631 const returnType = node.type; 632 if ( 633 ts.isTypeReferenceNode(returnType) && 634 ts.isIdentifier(returnType.typeName) && 635 returnType.typeName.text === 'Generator' 636 ) { 637 const newType = ts.factory.createTypeReferenceNode(JSValue, undefined); 638 return ts.factory.updateFunctionDeclaration( 639 node, 640 node.modifiers, 641 undefined, 642 node.name, 643 node.typeParameters, 644 node.parameters, 645 newType, 646 undefined 647 ); 648 } 649 } 650 651 return node; 652 } 653 654 /** 655 * Rule: `arkts-no-object-bind-params` 656 */ 657 private [FaultID.ObjectBindingParams](node: ts.Node): ts.VisitResult<ts.Node> { 658 /** 659 * ObjectBindingParams mapped to JSValue in arkts1.2 660 */ 661 662 if (ts.isFunctionDeclaration(node)) { 663 const parameters = node.parameters; 664 const newParamters: ts.ParameterDeclaration[] = []; 665 if (parameters !== undefined && parameters.length > 0) { 666 parameters.forEach((parameter, index) => { 667 if (ts.isObjectBindingPattern(parameter.name)) { 668 const typeNode = this.context.factory.createTypeReferenceNode( 669 this.context.factory.createIdentifier(JSValue), 670 undefined 671 ); 672 const currentParamter = this.context.factory.createParameterDeclaration( 673 undefined, 674 undefined, 675 this.context.factory.createIdentifier(`args${index}`), 676 undefined, 677 typeNode 678 ); 679 newParamters.push(currentParamter); 680 } else { 681 newParamters.push(parameter); 682 } 683 }); 684 685 return this.context.factory.updateFunctionDeclaration( 686 node, 687 node.modifiers, 688 node.asteriskToken, 689 node.name, 690 node.typeParameters, 691 newParamters, 692 node.type, 693 node.body 694 ); 695 } 696 } 697 698 return node; 699 } 700 701 /** 702 * Rule: `arkts-no-type-query` 703 */ 704 private [FaultID.TypeQuery](node: ts.Node): ts.VisitResult<ts.Node> { 705 void this; 706 707 /** 708 * TypeQuery mapped to Reflect in arkts1.2 709 */ 710 const identifiers = new Set(['Reflect', 'Promise']); 711 if (ts.isTypeQueryNode(node) && ts.isIdentifier(node.exprName)) { 712 if (identifiers.has(node.exprName.text)) { 713 return this.context.factory.createTypeReferenceNode(node.exprName.text, undefined); 714 } else { 715 return this.context.factory.createTypeReferenceNode(JSValue, undefined); 716 } 717 } 718 719 return node; 720 } 721 722 /** 723 * Rule: `arkts-no-instance-type` 724 */ 725 private [FaultID.InstanceType](node: ts.Node): ts.VisitResult<ts.Node> { 726 void this; 727 728 /** 729 * For InstanceType, convert them to JSValue 730 */ 731 if (ts.isTypeReferenceNode(node) && node.typeName !== undefined) { 732 const referenceNode = node.typeName as ts.Identifier; 733 if (referenceNode.text === 'InstanceType') { 734 return ts.factory.createTypeReferenceNode(JSValue); 735 } 736 } 737 738 return node; 739 } 740 741 /** 742 * Rule: `arkts-no-mappedtype` 743 */ 744 private [FaultID.MappedType](node: ts.Node): ts.VisitResult<ts.Node> { 745 void this; 746 747 /** 748 * For mapped types, convert them to JSValue 749 */ 750 if (ts.isMappedTypeNode(node)) { 751 return ts.factory.createTypeReferenceNode(JSValue); 752 } 753 754 return node; 755 } 756 757 /** 758 * Rule: `arkts-no-literal-type` 759 */ 760 private [FaultID.LiteralType](node: ts.Node): ts.VisitResult<ts.Node> { 761 /** 762 * Union literal type mapped to JSValue in arkts1.2 763 */ 764 if (ts.isTypeParameterDeclaration(node)) { 765 const { unionTypeNode, typeNode } = extractTypeNodes(node, this.context); 766 767 if (unionTypeNode === undefined && typeNode !== undefined) { 768 return createTypeParameterDeclaration(node, undefined, typeNode, this.context); 769 } else if (unionTypeNode !== undefined && typeNode === undefined) { 770 return createTypeParameterDeclaration(node, unionTypeNode, undefined, this.context); 771 } 772 } 773 return node; 774 } 775 776 /** 777 * Rule: `arkts-no-initializer` 778 */ 779 private [FaultID.NoInitializer](node: ts.Node): ts.VisitResult<ts.Node> { 780 /** 781 * For member variables with initial assignment, convert literals to their corresponding data types. 782 */ 783 784 if (!ts.isPropertyDeclaration(node)) { 785 return node; 786 } 787 788 const nodeType = inferNodeTypeFromInitializer(node, this.context); 789 790 if (nodeType !== undefined) { 791 return this.context.factory.updatePropertyDeclaration( 792 node, 793 node.modifiers, 794 node.name, 795 undefined, 796 nodeType, 797 undefined 798 ); 799 } 800 801 return node; 802 } 803 804 /* 805 * Rule: `For enums with heterogeneous types, convert them to JSValue` 806 */ 807 private [FaultID.EnumWithMixedType](node: ts.Node): ts.VisitResult<ts.Node> { 808 /** 809 * For enums with heterogeneous types, convert them to JSValue 810 */ 811 812 if (ts.isSourceFile(node)) { 813 const { mixedEnumNames, firstPassStatements } = findMixedEnums(node as ts.SourceFile); 814 const firstPassSourceFile = ts.factory.updateSourceFile(node, firstPassStatements); 815 const visit = (typeNode: ts.Node): ts.VisitResult<ts.Node> => { 816 if (ts.isTypeReferenceNode(typeNode)) { 817 const typeName = typeNode.typeName; 818 if (ts.isIdentifier(typeName) && mixedEnumNames.includes(typeName.text)) { 819 return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(JSValue), undefined); 820 } 821 } 822 return ts.visitEachChild(typeNode, visit, this.context); 823 }; 824 let statements = firstPassSourceFile.statements.slice(); 825 for (let i = 0; i < statements.length; i++) { 826 const stmt = statements[i]; 827 const newStmt = visit(stmt); 828 if (newStmt) { 829 statements[i] = newStmt as ts.Statement; 830 } 831 } 832 return ts.factory.updateSourceFile(firstPassSourceFile, statements); 833 } 834 return node; 835 } 836 837 /** 838 * Rule: `arkts-no-duplicated-enum` 839 */ 840 private [FaultID.DuplicatedEnum](node: ts.Node): ts.VisitResult<ts.Node> { 841 842 /** 843 * For duplicated enums, convert them to one enum 844 */ 845 846 const statements: ts.Statement[] = []; 847 const interfaceDeclarations: ts.InterfaceDeclaration[] = []; 848 const classDeclarations: ts.ClassDeclaration[] = []; 849 const enumDeclarations: ts.EnumDeclaration[] = []; 850 if (ts.isSourceFile(node) || ts.isModuleBlock(node)) { 851 for (const stmt of node.statements) { 852 if (ts.isInterfaceDeclaration(stmt)) { 853 interfaceDeclarations.push(stmt); 854 } else if (ts.isClassDeclaration(stmt)) { 855 classDeclarations.push(stmt); 856 } else if (ts.isEnumDeclaration(stmt)) { 857 enumDeclarations.push(stmt); 858 } else { 859 statements.push(stmt); 860 } 861 } 862 if (ts.isModuleBlock(node)) { 863 const moduleBlockNode = this.context.factory.updateModuleBlock(node, [ 864 ...statements, 865 ...findSameInterfaceOrClassOrEnumList( 866 interfaceDeclarations, 867 this.context, 868 ts.SyntaxKind.InterfaceDeclaration 869 ), 870 ...findSameInterfaceOrClassOrEnumList(classDeclarations, this.context, ts.SyntaxKind.ClassDeclaration), 871 ...findSameInterfaceOrClassOrEnumList(enumDeclarations, this.context, ts.SyntaxKind.EnumDeclaration) 872 ]); 873 return moduleBlockNode; 874 } else { 875 return this.context.factory.updateSourceFile(node, [ 876 ...statements, 877 ...findSameInterfaceOrClassOrEnumList( 878 interfaceDeclarations, 879 this.context, 880 ts.SyntaxKind.InterfaceDeclaration 881 ), 882 ...findSameInterfaceOrClassOrEnumList(classDeclarations, this.context, ts.SyntaxKind.ClassDeclaration), 883 ...findSameInterfaceOrClassOrEnumList(enumDeclarations, this.context, ts.SyntaxKind.EnumDeclaration) 884 ]); 885 } 886 } 887 888 return node; 889 } 890 891 /** 892 * Rule: `arkts-no-private-members` 893 */ 894 private [FaultID.NoPrivateMember](node: ts.Node): ts.VisitResult<ts.Node> { 895 void this; 896 897 /** 898 * For members modified by private, they do not appear in the declaration file. 899 */ 900 if (ts.isClassDeclaration(node)) { 901 const newMembers = node.members.filter((member) => { 902 if (!ts.canHaveModifiers(member)) { 903 return true; 904 } 905 const modifiers = ts.getModifiers(member); 906 return !(modifiers === null || modifiers === void 0 ? 907 void 0 : 908 modifiers.some((modifier) => { 909 return modifier.kind === ts.SyntaxKind.PrivateKeyword; 910 })); 911 }); 912 const nodeCanHaveModifiers = ts.canHaveModifiers(node); 913 const validModifiers = nodeCanHaveModifiers ? ts.getModifiers(node) : undefined; 914 return ts.factory.createClassDeclaration( 915 validModifiers, 916 node.name, 917 node.typeParameters, 918 node.heritageClauses, 919 newMembers 920 ); 921 } 922 923 return node; 924 } 925 926 /** 927 * Rule: `arkts-no-etskeyword` 928 */ 929 private [FaultID.NoETSKeyword](node: ts.Node): ts.VisitResult<ts.Node> { 930 void this; 931 932 /** 933 * Remove some ets keyword from the declaration. 934 */ 935 936 if ( 937 ts.isClassDeclaration(node) || 938 ts.isInterfaceDeclaration(node) || 939 ts.isMethodDeclaration(node) || 940 ts.isPropertyDeclaration(node) || 941 ts.isImportSpecifier(node) || 942 ts.isExportSpecifier(node) 943 ) { 944 return restrictIdentifierName(node); 945 } 946 return node; 947 } 948 949 /** 950 * Rule: `arkts-ESObject-to-object-type` 951 */ 952 private [FaultID.DefaultExport](node: ts.Node): ts.VisitResult<ts.Node> { 953 if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) { 954 return exportDefaultAssignment(node, this.context); 955 } 956 957 return node; 958 } 959 960 /** 961 * Rule: `arkts-no-call-signature-or-optional-methods` 962 */ 963 private [FaultID.CallorOptionFuncs](node: ts.Node): ts.VisitResult<ts.Node> { 964 void this; 965 966 /** 967 * Convert interfaces with call signatures or optional methods to JSValue 968 */ 969 970 if (ts.isInterfaceDeclaration(node)) { 971 for (const member of node.members) { 972 if (ts.isIndexSignatureDeclaration(member) || 973 ts.isCallSignatureDeclaration(member) || 974 (ts.isMethodSignature(member) && member.questionToken)) { 975 /** 976 * If the header comment of an interface declaration contains `@noninterop` field, 977 * the interface node will not be converted. 978 */ 979 if (isNonInterop(node)) { 980 return undefined; 981 } 982 const typeAliasDeclaration = ts.factory.createTypeAliasDeclaration( 983 node.modifiers, 984 node.name, 985 node.typeParameters, 986 ts.factory.createTypeReferenceNode(JSValue) 987 ); 988 989 return typeAliasDeclaration; 990 } 991 } 992 } 993 994 return node; 995 } 996 997 /* 998 * Rule: `arkts-no-type-generic` 999 */ 1000 private [FaultID.TypeGeneric](node: ts.Node): ts.VisitResult<ts.Node> { 1001 void this; 1002 1003 /** 1004 * Generic type alias mapped to JSValue in arkts1.2 1005 */ 1006 1007 if (ts.isTypeAliasDeclaration(node)) { 1008 const shouldReplace = [ 1009 (checkNode: ts.Node): boolean => { 1010 return SpecificTypes.includes(ts.SyntaxKind[checkNode.kind]); 1011 }, 1012 (checkNode: ts.Node): boolean => { 1013 return ( 1014 ts.isTypeReferenceNode(checkNode) && 1015 ts.isIdentifier(checkNode.typeName) && 1016 UtilityTypes.includes(checkNode.typeName.text) 1017 ); 1018 } 1019 ].some((check) => { 1020 return check(node.type); 1021 }); 1022 if (shouldReplace) { 1023 return ts.factory.createTypeAliasDeclaration( 1024 node.modifiers, 1025 node.name, 1026 node.typeParameters, 1027 ts.factory.createTypeReferenceNode(JSValue, undefined) 1028 ); 1029 } 1030 } 1031 return node; 1032 } 1033 1034 /** 1035 * Rule: `arkts-no-empty-export` 1036 */ 1037 private [FaultID.NoEmptyExport](node: ts.Node): ts.VisitResult<ts.Node> { 1038 void this; 1039 1040 /** 1041 * Remove empty export statements 1042 */ 1043 1044 if (ts.isExportDeclaration(node)) { 1045 const exportClause = node.exportClause; 1046 if (exportClause && ts.isNamedExports(exportClause)) { 1047 if (exportClause.elements.length === 0) { 1048 // If the named exports are empty, return undefined to remove this export statement 1049 return undefined; 1050 } 1051 } 1052 } 1053 1054 return node; 1055 } 1056 1057 /* 1058 * Rule: `arkts-no-limit-extends` 1059 */ 1060 private [FaultID.LimitExtends](node: ts.Node): ts.VisitResult<ts.Node> { 1061 /** 1062 * If some classes inherit from special classes or interfaces, 1063 * these classes will be directly removed from the declaration file. 1064 */ 1065 1066 if (ts.isSourceFile(node)) { 1067 const importDeclarations: ts.ImportDeclaration[] = []; 1068 const classDeclarations: ts.ClassDeclaration[] = []; 1069 const interfaceDeclarations: ts.InterfaceDeclaration[] = []; 1070 const statements: ts.Statement[] = []; 1071 for (const stmt of node.statements) { 1072 if (ts.isImportDeclaration(stmt)) { 1073 importDeclarations.push(stmt); 1074 } else if (ts.isInterfaceDeclaration(stmt)) { 1075 interfaceDeclarations.push(stmt); 1076 } else if (ts.isClassDeclaration(stmt)) { 1077 classDeclarations.push(stmt); 1078 } else { 1079 statements.push(stmt); 1080 } 1081 } 1082 1083 const importSpecifierNames = getImportSpecifierNames(importDeclarations); 1084 1085 return this.context.factory.updateSourceFile(node, [ 1086 ...importDeclarations, 1087 ...statements, 1088 ...tranClassDeclarationList(classDeclarations, this.context, importSpecifierNames), 1089 ...tranInterfaceDeclarationList(interfaceDeclarations, this.context, importSpecifierNames)]) 1090 } 1091 1092 return node; 1093 } 1094 1095 /** 1096 * Rule: `arkts-no-empty-import` 1097 */ 1098 private [FaultID.NoEmptyImport](node: ts.Node): ts.VisitResult<ts.Node> { 1099 void this; 1100 1101 /** 1102 * Remove empty import statements 1103 */ 1104 1105 if (ts.isImportDeclaration(node)) { 1106 const importClause = node.importClause; 1107 if (importClause?.namedBindings && ts.isNamedImports(importClause.namedBindings)) { 1108 if (importClause.namedBindings.elements.length === 0) { 1109 // If the named imports are empty, return undefined to remove this import statement 1110 return undefined; 1111 } 1112 } 1113 } 1114 1115 return node; 1116 } 1117 1118 /* 1119 * Rule: `arkts-no-property-access-expression` 1120 */ 1121 private [FaultID.PropertyAccessExpression](node: ts.Node): ts.VisitResult<ts.Node> { 1122 1123 /** 1124 * Property access expression convert to specific type 1125 */ 1126 1127 if (ts.isPropertyDeclaration(node)) { 1128 const initializer = node.initializer; 1129 if (!initializer || initializer.kind !== ts.SyntaxKind.PropertyAccessExpression) { 1130 return node; 1131 } 1132 1133 const updatedInitializer = updatePropertyAccessExpression(initializer as ts.PropertyAccessExpression, this.context); 1134 if (updatedInitializer) { 1135 return this.context.factory.updatePropertyDeclaration( 1136 node, 1137 node.modifiers, 1138 node.name, 1139 node.questionToken, 1140 updatedInitializer, 1141 undefined 1142 ); 1143 } 1144 } 1145 1146 return node; 1147 } 1148 1149 /** 1150 * Rule: `arkts-no-built-in-type` 1151 */ 1152 private [FaultID.NoBuiltInType](node: ts.Node): ts.VisitResult<ts.Node> { 1153 void this; 1154 1155 /** 1156 * Built in types will convert to ESObject 1157 */ 1158 1159 if (ts.isTypeReferenceNode(node)) { 1160 const typeName = node.typeName; 1161 if (ts.isIdentifier(typeName) && BuiltInType.includes(typeName.text)) { 1162 const newTypeNode = ts.factory.createTypeReferenceNode(JSValue, undefined); 1163 return ts.factory.updateTypeReferenceNode( 1164 node, 1165 newTypeNode.typeName, 1166 newTypeNode.typeArguments 1167 ); 1168 } 1169 } 1170 1171 return node; 1172 } 1173 1174 /** 1175 * Rule: `arkts:add-declare-to-top-level-interfaces` 1176 */ 1177 private [FaultID.AddDeclareToTopLevelInterfaces](node: ts.Node): ts.VisitResult<ts.Node> { 1178 if (!ts.isSourceFile(node)) { 1179 return node; 1180 } 1181 1182 const statements = node.statements.map(stmt => { 1183 if (ts.isInterfaceDeclaration(stmt) && stmt.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) { 1184 // If 'declare' already exists, do not add it again 1185 if (stmt.modifiers.some(m => m.kind === ts.SyntaxKind.DeclareKeyword)) { 1186 return stmt; 1187 } 1188 1189 // Transform interface nodes by adding the declare keyword 1190 const newModifiers = [ 1191 ...stmt.modifiers.filter(m => ts.isModifier(m)), 1192 this.context.factory.createModifier(ts.SyntaxKind.DeclareKeyword) 1193 ] as ts.Modifier[]; 1194 1195 return this.context.factory.updateInterfaceDeclaration( 1196 stmt, 1197 newModifiers, 1198 stmt.name, 1199 stmt.typeParameters, 1200 stmt.heritageClauses, 1201 stmt.members 1202 ); 1203 } 1204 return stmt; 1205 }); 1206 1207 return this.context.factory.updateSourceFile(node, statements); 1208 } 1209 1210 /** 1211 * Rule: `remove-limit-decorator` 1212 */ 1213 private [FaultID.RemoveLimitDecorator](node: ts.Node): ts.VisitResult<ts.Node> { 1214 if (!ts.isFunctionDeclaration(node) && !ts.isClassDeclaration(node)) { 1215 return node; 1216 } 1217 1218 let decorators: ts.Decorator[] = []; 1219 1220 const illegalDecorators = ts.getAllDecorators(node) 1221 decorators = illegalDecorators?.filter(ts.isDecorator) as ts.Decorator[]; 1222 // Filter out restricted decorators 1223 const filteredDecorators = decorators?.filter((decorator) => { 1224 const expression = decorator.expression; 1225 return !(ts.isIdentifier(expression) && LIMIT_DECORATOR.includes(expression.text)); 1226 }); 1227 (node as any).illegalDecorators = filteredDecorators; 1228 1229 return node; 1230 } 1231 1232 /** 1233 * Rule: `arkts-no-optional-member-function` 1234 */ 1235 private [FaultID.NoOptionalMemberFunction](node: ts.Node): ts.VisitResult<ts.Node> { 1236 /** 1237 * Member functions with the optional question token will no longer be supported in ArkTS 1.2. 1238 * Remove such methods from class declarations. 1239 */ 1240 if (ts.isClassDeclaration(node)) { 1241 const updatedMembers = node.members.filter(isNotOptionalMemberFunction); 1242 return this.context.factory.updateClassDeclaration( 1243 node, 1244 node.modifiers, 1245 node.name, 1246 node.typeParameters, 1247 node.heritageClauses, 1248 updatedMembers 1249 ); 1250 } 1251 return node; 1252 } 1253 1254 /** 1255 * Rule: `tuple map to Array in type annotation` 1256 */ 1257 private [FaultID.TupleTypeToArray](node: ts.Node): ts.VisitResult<ts.Node> { 1258 if (ts.isTupleTypeNode(node)) { 1259 return this.context.factory.createTypeReferenceNode( 1260 this.context.factory.createIdentifier('Array'), 1261 [this.context.factory.createTypeReferenceNode(JSValue)] 1262 ); 1263 } 1264 1265 return node; 1266 } 1267 1268 /** 1269 * Rule: `keeping the struct in its original form` 1270 */ 1271 private [FaultID.StructDeclaration](node: ts.Node): ts.VisitResult<ts.Node> { 1272 if (ts.isStructDeclaration(node)) { 1273 1274 const filteredMembers = node.members.filter(member => { 1275 return !ts.isConstructorDeclaration(member); 1276 }); 1277 1278 // Create a new struct node, removing only the constructor 1279 const newStruct = ts.factory.updateStructDeclaration( 1280 node, 1281 node.modifiers, 1282 node.name, 1283 node.typeParameters, 1284 node.heritageClauses, 1285 ts.factory.createNodeArray(filteredMembers) 1286 ); 1287 1288 return newStruct; 1289 } 1290 1291 return node; 1292 } 1293 1294 /** 1295 * Rule: `union type with void mapped to Any` 1296 */ 1297 private [FaultID.NoVoidUnionType](node: ts.Node): ts.VisitResult<ts.Node> { 1298 1299 /** 1300 * If a union type contains the void type, 1301 * convert the union type to Any. 1302 */ 1303 if (ts.isUnionTypeNode(node)) { 1304 const hasVoid = node.types.some((type) => type.kind === ts.SyntaxKind.VoidKeyword); 1305 if (hasVoid) { 1306 return this.context.factory.createTypeReferenceNode(JSValue); 1307 } 1308 } 1309 1310 return node; 1311 } 1312 1313 /* 1314 * Rule: `arkts-const-literal-to-type` 1315 */ 1316 private [FaultID.ConstLiteralToType](node: ts.Node): ts.VisitResult<ts.Node> { 1317 /** 1318 * Convert const variable declarations with literal values to type declarations. 1319 * e.g. export declare const c = 123; -> export declare const c: number; 1320 */ 1321 1322 if (ts.isVariableDeclaration(node) && node.initializer) { 1323 let typeNode: ts.TypeNode | undefined; 1324 1325 if (ts.isNumericLiteral(node.initializer)) { 1326 typeNode = this.context.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); 1327 } 1328 1329 if (typeNode) { 1330 const result = this.context.factory.createVariableDeclaration( 1331 node.name, 1332 node.exclamationToken, 1333 typeNode, 1334 undefined 1335 ); 1336 1337 return result; 1338 } 1339 } 1340 1341 return node; 1342 } 1343} 1344 1345/** 1346 * Modify the ModuleBlock node to add Export so that it can be referenced by external methods. 1347 */ 1348function transModuleBlockformation( 1349 stmt: ts.Statement, 1350 context: ts.TransformationContext, 1351 kindType: ts.SyntaxKind, 1352): ts.Statement { 1353 const exportToken = context.factory.createToken(ts.SyntaxKind.ExportKeyword); 1354 return updateNodetoAddExport(stmt, context, kindType, exportToken); 1355} 1356 1357/** 1358 * Extract interface members together. 1359 */ 1360function extractInterfaceMembers(item: ts.InterfaceDeclaration, methods: ts.TypeElement[]): void { 1361 item.members.forEach((member) => { 1362 if ((ts.isMethodSignature(member) || ts.isPropertySignature(member)) && member.name) { 1363 const memberName = (member.name as ts.Identifier).text; 1364 if ( 1365 !methods.some( 1366 (m) => 1367 isSameFunction(m, member) || 1368 (ts.isPropertySignature(m) && ts.isPropertySignature(member) && m.name.getText() === memberName) 1369 ) 1370 ) { 1371 methods.push(member); 1372 } 1373 } 1374 }); 1375} 1376 1377/** 1378 * Extract class members together. 1379 */ 1380function extractClassMembers(item: ts.ClassDeclaration, classMethods: ts.ClassElement[]): void { 1381 item.members.forEach((member) => { 1382 if ((ts.isMethodDeclaration(member) || ts.isPropertyDeclaration(member)) && member.name) { 1383 const memberName = (member.name as ts.Identifier).text; 1384 if ( 1385 !classMethods.some( 1386 (m) => 1387 (ts.isMethodDeclaration(m) && ts.isMethodDeclaration(member) && m.name.getText() === memberName) || 1388 (ts.isPropertyDeclaration(m) && ts.isPropertyDeclaration(member) && m.name.getText() === memberName) 1389 ) 1390 ) { 1391 classMethods.push(member); 1392 } 1393 } 1394 }); 1395} 1396 1397function isSameFunction(m: ts.TypeElement, member: ts.TypeElement): boolean { 1398 const memberName = (member.name as ts.Identifier).text; 1399 const mName = (m.name as ts.Identifier).text; 1400 1401 if (mName !== memberName) { 1402 return false; 1403 } 1404 if (!ts.isMethodSignature(m) || !ts.isMethodSignature(member)) { 1405 return false; 1406 } 1407 if (!compareParameters(m.parameters, member.parameters)) { 1408 return false; 1409 } 1410 if (m.type && member.type) { 1411 if (!typesAreEqual(m.type, member.type)) { 1412 return false; 1413 } 1414 } else if (!!m.type !== !!member.type) { 1415 return false; 1416 } 1417 if (!compareTypeParameters(m.typeParameters, member.typeParameters)) { 1418 return false; 1419 } 1420 return true; 1421} 1422 1423function compareParameters( 1424 params1: ts.NodeArray<ts.ParameterDeclaration>, 1425 params2: ts.NodeArray<ts.ParameterDeclaration> 1426): boolean { 1427 if (params1.length !== params2.length) { 1428 return false; 1429 } 1430 for (let i = 0; i < params1.length; i++) { 1431 const p1 = params1[i]; 1432 const p2 = params2[i]; 1433 if (p1.name.getText() !== p2.name.getText()) { 1434 return false; 1435 } 1436 if (!typesAreEqual(p1.type, p2.type)) { 1437 return false; 1438 } 1439 1440 if (p1.questionToken !== p2.questionToken || p1.initializer !== p2.initializer) { 1441 return false; 1442 } 1443 } 1444 1445 return true; 1446} 1447 1448function typesAreEqual(type1: ts.TypeNode | undefined, type2: ts.TypeNode | undefined): boolean { 1449 if (!type1 || !type2) { 1450 return type1 === type2; 1451 } 1452 1453 return type1.getText() === type2.getText(); 1454} 1455 1456function compareTypeParameters( 1457 params1: ts.NodeArray<ts.TypeParameterDeclaration> | undefined, 1458 params2: ts.NodeArray<ts.TypeParameterDeclaration> | undefined 1459): boolean { 1460 if (!params1 && !params2) { 1461 return true; 1462 } 1463 if (!params1 || !params2) { 1464 return false; 1465 } 1466 if (params1.length !== params2.length) { 1467 return false; 1468 } 1469 1470 for (let i = 0; i < params1.length; i++) { 1471 const p1 = params1[i]; 1472 const p2 = params2[i]; 1473 1474 if (p1.name.getText() !== p2.name.getText()) { 1475 return false; 1476 } 1477 } 1478 1479 return true; 1480} 1481 1482/** 1483 * Find a list of interfaces or classes with the same name 1484 */ 1485function findSameInterfaceAndClassList( 1486 statements: ts.Statement[], 1487 context: ts.TransformationContext, 1488 kindType: number 1489): ts.Statement[] { 1490 const sameInterfaceMap = new Map<string, { count: number; nodes: ts.Statement[] }>(); 1491 statements.forEach((statement) => { 1492 let name: string | undefined; 1493 const isInterfaceDeclaration = ts.isInterfaceDeclaration(statement) && statement.name; 1494 const isClassDeclaration = ts.isClassDeclaration(statement) && statement.name; 1495 if (isInterfaceDeclaration || isClassDeclaration) { 1496 name = statement.name.text; 1497 } 1498 if (name) { 1499 if (!sameInterfaceMap.has(name)) { 1500 sameInterfaceMap.set(name, { count: 0, nodes: [] }); 1501 } 1502 const entry = sameInterfaceMap.get(name)!; 1503 entry.count++; 1504 entry.nodes.push(statement); 1505 } 1506 }); 1507 const uniqueNodes: ts.Statement[] = []; 1508 const newNodes: ts.Statement[] = []; 1509 sameInterfaceMap.forEach((entry) => { 1510 if (entry.count === 1) { 1511 uniqueNodes.push(...entry.nodes); 1512 } else { 1513 // Reorganize interface information 1514 const newInterface = createCombinedInterfaceAndClass(entry.nodes, context, kindType); 1515 newNodes.push(newInterface); 1516 } 1517 }); 1518 1519 return [...uniqueNodes, ...newNodes]; 1520} 1521 1522/** 1523 * Modify the node node and add the export keyword. 1524 */ 1525function updateNodetoAddExport( 1526 stmt: ts.Statement, 1527 context: ts.TransformationContext, 1528 kindType: ts.SyntaxKind, 1529 exportToken: ts.Modifier 1530): ts.Statement { 1531 switch (kindType) { 1532 case ts.SyntaxKind.VariableDeclaration: 1533 case ts.SyntaxKind.VariableStatement: 1534 return updateVariableOrStatement(stmt, context, exportToken); 1535 case ts.SyntaxKind.ModuleDeclaration: 1536 return updateModuleDeclaration(stmt, context, exportToken); 1537 case ts.SyntaxKind.FunctionDeclaration: 1538 return updateFunctionDeclaration(stmt, context, exportToken); 1539 case ts.SyntaxKind.InterfaceDeclaration: 1540 return updateInterfaceDeclaration(stmt, context, exportToken); 1541 case ts.SyntaxKind.ClassDeclaration: 1542 return updateClassDeclaration(stmt, context, exportToken); 1543 case ts.SyntaxKind.TypeAliasDeclaration: 1544 return updateTypeAliasDeclaration(stmt, context, exportToken); 1545 case ts.SyntaxKind.EnumDeclaration: 1546 return updateEnumDeclaration(stmt, context, exportToken); 1547 default: 1548 return stmt; 1549 } 1550} 1551 1552function updateVariableOrStatement( 1553 stmt: ts.Statement, 1554 context: ts.TransformationContext, 1555 exportToken: ts.Modifier 1556): ts.Statement { 1557 if (ts.isVariableStatement(stmt)) { 1558 return context.factory.updateVariableStatement( 1559 stmt, 1560 [exportToken, ...(stmt.modifiers || [])], 1561 stmt.declarationList 1562 ); 1563 } 1564 throw new Error('Unsupported statement type'); 1565} 1566 1567function updateModuleDeclaration( 1568 stmt: ts.Statement, 1569 context: ts.TransformationContext, 1570 exportToken: ts.Modifier 1571): ts.Statement { 1572 if (ts.isModuleDeclaration(stmt)) { 1573 return context.factory.updateModuleDeclaration( 1574 stmt, 1575 [exportToken, ...(stmt.modifiers || [])], 1576 stmt.name, 1577 stmt.body 1578 ); 1579 } 1580 return stmt; 1581} 1582 1583function updateFunctionDeclaration( 1584 stmt: ts.Statement, 1585 context: ts.TransformationContext, 1586 exportToken: ts.Modifier 1587): ts.Statement { 1588 if (ts.isFunctionDeclaration(stmt)) { 1589 return context.factory.updateFunctionDeclaration( 1590 stmt, 1591 [exportToken, ...(stmt.modifiers || [])], 1592 stmt.asteriskToken, 1593 stmt.name, 1594 stmt.typeParameters, 1595 stmt.parameters, 1596 stmt.type, 1597 stmt.body 1598 ); 1599 } 1600 return stmt; 1601} 1602 1603function updateInterfaceDeclaration( 1604 stmt: ts.Statement, 1605 context: ts.TransformationContext, 1606 exportToken: ts.Modifier 1607): ts.Statement { 1608 if (ts.isInterfaceDeclaration(stmt)) { 1609 return context.factory.updateInterfaceDeclaration( 1610 stmt, 1611 [exportToken, ...(stmt.modifiers || [])], 1612 stmt.name, 1613 stmt.typeParameters, 1614 stmt.heritageClauses, 1615 stmt.members 1616 ); 1617 } 1618 return stmt; 1619} 1620 1621function updateClassDeclaration( 1622 stmt: ts.Statement, 1623 context: ts.TransformationContext, 1624 exportToken: ts.Modifier 1625): ts.Statement { 1626 if (ts.isClassDeclaration(stmt)) { 1627 return context.factory.updateClassDeclaration( 1628 stmt, 1629 [exportToken, ...(stmt.modifiers || [])], 1630 stmt.name, 1631 stmt.typeParameters, 1632 stmt.heritageClauses, 1633 stmt.members 1634 ); 1635 } 1636 return stmt; 1637} 1638 1639function updateTypeAliasDeclaration( 1640 stmt: ts.Statement, 1641 context: ts.TransformationContext, 1642 exportToken: ts.Modifier 1643): ts.Statement { 1644 if (ts.isTypeAliasDeclaration(stmt)) { 1645 return context.factory.updateTypeAliasDeclaration( 1646 stmt, 1647 [exportToken, ...(stmt.modifiers || [])], 1648 stmt.name, 1649 stmt.typeParameters, 1650 stmt.type 1651 ); 1652 } 1653 return stmt; 1654} 1655 1656function updateEnumDeclaration( 1657 stmt: ts.Statement, 1658 context: ts.TransformationContext, 1659 exportToken: ts.Modifier 1660): ts.Statement { 1661 if (ts.isEnumDeclaration(stmt)) { 1662 return context.factory.updateEnumDeclaration( 1663 stmt, 1664 [exportToken, ...(stmt.modifiers || [])], 1665 stmt.name, 1666 stmt.members 1667 ); 1668 } 1669 return stmt; 1670} 1671 1672/** 1673 * Determine whether the type reference needs to be replaced. 1674 */ 1675function shouldReplaceTypeReference(typeName: ts.EntityName, typeAliasMap: Map<string, string>): boolean { 1676 if (ts.isIdentifier(typeName)) { 1677 return typeAliasMap.has(typeName.text); 1678 } else if (ts.isQualifiedName(typeName)) { 1679 const firstIdentifier = getFirstIdentifierInQualifiedName(typeName); 1680 if (firstIdentifier !== undefined) { 1681 return typeAliasMap.has(firstIdentifier.text); 1682 } 1683 } 1684 return false; 1685} 1686 1687/** 1688 * Replace the type reference 1689 */ 1690function replaceTypeReference(node: ts.TypeReferenceNode, context: ts.TransformationContext): ts.Node { 1691 return context.factory.createTypeReferenceNode(context.factory.createIdentifier(JSValue), undefined); 1692} 1693 1694/** 1695 * Traverse the nodes 1696 */ 1697function traverseNode(node: ts.Node, context: ts.TransformationContext, typeAliasMap: Map<string, string>): ts.Node { 1698 if (ts.isTypeReferenceNode(node)) { 1699 const typeName = node.typeName; 1700 if (shouldReplaceTypeReference(typeName, typeAliasMap)) { 1701 return replaceTypeReference(node, context); 1702 } 1703 } 1704 return ts.visitEachChild(node, (child) => traverseNode(child, context, typeAliasMap), context); 1705} 1706 1707/** 1708 * Replace the type reference with JSValue 1709 */ 1710function replaceTypeReferenceWithObject( 1711 node: ts.Node, 1712 context: ts.TransformationContext, 1713 typeAliasMap: Map<string, string> 1714): ts.Node { 1715 return traverseNode(node, context, typeAliasMap); 1716} 1717 1718/** 1719 * Get the first identifier in a qualified name 1720 */ 1721function getFirstIdentifierInQualifiedName(qualifiedName: ts.QualifiedName): ts.Identifier | undefined { 1722 let current: ts.Node = qualifiedName; 1723 while (ts.isQualifiedName(current)) { 1724 current = current.left; 1725 } 1726 return ts.isIdentifier(current) ? current : undefined; 1727} 1728 1729/** 1730 * Check if the module import is limited 1731 */ 1732function isLimitImport(moduleSpecifier: ts.Expression, limitImport: string[]): boolean { 1733 for (const item of limitImport) { 1734 if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text.startsWith(item)) { 1735 return true; 1736 } 1737 } 1738 1739 return false; 1740} 1741 1742/** 1743 * Check if the node is of bigint type. 1744 */ 1745function isBigintType(v: ts.Node): boolean { 1746 if (ts.isLiteralTypeNode(v)) { 1747 const literalTypeNode = v as ts.LiteralTypeNode; 1748 if (literalTypeNode.literal && literalTypeNode.literal.kind !== undefined) { 1749 if (literalTypeNode.literal.kind === ts.SyntaxKind.PrefixUnaryExpression) { 1750 const prefix = literalTypeNode.literal as ts.PrefixUnaryExpression; 1751 return prefix.pos !== -1 && prefix.operand.kind === ts.SyntaxKind.BigIntLiteral; 1752 } 1753 return literalTypeNode.literal.kind === ts.SyntaxKind.BigIntLiteral; 1754 } 1755 } 1756 return v.kind === ts.SyntaxKind.BigIntKeyword || v.kind === ts.SyntaxKind.BigIntLiteral; 1757} 1758 1759/** 1760 * Check if the node is of boolean type. 1761 */ 1762function isBooleanType(v: ts.Node): boolean { 1763 if (ts.isLiteralTypeNode(v)) { 1764 const literalTypeNode = v as ts.LiteralTypeNode; 1765 if (literalTypeNode.literal && literalTypeNode.literal.kind !== undefined) { 1766 return ( 1767 literalTypeNode.literal.kind === ts.SyntaxKind.FalseKeyword || 1768 literalTypeNode.literal.kind === ts.SyntaxKind.TrueKeyword 1769 ); 1770 } 1771 } 1772 return ( 1773 v.kind === ts.SyntaxKind.FalseKeyword || 1774 v.kind === ts.SyntaxKind.TrueKeyword || 1775 v.kind === ts.SyntaxKind.BooleanKeyword 1776 ); 1777} 1778 1779/** 1780 * Get the updated type. 1781 */ 1782function getUpdatedType( 1783 v: ts.Node, 1784 nodeFlag: ts.NodeFlags, 1785 context: ts.TransformationContext 1786): ts.TypeNode | undefined { 1787 if (ts.isIdentifier(v)) { 1788 return undefined; 1789 } 1790 const isBigint = isBigintType(v); 1791 const isBoolean = isBooleanType(v); 1792 1793 const allowedFlags = [ts.NodeFlags.Const, ts.NodeFlags.Let]; 1794 if (isBigint && allowedFlags.includes(nodeFlag)) { 1795 return context.factory.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword); 1796 } else if (isBoolean) { 1797 return context.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword); 1798 } 1799 return undefined; 1800} 1801 1802/** 1803 * Process variable declarations 1804 */ 1805function processVariableDeclaration( 1806 child: ts.VariableDeclaration, 1807 nodeFlag: ts.NodeFlags, 1808 context: ts.TransformationContext 1809): ts.VariableDeclaration { 1810 let updatedType: ts.TypeNode | undefined; 1811 child.forEachChild((v) => { 1812 updatedType = getUpdatedType(v, nodeFlag, context); 1813 return !!updatedType; 1814 }); 1815 if (updatedType) { 1816 return context.factory.createVariableDeclaration(child.name, undefined, updatedType, undefined); 1817 } 1818 1819 return child; 1820} 1821 1822/** 1823 * Change the declarations of logical operators to the boolean type, 1824 * and change the declarations of bigint constants to the bigint type. 1825 */ 1826function transformLogicalOperators( 1827 node: ts.Node, 1828 context: ts.TransformationContext, 1829 nodeFlag: ts.NodeFlags 1830): ts.VisitResult<ts.Node> { 1831 const variableDeclarations: ts.VariableDeclaration[] = []; 1832 node.forEachChild((child) => { 1833 if (ts.isVariableDeclaration(child)) { 1834 const processedDeclaration = processVariableDeclaration(child, nodeFlag, context); 1835 variableDeclarations.push(processedDeclaration); 1836 } 1837 }); 1838 return context.factory.createVariableDeclarationList(variableDeclarations, nodeFlag); 1839} 1840 1841/** 1842 * Change the literal union type to a type union. 1843 */ 1844function createUnionTypeNode( 1845 unionTypeNode: ts.UnionTypeNode, 1846 context: ts.TransformationContext 1847): ts.VisitResult<ts.Node> { 1848 let typeNode: ts.TypeNode[] = []; 1849 unionTypeNode.forEachChild((child) => { 1850 if (ts.isLiteralTypeNode(child)) { 1851 if (child.literal.kind === ts.SyntaxKind.FalseKeyword || child.literal.kind === ts.SyntaxKind.TrueKeyword) { 1852 typeNode.push(context.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword)); 1853 } else if (child.literal.kind === ts.SyntaxKind.NumericLiteral) { 1854 typeNode.push(context.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)); 1855 } else { 1856 typeNode.push(child); 1857 } 1858 } else { 1859 typeNode.push(child as ts.TypeNode); 1860 } 1861 }); 1862 return context.factory.createUnionTypeNode(typeNode); 1863} 1864 1865function processSourceFileStatements( 1866 statements: readonly ts.Statement[], 1867 context: ts.TransformationContext 1868): { 1869 importDeclarations: ts.Statement[]; 1870 statements: ts.Statement[]; 1871 typeAliasMap: Map<string, string>; 1872} { 1873 const importDeclarations: ts.Statement[] = []; 1874 const statementsResult: ts.Statement[] = []; 1875 const typeAliasMap = new Map<string, string>(); 1876 1877 for (const stmt of statements) { 1878 if (ts.isImportDeclaration(stmt)) { 1879 processImportDeclaration(stmt, importDeclarations, typeAliasMap); 1880 } else { 1881 const newStmt = replaceTypeReferenceWithObject(stmt, context, typeAliasMap); 1882 statementsResult.push(newStmt as ts.Statement); 1883 } 1884 } 1885 1886 return { importDeclarations, statements: statementsResult, typeAliasMap }; 1887} 1888 1889function processImportDeclaration( 1890 stmt: ts.ImportDeclaration, 1891 importDeclarations: ts.Statement[], 1892 typeAliasMap: Map<string, string> 1893): void { 1894 const moduleSpecifier = stmt.moduleSpecifier; 1895 if (isLimitImport(moduleSpecifier, KitPrefix)) { 1896 const importClause = stmt.importClause; 1897 if (importClause && importClause.namedBindings && ts.isNamedImports(importClause.namedBindings)) { 1898 processNamedImports(importClause.namedBindings, typeAliasMap); 1899 } else if (importClause?.name) { 1900 const originalName = importClause?.name?.text; 1901 const alias = originalName; 1902 typeAliasMap.set(alias, originalName); 1903 } 1904 return; 1905 } 1906 importDeclarations.push(stmt); 1907} 1908 1909function processNamedImports(namedImports: ts.NamedImports, typeAliasMap: Map<string, string>): void { 1910 namedImports.elements.forEach((element) => { 1911 const originalName = element.propertyName ? element.propertyName.text : element.name.text; 1912 const alias = element.propertyName ? element.name.text : originalName; 1913 typeAliasMap.set(alias, originalName); 1914 }); 1915} 1916 1917function extractTypeNodes( 1918 node: ts.TypeParameterDeclaration, 1919 context: ts.TransformationContext 1920): { 1921 unionTypeNode: ts.TypeNode | undefined; 1922 typeNode: ts.TypeNode | undefined; 1923} { 1924 let unionTypeNode: ts.TypeNode | undefined; 1925 let typeNode: ts.TypeNode | undefined; 1926 1927 node.forEachChild((parm) => { 1928 if (!ts.isIdentifier(parm)) { 1929 if (ts.isUnionTypeNode(parm)) { 1930 unionTypeNode = createUnionTypeNode(parm, context) as ts.TypeNode; 1931 } else if (ts.isLiteralTypeNode(parm)) { 1932 typeNode = getLiteralType(parm, context); 1933 } else { 1934 typeNode = undefined; 1935 } 1936 } 1937 }); 1938 1939 return { unionTypeNode, typeNode }; 1940} 1941 1942function getLiteralType(parm: ts.LiteralTypeNode, context: ts.TransformationContext): ts.TypeNode | undefined { 1943 if (parm.literal.kind === ts.SyntaxKind.NumericLiteral) { 1944 return context.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); 1945 } else if (parm.literal.kind === ts.SyntaxKind.TrueKeyword || parm.literal.kind === ts.SyntaxKind.FalseKeyword) { 1946 return context.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword); 1947 } 1948 return undefined; 1949} 1950 1951function createTypeParameterDeclaration( 1952 node: ts.TypeParameterDeclaration, 1953 constraint: ts.TypeNode | undefined, 1954 defaultType: ts.TypeNode | undefined, 1955 context: ts.TransformationContext 1956): ts.TypeParameterDeclaration { 1957 return context.factory.createTypeParameterDeclaration(undefined, node.name, constraint, defaultType); 1958} 1959 1960/*** 1961 * Handle initializes are not allowed in the environment content 1962 */ 1963function inferNodeTypeFromInitializer( 1964 node: ts.PropertyDeclaration, 1965 context: ts.TransformationContext 1966): ts.TypeNode | undefined { 1967 if (node.initializer !== undefined) { 1968 switch (node.initializer.kind) { 1969 case ts.SyntaxKind.FalseKeyword: 1970 case ts.SyntaxKind.TrueKeyword: 1971 return context.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword); 1972 case ts.SyntaxKind.NumericLiteral: 1973 return context.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); 1974 case ts.SyntaxKind.BigIntLiteral: 1975 return context.factory.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword); 1976 case ts.SyntaxKind.StringLiteral: 1977 return context.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); 1978 default: 1979 return undefined; 1980 } 1981 } else if (node.type !== undefined && ts.isLiteralTypeNode(node.type)) { 1982 if (isBooleanType(node.type)) { 1983 return context.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword); 1984 } else if (isBigintType(node.type)) { 1985 return context.factory.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword); 1986 } 1987 } 1988 1989 return undefined; 1990} 1991 1992function findMixedEnums(sourceFile: ts.SourceFile): { mixedEnumNames: string[]; firstPassStatements: ts.Statement[] } { 1993 let mixedEnumNames: string[] = []; 1994 const firstPassStatements: ts.Statement[] = []; 1995 1996 for (const stmt of sourceFile.statements) { 1997 if (!ts.isEnumDeclaration(stmt)) { 1998 firstPassStatements.push(stmt); 1999 continue; 2000 } 2001 2002 if (!hasMixedTypes(stmt)) { 2003 firstPassStatements.push(stmt); 2004 continue; 2005 } 2006 2007 const enumName = stmt.name.text; 2008 mixedEnumNames.push(enumName); 2009 2010 const jsValueType = ts.factory.createTypeReferenceNode(JSValue, undefined); 2011 const variableDeclaration = ts.factory.createVariableDeclaration(enumName, undefined, jsValueType, undefined); 2012 const variableDeclarationList = ts.factory.createVariableDeclarationList( 2013 [variableDeclaration], 2014 ts.NodeFlags.Const 2015 ); 2016 const variableStatement = ts.factory.createVariableStatement( 2017 [ts.factory.createToken(ts.SyntaxKind.ExportKeyword), ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], 2018 variableDeclarationList 2019 ); 2020 2021 firstPassStatements.push(variableStatement); 2022 } 2023 2024 return { mixedEnumNames, firstPassStatements }; 2025} 2026 2027function hasMixedTypes(enumDeclaration: ts.EnumDeclaration): boolean { 2028 let firstKind: ts.SyntaxKind | null = null; 2029 for (const member of enumDeclaration.members) { 2030 if (member.initializer) { 2031 const currentKind = member.initializer.kind; 2032 if (firstKind === null) { 2033 firstKind = currentKind; 2034 } else if (currentKind !== firstKind) { 2035 return true; 2036 } 2037 } 2038 } 2039 return false; 2040} 2041 2042/** 2043 * Extract enum members together. 2044 */ 2045function extractEnumMembers(item: ts.EnumDeclaration, enumbers: ts.EnumMember[]): void { 2046 item.members.forEach((member) => { 2047 if (ts.isEnumMember(member) && member.name) { 2048 const memberName = (member.name as ts.Identifier).text; 2049 if (!enumbers.some((m) => ts.isEnumMember(m) && ts.isEnumMember(member) && m.name.getText() === memberName)) { 2050 enumbers.push(member); 2051 } 2052 } 2053 }); 2054} 2055 2056/** 2057 * Merge classes or interfaces or enum with the same name 2058 */ 2059function createCombinedInterfaceAndClass( 2060 list: ts.Statement[], 2061 context: ts.TransformationContext, 2062 kindType: ts.SyntaxKind 2063): ts.Statement { 2064 const methods: ts.TypeElement[] = []; 2065 const classMethods: ts.ClassElement[] = []; 2066 const enumMembers: ts.EnumMember[] = []; 2067 2068 list.forEach((item) => { 2069 if (ts.isInterfaceDeclaration(item)) { 2070 extractInterfaceMembers(item, methods); 2071 } else if (ts.isClassDeclaration(item)) { 2072 extractClassMembers(item, classMethods); 2073 } else if (ts.isEnumDeclaration(item)) { 2074 extractEnumMembers(item, enumMembers); 2075 } 2076 }); 2077 2078 if (kindType === ts.SyntaxKind.ClassDeclaration) { 2079 const classNode = list[0] as ts.ClassDeclaration; 2080 return context.factory.createClassDeclaration( 2081 classNode.modifiers, 2082 classNode.name, 2083 undefined, 2084 undefined, 2085 classMethods 2086 ); 2087 } else if (kindType === ts.SyntaxKind.EnumDeclaration) { 2088 const enumNode = list[0] as ts.EnumDeclaration; 2089 return context.factory.createEnumDeclaration(enumNode.modifiers, enumNode.name, enumMembers); 2090 } else { 2091 const node = list[0] as ts.InterfaceDeclaration; 2092 return context.factory.createInterfaceDeclaration(node.modifiers, node.name, undefined, undefined, methods); 2093 } 2094} 2095 2096/** 2097 * Find a list of interfaces or classes with the same name 2098 */ 2099function findSameInterfaceOrClassOrEnumList( 2100 statements: ts.Statement[], 2101 context: ts.TransformationContext, 2102 kindType: ts.SyntaxKind 2103): ts.Statement[] { 2104 const sameInterfaceMap = new Map<string, { count: number; nodes: ts.Statement[] }>(); 2105 statements.forEach((statement) => { 2106 let name: string | undefined; 2107 const isInterfaceDeclaration = ts.isInterfaceDeclaration(statement) && statement.name; 2108 const isClassDeclaration = ts.isClassDeclaration(statement) && statement.name; 2109 const isEnumDeclaration = ts.isEnumDeclaration(statement) && statement.name; 2110 if (isInterfaceDeclaration || isClassDeclaration || isEnumDeclaration) { 2111 name = statement.name.text; 2112 } 2113 if (name) { 2114 if (!sameInterfaceMap.has(name)) { 2115 sameInterfaceMap.set(name, { count: 0, nodes: [] }); 2116 } 2117 const entry = sameInterfaceMap.get(name)!; 2118 entry.count++; 2119 entry.nodes.push(statement); 2120 } 2121 }); 2122 const uniqueNodes: ts.Statement[] = []; 2123 const newNodes: ts.Statement[] = []; 2124 sameInterfaceMap.forEach((entry) => { 2125 if (entry.count === 1) { 2126 uniqueNodes.push(...entry.nodes); 2127 } else { 2128 // Reorganize interface information 2129 const newInterface = createCombinedInterfaceAndClass(entry.nodes, context, kindType); 2130 newNodes.push(newInterface); 2131 } 2132 }); 2133 2134 return [...uniqueNodes, ...newNodes]; 2135} 2136 2137function restrictIdentifierName( 2138 node: ts.PropertyDeclaration | ts.MethodDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration | ts.ImportSpecifier | ts.ExportSpecifier 2139): ts.VisitResult<ts.Node> { 2140 const restrictedNames: ReadonlySet<string> = new Set(ETSKeyword); 2141 2142 if ( 2143 ts.isPropertyDeclaration(node) || 2144 ts.isMethodDeclaration(node) || 2145 ts.isClassDeclaration(node) || 2146 ts.isInterfaceDeclaration(node) || 2147 ts.isImportSpecifier(node) || 2148 ts.isExportSpecifier(node) 2149 ) { 2150 return restrictDeclarationName(node, restrictedNames); 2151 } 2152 2153 return node; 2154} 2155 2156function restrictDeclarationName( 2157 node: ts.PropertyDeclaration | ts.MethodDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration | ts.ImportSpecifier | ts.ExportSpecifier, 2158 restrictedNames: ReadonlySet<string> 2159): ts.VisitResult<ts.Node> { 2160 const name = node.name; 2161 2162 if (name && ts.isIdentifier(name) && restrictedNames.has(name.text)) { 2163 return undefined; 2164 } 2165 2166 return node; 2167} 2168 2169function exportDefaultAssignment( 2170 node: ts.FunctionDeclaration | ts.ClassDeclaration, 2171 context: ts.TransformationContext 2172): ts.VisitResult<ts.Node> { 2173 const modifiers = node.modifiers; 2174 2175 if (modifiers === undefined) { 2176 return node; 2177 } 2178 2179 if (modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword)) { 2180 const newModifiers = [...modifiers]; 2181 2182 if (!modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword)) { 2183 const declareModifier = context.factory.createModifier(ts.SyntaxKind.DeclareKeyword); 2184 2185 const defaultIndex = modifiers.findIndex(mod => mod.kind === ts.SyntaxKind.DefaultKeyword); 2186 if (defaultIndex !== -1) { 2187 newModifiers.splice(defaultIndex + 1, 0, declareModifier); 2188 } else { 2189 newModifiers.push(declareModifier); 2190 } 2191 } 2192 2193 const safeModifiers = newModifiers as ts.Modifier[]; 2194 2195 return updateNodeWithModifiers(node, safeModifiers, context); 2196 } 2197 2198 return node; 2199} 2200 2201function updateNodeWithModifiers( 2202 node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration, 2203 modifiers: ts.Modifier[], 2204 context: ts.TransformationContext 2205): ts.VisitResult<ts.Node> { 2206 if (ts.isFunctionDeclaration(node)) { 2207 return updateFunctionDeclarationWithModifiers(node, modifiers, context); 2208 } else if (ts.isClassDeclaration(node)) { 2209 return updateClassDeclarationWithModifiers(node, modifiers, context); 2210 } 2211 2212 return node; 2213} 2214 2215function updateFunctionDeclarationWithModifiers( 2216 node: ts.FunctionDeclaration, 2217 modifiers: ts.Modifier[], 2218 context: ts.TransformationContext 2219): ts.FunctionDeclaration { 2220 return context.factory.updateFunctionDeclaration( 2221 node, 2222 modifiers, 2223 node.asteriskToken, 2224 node.name, 2225 node.typeParameters, 2226 node.parameters, 2227 node.type, 2228 node.body 2229 ); 2230} 2231 2232function updateClassDeclarationWithModifiers( 2233 node: ts.ClassDeclaration, 2234 modifiers: ts.Modifier[], 2235 context: ts.TransformationContext 2236): ts.ClassDeclaration { 2237 return context.factory.updateClassDeclaration( 2238 node, 2239 modifiers, 2240 node.name, 2241 node.typeParameters, 2242 node.heritageClauses, 2243 node.members 2244 ); 2245} 2246 2247function updateInterfaceDeclarationWithModifiers( 2248 node: ts.InterfaceDeclaration, 2249 modifiers: ts.Modifier[], 2250 context: ts.TransformationContext 2251): ts.InterfaceDeclaration { 2252 return context.factory.updateInterfaceDeclaration( 2253 node, 2254 modifiers, 2255 node.name, 2256 node.typeParameters, 2257 node.heritageClauses, 2258 node.members 2259 ); 2260} 2261 2262function tranDeclarationList<T extends ts.ClassDeclaration | ts.InterfaceDeclaration>( 2263 declarations: T[], 2264 context: ts.TransformationContext, 2265 promises: string[] 2266): T[] { 2267 return declarations.map((decl) => { 2268 const heritageClause = decl.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword); 2269 const expressions = expressionList(heritageClause?.types); 2270 2271 if (shouldCreateTypeAlias(decl, expressions, promises)) { 2272 return createTypeAliasForClass(decl, context) as T; 2273 } else { 2274 return decl; 2275 } 2276 }); 2277} 2278 2279function shouldCreateTypeAlias<T extends ts.ClassDeclaration | ts.InterfaceDeclaration>( 2280 decl: T, 2281 expressions: string[], 2282 promises: string[] 2283): boolean { 2284 if (ts.isInterfaceDeclaration(decl) && decl.typeParameters && decl.typeParameters.length > 0) { 2285 return true; 2286 } 2287 2288 return expressions.some(className => FINAL_CLASS.includes(className)) && 2289 !promises.some(promise => expressions.includes(promise)); 2290} 2291 2292function tranClassDeclarationList( 2293 classDeclarations: ts.ClassDeclaration[], 2294 context: ts.TransformationContext, 2295 promises: string[] 2296): ts.ClassDeclaration[] { 2297 return tranDeclarationList(classDeclarations, context, promises); 2298} 2299 2300function createTypeAliasForClass( 2301 decl: ts.ClassDeclaration | ts.InterfaceDeclaration, 2302 context: ts.TransformationContext 2303): ts.TypeAliasDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration { 2304 if (decl.modifiers !== undefined && decl.name !== undefined) { 2305 return context.factory.createTypeAliasDeclaration( 2306 filterModifiers(decl.modifiers as ts.NodeArray<ts.Modifier>), 2307 decl.name, 2308 decl.typeParameters, 2309 context.factory.createTypeReferenceNode(JSValue) 2310 ); 2311 } else { 2312 return decl; 2313 } 2314} 2315 2316function filterModifiers(modifiers: ts.NodeArray<ts.Modifier>): ts.NodeArray<ts.Modifier> | undefined { 2317 const filteredModifiers = modifiers.filter(mod => mod.kind !== undefined && mod.kind !== ts.SyntaxKind.DeclareKeyword); 2318 return filteredModifiers.length > 0 ? 2319 ts.factory.createNodeArray(filteredModifiers, modifiers.hasTrailingComma) : 2320 undefined; 2321} 2322 2323function tranInterfaceDeclarationList( 2324 interfaceDeclarations: ts.InterfaceDeclaration[], 2325 context: ts.TransformationContext, 2326 promises: string[] 2327): ts.InterfaceDeclaration[] { 2328 return tranDeclarationList(interfaceDeclarations, context, promises); 2329} 2330 2331function expressionList(typeArguments?: readonly ts.ExpressionWithTypeArguments[]): string[] { 2332 let expressions: string[] = []; 2333 if (typeArguments !== undefined && typeArguments.length > 0) { 2334 for (const arg of typeArguments) { 2335 if (arg.expression !== undefined && ts.isIdentifier(arg.expression)) { 2336 expressions.push(arg.expression.text); 2337 } 2338 } 2339 } 2340 return expressions; 2341} 2342 2343function isFinalClassImport(element: ts.ImportSpecifier): boolean { 2344 return FINAL_CLASS.includes(element.name.text); 2345} 2346 2347function collectFinalClassImports(namedImports: ts.NamedImports, promises: string[]): void { 2348 namedImports.elements.forEach((element) => { 2349 if (ts.isImportSpecifier(element) && isFinalClassImport(element)) { 2350 promises.push(element.name.text); 2351 } 2352 }); 2353} 2354 2355function getImportSpecifierNames(importSpecifierNodes: ts.ImportDeclaration[]): string[] { 2356 const promises: string[] = []; 2357 2358 importSpecifierNodes.forEach((item) => { 2359 if (ts.isImportDeclaration(item)) { 2360 const namedImports = item.importClause?.namedBindings as ts.NamedImports | undefined; 2361 if (namedImports !== undefined && ts.isNamedImports(namedImports)) { 2362 collectFinalClassImports(namedImports, promises); 2363 } 2364 } 2365 }); 2366 2367 return promises; 2368} 2369 2370function updatePropertyAccessExpression(node: ts.PropertyAccessExpression, context: ts.TransformationContext): ts.TypeNode | undefined { 2371 let identifiers: ts.Identifier[] = []; 2372 if (ts.isPropertyAccessExpression(node.expression)) { 2373 identifiers = [ 2374 node.expression.expression, 2375 node.expression.name 2376 ] as ts.Identifier[]; 2377 } else { 2378 identifiers = [node.expression] as ts.Identifier[]; 2379 } 2380 if (identifiers.length > 1) { 2381 return context.factory.createTypeReferenceNode( 2382 context.factory.createQualifiedName( 2383 identifiers[0], 2384 identifiers[1] 2385 ) 2386 ); 2387 } else if (identifiers.length === 1) { 2388 return context.factory.createTypeReferenceNode( 2389 identifiers[0] 2390 ); 2391 } 2392 2393 return undefined; 2394} 2395 2396// Check whether the header comment contains the @noninterop field. 2397function isNonInterop(node: ts.Node): boolean { 2398 const fullText = ts.getOriginalNode(node).getFullText(); 2399 const codeText = ts.getOriginalNode(node).getText(); 2400 const commentText = fullText.replace(codeText, ''); 2401 2402 return /\/\*\*.*?@noninterop\b.*?\*\//s.test(commentText); 2403} 2404 2405function replaceEsObjectTypeName(node: ts.TypeReferenceNode, factory: ts.NodeFactory): ts.TypeReferenceNode { 2406 return factory.updateTypeReferenceNode( 2407 node, 2408 factory.createIdentifier(JSValue), 2409 node.typeArguments 2410 ); 2411} 2412 2413/** 2414 * helper function to filter out optional(with question token) methods in class declaration. 2415 */ 2416function isNotOptionalMemberFunction(member: ts.ClassElement): boolean { 2417 if (ts.isMethodDeclaration(member) && member.questionToken) { 2418 return false; 2419 } 2420 return true; 2421} 2422