• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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