• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023-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';
17import { TsUtils } from '../utils/TsUtils';
18import { scopeContainsThis } from '../utils/functions/ContainsThis';
19import { NameGenerator } from '../utils/functions/NameGenerator';
20import { isAssignmentOperator } from '../utils/functions/isAssignmentOperator';
21import { SymbolCache } from './SymbolCache';
22import { SENDABLE_DECORATOR } from '../utils/consts/SendableAPI';
23import { DEFAULT_MODULE_NAME, PATH_SEPARATOR, SRC_AND_MAIN } from '../utils/consts/OhmUrl';
24import { STRINGLITERAL_NUMBER, STRINGLITERAL_NUMBER_ARRAY } from '../utils/consts/StringLiteral';
25import {
26  DOUBLE_DOLLAR_IDENTIFIER,
27  THIS_IDENTIFIER,
28  ATTRIBUTE_SUFFIX,
29  INSTANCE_IDENTIFIER,
30  COMMON_METHOD_IDENTIFIER,
31  APPLY_STYLES_IDENTIFIER,
32  CustomDecoratorName,
33  ARKUI_PACKAGE_NAME,
34  VALUE_IDENTIFIER,
35  INDENT_STEP,
36  ENTRY_DECORATOR_NAME,
37  ENTRY_STORAGE_PROPERITY,
38  LOCAL_STORAGE_TYPE_NAME,
39  GET_LOCAL_STORAGE_FUNC_NAME,
40  PROVIDE_DECORATOR_NAME,
41  PROVIDE_ALIAS_PROPERTY_NAME,
42  PROVIDE_ALLOW_OVERRIDE_PROPERTY_NAME,
43  NEW_PROP_DECORATOR_SUFFIX
44} from '../utils/consts/ArkuiConstants';
45import { ES_VALUE } from '../utils/consts/ESObject';
46import type { IncrementDecrementNodeInfo } from '../utils/consts/InteropAPI';
47import {
48  LOAD,
49  GET_PROPERTY,
50  SET_PROPERTY,
51  ARE_EQUAL,
52  ARE_STRICTLY_EQUAL,
53  WRAP,
54  INSTANTIATE,
55  TO_NUMBER,
56  TO_PROMISE,
57  INVOKE,
58  INVOKE_METHOD,
59  LENGTH,
60  IS_INSTANCE_OF
61} from '../utils/consts/InteropAPI';
62import { ESLIB_SHAREDARRAYBUFFER } from '../utils/consts/ConcurrentAPI';
63
64const UNDEFINED_NAME = 'undefined';
65
66const LINE_FEED = '\n';
67const CARRIAGE_RETURN_LINE_FEED = '\r\n';
68
69const NEW_LINE_SEARCH_REGEX = /\r\n|\n|\r/;
70
71const GENERATED_OBJECT_LITERAL_INTERFACE_NAME = 'GeneratedObjectLiteralInterface_';
72const GENERATED_OBJECT_LITERAL_INTERFACE_TRESHOLD = 1000;
73
74const GENERATED_OBJECT_LITERAL_CLASS_NAME = 'GeneratedObjectLiteralClass_';
75const GENERATED_OBJECT_LITERAL_CLASS_TRESHOLD = 1000;
76
77const GENERATED_OBJECT_LITERAL_INIT_INTERFACE_NAME = 'GeneratedObjectLiteralInitInterface_';
78const GENERATED_OBJECT_LITERAL_INIT_INTERFACE_TRESHOLD = 1000;
79
80const GENERATED_TYPE_LITERAL_INTERFACE_NAME = 'GeneratedTypeLiteralInterface_';
81const GENERATED_TYPE_LITERAL_INTERFACE_TRESHOLD = 1000;
82
83const GENERATED_DESTRUCT_OBJECT_NAME = 'GeneratedDestructObj_';
84const GENERATED_DESTRUCT_OBJECT_TRESHOLD = 1000;
85
86const GENERATED_DESTRUCT_ARRAY_NAME = 'GeneratedDestructArray_';
87const GENERATED_DESTRUCT_ARRAY_TRESHOLD = 1000;
88
89const GENERATED_IMPORT_VARIABLE_NAME = 'GeneratedImportVar_';
90const GENERATED_IMPORT_VARIABLE_TRESHOLD = 1000;
91
92const GENERATED_TMP_VARIABLE_NAME = 'tmp_';
93const GENERATED_TMP_VARIABLE_TRESHOLD = 1000;
94
95const SPECIAL_LIB_NAME = 'specialAutofixLib';
96
97const OBJECT_LITERAL_CLASS_CONSTRUCTOR_PARAM_NAME = 'init';
98
99const METHOD_KEYS = 'keys';
100
101interface CreateClassPropertyForObjectLiteralParams {
102  prop: ts.PropertyAssignment | ts.ShorthandPropertyAssignment;
103  enclosingStmt: ts.Node;
104  classFields: ts.PropertyDeclaration[];
105  ctorBodyStmts: ts.Statement[];
106  ctorInitProps: ts.PropertyAssignment[];
107}
108
109export interface Autofix {
110  replacementText: string;
111  start: number;
112  end: number;
113  line?: number;
114  column?: number;
115  endLine?: number;
116  endColumn?: number;
117}
118
119export class Autofixer {
120  private readonly printer: ts.Printer;
121
122  private readonly nonCommentPrinter: ts.Printer;
123
124  private readonly typeLiteralInterfaceNameGenerator = new NameGenerator(
125    GENERATED_TYPE_LITERAL_INTERFACE_NAME,
126    GENERATED_TYPE_LITERAL_INTERFACE_TRESHOLD
127  );
128
129  private readonly destructObjNameGenerator = new NameGenerator(
130    GENERATED_DESTRUCT_OBJECT_NAME,
131    GENERATED_DESTRUCT_OBJECT_TRESHOLD
132  );
133
134  private readonly destructArrayNameGenerator = new NameGenerator(
135    GENERATED_DESTRUCT_ARRAY_NAME,
136    GENERATED_DESTRUCT_ARRAY_TRESHOLD
137  );
138
139  private readonly objectLiteralInterfaceNameGenerator = new NameGenerator(
140    GENERATED_OBJECT_LITERAL_INTERFACE_NAME,
141    GENERATED_OBJECT_LITERAL_INTERFACE_TRESHOLD
142  );
143
144  private readonly objectLiteralClassNameGenerator = new NameGenerator(
145    GENERATED_OBJECT_LITERAL_CLASS_NAME,
146    GENERATED_OBJECT_LITERAL_CLASS_TRESHOLD
147  );
148
149  private readonly objectLiteralInitInterfaceNameGenerator = new NameGenerator(
150    GENERATED_OBJECT_LITERAL_INIT_INTERFACE_NAME,
151    GENERATED_OBJECT_LITERAL_INIT_INTERFACE_TRESHOLD
152  );
153
154  private readonly importVarNameGenerator = new NameGenerator(
155    GENERATED_IMPORT_VARIABLE_NAME,
156    GENERATED_IMPORT_VARIABLE_TRESHOLD
157  );
158
159  private readonly tmpVariableNameGenerator = new NameGenerator(
160    GENERATED_TMP_VARIABLE_NAME,
161    GENERATED_TMP_VARIABLE_TRESHOLD
162  );
163
164  private modVarName: string = '';
165  private readonly lastImportEndMap = new Map<string, number>();
166
167  private readonly symbolCache: SymbolCache;
168
169  private readonly renameSymbolAsIdentifierCache = new Map<ts.Symbol, Autofix[] | undefined>();
170
171  private readonly enumMergingCache = new Map<ts.Symbol, Autofix[] | undefined>();
172
173  private readonly privateIdentifierCache = new Map<ts.Symbol, Autofix[] | undefined>();
174
175  private readonly sendableDecoratorCache = new Map<ts.Declaration, Autofix[] | undefined>();
176
177  private readonly newLine: string;
178
179  constructor(
180    private readonly typeChecker: ts.TypeChecker,
181    private readonly utils: TsUtils,
182    readonly sourceFile: ts.SourceFile,
183    readonly cancellationToken?: ts.CancellationToken
184  ) {
185    this.symbolCache = new SymbolCache(this.typeChecker, this.utils, sourceFile, cancellationToken);
186    this.newLine = Autofixer.getNewLineCharacterFromSrcFile(sourceFile);
187
188    const tsNewLineKind =
189      this.newLine === CARRIAGE_RETURN_LINE_FEED ? ts.NewLineKind.CarriageReturnLineFeed : ts.NewLineKind.LineFeed;
190    this.printer = ts.createPrinter({
191      omitTrailingSemicolon: false,
192      removeComments: false,
193      newLine: tsNewLineKind
194    });
195    this.nonCommentPrinter = ts.createPrinter({
196      omitTrailingSemicolon: false,
197      removeComments: true,
198      newLine: tsNewLineKind
199    });
200  }
201
202  private static getNewLineCharacterFromSrcFile(srcFile: ts.SourceFile): string {
203    const match = srcFile.text.match(NEW_LINE_SEARCH_REGEX);
204    return match ? match[0] : LINE_FEED;
205  }
206
207  private getNewLine(srcFile?: ts.SourceFile): string {
208    return srcFile ? Autofixer.getNewLineCharacterFromSrcFile(srcFile) : this.newLine;
209  }
210
211  /**
212   * Generates the text representation for destructuring elements in an object.
213   * @param variableDeclarationMap - Map of property names to variable names.
214   * @param newObjectName - Name of the new object to destructure.
215   * @param declarationFlags - Flags for the variable declaration.
216   * @param printer - TypeScript printer instance.
217   * @param sourceFile - Source file from which the nodes are taken.
218   * @returns The generated destructuring text.
219   */
220  private genDestructElementTextForObjDecls(
221    variableDeclarationMap: Map<string, string>,
222    newObjectName: string,
223    declarationFlags: ts.NodeFlags,
224    printer: ts.Printer,
225    sourceFile: ts.SourceFile
226  ): string {
227    let destructElementText: string = '';
228
229    variableDeclarationMap.forEach((propertyName, variableName) => {
230      // Create a property access expression for the new object
231      const propertyAccessExpr = ts.factory.createPropertyAccessExpression(
232        ts.factory.createIdentifier(newObjectName),
233        ts.factory.createIdentifier(variableName)
234      );
235
236      // Create a variable declaration with the property access expression
237      const variableDecl = ts.factory.createVariableDeclaration(
238        ts.factory.createIdentifier(propertyName),
239        undefined,
240        undefined,
241        propertyAccessExpr
242      );
243
244      // Create a variable statement for the variable declaration
245      const variableStatement = ts.factory.createVariableStatement(
246        undefined,
247        ts.factory.createVariableDeclarationList([variableDecl], declarationFlags)
248      );
249
250      // Print the variable statement to text and append it
251      const text = printer.printNode(ts.EmitHint.Unspecified, variableStatement, sourceFile);
252      destructElementText += text + this.getNewLine();
253    });
254
255    return destructElementText;
256  }
257
258  /**
259   * Creates autofix suggestions for destructuring assignment.
260   * @param variableDeclaration - The variable declaration to fix.
261   * @param newObjectName - Name of the new object to use for destructuring.
262   * @param destructElementText - Generated text for destructuring elements.
263   * @returns Array of autofix suggestions or undefined.
264   */
265  private genAutofixForObjDecls(
266    variableDeclaration: ts.VariableDeclaration,
267    newObjectName: string | undefined,
268    destructElementText: string,
269    isIdentifier: boolean
270  ): Autofix[] | undefined {
271    let variableNameReplaceText: Autofix;
272    let destructElementReplaceText: Autofix;
273    let autofix: Autofix[] | undefined = [];
274
275    // Check if the initializer is a identifier
276    if (isIdentifier) {
277      destructElementReplaceText = {
278        replacementText: destructElementText,
279        start: variableDeclaration.parent.getStart(),
280        end: variableDeclaration.parent.parent.getEnd()
281      };
282      autofix = [destructElementReplaceText];
283    } else {
284      // Create autofix suggestions for both variable name and destructuring
285      variableNameReplaceText = {
286        replacementText: newObjectName as string,
287        start: variableDeclaration.name.getStart(),
288        end: variableDeclaration.name.getEnd()
289      };
290      destructElementReplaceText = {
291        replacementText: this.getNewLine() + destructElementText,
292        start: variableDeclaration.parent.parent.getEnd(),
293        end: variableDeclaration.parent.parent.getEnd()
294      };
295      autofix = [variableNameReplaceText, destructElementReplaceText];
296    }
297
298    return autofix;
299  }
300
301  /**
302   * Checks if the given variable declaration passes boundary checks for object declarations.
303   *
304   * @param faultId - The fault ID indicating the type of check to perform (e.g., destructuring parameter).
305   * @param variableDeclaration - The variable or parameter declaration to check for boundary conditions.
306   * @returns A boolean indicating if the declaration passes the boundary checks.
307   */
308  private static passBoundaryCheckForObjDecls(variableDeclaration: ts.VariableDeclaration): boolean {
309    // Check if the fault ID is for a destructuring parameter or if the declaration has a spread operator
310    if (
311      TsUtils.destructuringDeclarationHasSpreadOperator(variableDeclaration.name as ts.BindingPattern) ||
312      TsUtils.destructuringDeclarationHasDefaultValue(variableDeclaration.name as ts.BindingPattern)
313    ) {
314      return false;
315    }
316
317    // If the initializer is an object literal expression, check its properties
318    if (ts.isObjectLiteralExpression(variableDeclaration.initializer as ts.Node)) {
319      const len = (variableDeclaration.initializer as ts.ObjectLiteralExpression).properties.length;
320      if (len === 0) {
321        // Return false if there are no properties
322        return false;
323      }
324    }
325
326    // Return true if all checks are passed
327    return true;
328  }
329
330  /**
331   ** Fixes object binding pattern declarations by generating appropriate autofix suggestions.
332   * @param variableDeclaration - The variable declaration to fix.
333   * @param faultId - The fault ID indicating the type of check to perform.
334   * @returns Array of autofix suggestions or undefined.
335   */
336  fixObjectBindingPatternDeclarations(variableDeclaration: ts.VariableDeclaration): Autofix[] | undefined {
337    if (!Autofixer.passBoundaryCheckForObjDecls(variableDeclaration)) {
338      return undefined;
339    }
340    // Map to hold variable names and their corresponding property names
341    const variableDeclarationMap: Map<string, string> = new Map();
342    // If the declaration is an object binding pattern, extract names
343    if (ts.isObjectBindingPattern(variableDeclaration.name)) {
344      variableDeclaration.name.elements.forEach((element) => {
345        if (!element.propertyName) {
346          variableDeclarationMap.set(element.name.getText(), element.name.getText());
347        } else {
348          variableDeclarationMap.set((element.propertyName as ts.Identifier).text, element.name.getText());
349        }
350      });
351    }
352    const sourceFile = variableDeclaration.getSourceFile();
353    let newObjectName: string | undefined;
354    const isIdentifier = ts.isIdentifier(variableDeclaration.initializer as ts.Node);
355    // If it is identifer, use its text as the new object name; otherwise, generate a unique name
356    if (isIdentifier) {
357      newObjectName = variableDeclaration.initializer?.getText();
358    } else {
359      newObjectName = TsUtils.generateUniqueName(this.destructObjNameGenerator, sourceFile);
360    }
361    if (!newObjectName) {
362      return undefined;
363    }
364    const declarationFlags = ts.getCombinedNodeFlags(variableDeclaration);
365    const destructElementText = this.genDestructElementTextForObjDecls(
366      variableDeclarationMap,
367      newObjectName,
368      declarationFlags,
369      this.printer,
370      sourceFile
371    );
372
373    // Generate and return autofix suggestions for the object declarations
374    return this.genAutofixForObjDecls(variableDeclaration, newObjectName, destructElementText, isIdentifier);
375  }
376
377  /**
378   * Generates the text representation for destructuring elements in an array.
379   * @param variableNames - Array of variable names corresponding to array elements.
380   * @param newArrayName - Name of the new array to destructure.
381   * @param declarationFlags - Flags for the variable declaration.
382   * @param printer - TypeScript printer instance.
383   * @param sourceFile - Source file from which the nodes are taken.
384   * @returns The generated destructuring text.
385   */
386  private genDestructElementTextForArrayDecls(
387    variableNames: string[],
388    newArrayName: string,
389    declarationFlags: ts.NodeFlags,
390    printer: ts.Printer,
391    sourceFile: ts.SourceFile
392  ): string {
393    let destructElementText: string = '';
394    const length = variableNames.length;
395
396    // Iterate over the array of variable names
397    for (let i = 0; i < length; i++) {
398      const variableName = variableNames[i];
399
400      // Create an element access expression for the new array
401      const elementAccessExpr = ts.factory.createElementAccessExpression(
402        ts.factory.createIdentifier(newArrayName),
403        ts.factory.createNumericLiteral(i)
404      );
405
406      // Create a variable declaration with the element access expression
407      const variableDecl = ts.factory.createVariableDeclaration(
408        ts.factory.createIdentifier(variableName),
409        undefined,
410        undefined,
411        elementAccessExpr
412      );
413
414      // Create a variable statement for the variable declaration
415      const variableStatement = ts.factory.createVariableStatement(
416        undefined,
417        ts.factory.createVariableDeclarationList([variableDecl], declarationFlags)
418      );
419
420      // Print the variable statement to text and append it
421      const text = printer.printNode(ts.EmitHint.Unspecified, variableStatement, sourceFile);
422      destructElementText += text + this.getNewLine();
423    }
424
425    return destructElementText;
426  }
427
428  /**
429   * Creates autofix suggestions for array destructuring assignment.
430   * @param variableDeclaration - The variable declaration to fix.
431   * @param newArrayName - Name of the new array to use for destructuring.
432   * @param destructElementText - Generated text for destructuring elements.
433   * @returns Array of autofix suggestions.
434   */
435  private genAutofixForArrayDecls(
436    variableDeclaration: ts.VariableDeclaration,
437    newArrayName: string | undefined,
438    destructElementText: string,
439    isIdentifierOrElementAccess: boolean
440  ): Autofix[] {
441    let variableNameReplaceText: Autofix;
442    let destructElementReplaceText: Autofix;
443    let autofix: Autofix[] = [];
444
445    // Check if the initializer is an identifier or element access expression
446    if (isIdentifierOrElementAccess) {
447      destructElementReplaceText = {
448        replacementText: destructElementText,
449        start: variableDeclaration.parent.getStart(),
450        end: variableDeclaration.parent.parent.getEnd()
451      };
452      autofix = [destructElementReplaceText];
453    } else {
454      // Create autofix suggestions for both variable name and destructuring
455      variableNameReplaceText = {
456        replacementText: newArrayName as string,
457        start: variableDeclaration.name.getStart(),
458        end: variableDeclaration.name.getEnd()
459      };
460      destructElementReplaceText = {
461        replacementText: this.getNewLine() + destructElementText,
462        start: variableDeclaration.parent.parent.getEnd(),
463        end: variableDeclaration.parent.parent.getEnd()
464      };
465      autofix = [variableNameReplaceText, destructElementReplaceText];
466    }
467
468    return autofix;
469  }
470
471  /**
472   * Checks if the given variable declaration passes boundary checks for array or tuple declarations.
473   *
474   * @param variableDeclaration - A variable declaration or parameter declaration to be checked.
475   * @param isArrayOrTuple - A boolean indicating whether the declaration is an array or tuple.
476   * @returns A boolean indicating if the declaration passes the boundary checks.
477   */
478  private static passBoundaryCheckForArrayDecls(
479    variableDeclaration: ts.VariableDeclaration,
480    isArrayOrTuple: boolean
481  ): boolean {
482    // If it's not an array/tuple or the declaration has a spread operator in destructuring
483    if (
484      !isArrayOrTuple ||
485      TsUtils.destructuringDeclarationHasSpreadOperator(variableDeclaration.name as ts.BindingPattern) ||
486      TsUtils.destructuringDeclarationHasDefaultValue(variableDeclaration.name as ts.BindingPattern)
487    ) {
488      // Return false if it fails the boundary check
489      return false;
490    }
491
492    // Check if the array binding pattern has any empty elements
493    if (TsUtils.checkArrayBindingHasEmptyElement(variableDeclaration.name as ts.ArrayBindingPattern)) {
494      // Return false if it contains empty elements
495      return false;
496    }
497
498    // Check if the initializer has the same dimension as expected
499    if (!TsUtils.isSameDimension(variableDeclaration.initializer as ts.Node)) {
500      return false;
501    }
502
503    // Return true if all checks are passed
504    return true;
505  }
506
507  /**
508   * Fixes array binding pattern declarations by generating appropriate autofix suggestions.
509   *
510   * @param variableDeclaration - The variable declaration to fix.
511   * @param isArrayOrTuple - Flag indicating if the declaration is for an array or tuple.
512   * @returns Array of autofix suggestions or undefined.
513   */
514  fixArrayBindingPatternDeclarations(
515    variableDeclaration: ts.VariableDeclaration,
516    isArrayOrTuple: boolean
517  ): Autofix[] | undefined {
518    if (!Autofixer.passBoundaryCheckForArrayDecls(variableDeclaration, isArrayOrTuple)) {
519      return undefined;
520    }
521    const variableNames: string[] = [];
522    // If the declaration is an array binding pattern, extract variable names
523    if (ts.isArrayBindingPattern(variableDeclaration.name)) {
524      variableDeclaration.name.elements.forEach((element) => {
525        variableNames.push(element.getText());
526      });
527    }
528
529    const sourceFile = variableDeclaration.getSourceFile();
530    let newArrayName: string | undefined = '';
531    // Check if the initializer is either an identifier or an element access expression
532    const isIdentifierOrElementAccess =
533      ts.isIdentifier(variableDeclaration.initializer as ts.Node) ||
534      ts.isElementAccessExpression(variableDeclaration.initializer as ts.Node);
535    // If it is, use its text as the new array name; otherwise, generate a unique name
536    if (isIdentifierOrElementAccess) {
537      newArrayName = variableDeclaration.initializer?.getText();
538    } else {
539      newArrayName = TsUtils.generateUniqueName(this.destructArrayNameGenerator, sourceFile);
540    }
541    if (!newArrayName) {
542      return undefined;
543    }
544
545    // Get the combined node flags for the variable declaration
546    const declarationFlags = ts.getCombinedNodeFlags(variableDeclaration);
547    // Generate the destructuring element text for the array declaration
548    const destructElementText = this.genDestructElementTextForArrayDecls(
549      variableNames,
550      newArrayName,
551      declarationFlags,
552      this.printer,
553      sourceFile
554    );
555
556    // Generate and return autofix suggestions for the array declarations
557    return this.genAutofixForArrayDecls(
558      variableDeclaration,
559      newArrayName,
560      destructElementText,
561      isIdentifierOrElementAccess
562    );
563  }
564
565  /**
566   * Generates the text representation for destructuring assignments in an array.
567   * @param variableNames - Array of variable names corresponding to array elements.
568   * @param newArrayName - Name of the new array to use for destructuring.
569   * @param printer - TypeScript printer instance.
570   * @param sourceFile - Source file from which the nodes are taken.
571   * @returns The generated destructuring assignment text.
572   */
573  private genDestructElementTextForArrayAssignment(
574    variableNames: string[],
575    newArrayName: string | undefined,
576    printer: ts.Printer,
577    sourceFile: ts.SourceFile
578  ): string {
579    let destructElementText: string = '';
580    const length = variableNames.length;
581
582    // Iterate over the array of variable names
583    for (let i = 0; i < length; i++) {
584      const variableName = variableNames[i];
585
586      // Create an element access expression for the new array
587      const elementAccessExpr = ts.factory.createElementAccessExpression(
588        ts.factory.createIdentifier(newArrayName as string),
589        ts.factory.createNumericLiteral(i)
590      );
591
592      // Create a binary expression to assign the array element to the variable
593      const assignmentExpr = ts.factory.createBinaryExpression(
594        ts.factory.createIdentifier(variableName),
595        ts.factory.createToken(ts.SyntaxKind.EqualsToken),
596        elementAccessExpr
597      );
598
599      // Create an expression statement for the assignment expression
600      const expressionStatement = ts.factory.createExpressionStatement(assignmentExpr);
601
602      // Print the expression statement to text and append it
603      const text = printer.printNode(ts.EmitHint.Unspecified, expressionStatement, sourceFile);
604      destructElementText += text + this.getNewLine();
605    }
606
607    return destructElementText;
608  }
609
610  /**
611   * Creates autofix suggestions for array destructuring assignment.
612   * @param assignmentExpr - The binary expression for the assignment.
613   * @param newArrayName - Name of the new array to use for destructuring.
614   * @param destructElementText - Generated text for destructuring assignments.
615   * @returns Array of autofix suggestions.
616   */
617  private genAutofixForArrayAssignment(
618    assignmentExpr: ts.BinaryExpression,
619    newArrayName: string | undefined,
620    destructElementText: string,
621    isIdentifierOrElementAccess: boolean
622  ): Autofix[] {
623    let arrayNameReplaceText: Autofix;
624    let destructElementReplaceText: Autofix;
625    let autofix: Autofix[] = [];
626
627    // Check if the right side of the assignment is an identifier or element access expression
628    if (isIdentifierOrElementAccess) {
629      destructElementReplaceText = {
630        replacementText: destructElementText,
631        start: assignmentExpr.parent.getStart(),
632        end: assignmentExpr.parent.getEnd()
633      };
634      autofix = [destructElementReplaceText];
635    } else {
636      // Create autofix suggestions for both array name and destructuring assignments
637      const keywordsLet = 'let ';
638      arrayNameReplaceText = {
639        replacementText: keywordsLet + newArrayName,
640        start: assignmentExpr.left.getStart(),
641        end: assignmentExpr.left.getEnd()
642      };
643      destructElementReplaceText = {
644        replacementText: this.getNewLine() + destructElementText,
645        start: assignmentExpr.parent.getEnd(),
646        end: assignmentExpr.parent.getEnd()
647      };
648      autofix = [arrayNameReplaceText, destructElementReplaceText];
649    }
650
651    return autofix;
652  }
653
654  /**
655   * Checks if the given assignment expression passes boundary checks for array assignments.
656   *
657   * @param assignmentExpr - The assignment expression to check (a binary expression).
658   * @param isArrayOrTuple - A boolean indicating if the assignment is for an array or tuple.
659   * @returns A boolean indicating if the assignment passes the boundary checks.
660   */
661  private static passBoundaryCheckForArrayAssignment(
662    assignmentExpr: ts.BinaryExpression,
663    isArrayOrTuple: boolean
664  ): boolean {
665    // Return false if the assignment is not for an array or tuple
666    if (!isArrayOrTuple) {
667      return false;
668    }
669
670    // Check if the left side of the assignment is an array literal expression with a spread operator
671    if (TsUtils.destructuringAssignmentHasSpreadOperator(assignmentExpr.left as ts.ArrayLiteralExpression)) {
672      return false;
673    }
674
675    if (TsUtils.destructuringAssignmentHasDefaultValue(assignmentExpr.left as ts.ArrayLiteralExpression)) {
676      return false;
677    }
678
679    // Check if the left side of the assignment has an empty element
680    if (TsUtils.checkArrayLiteralHasEmptyElement(assignmentExpr.left as ts.ArrayLiteralExpression)) {
681      return false;
682    }
683
684    // Check if the right side of the assignment has the same dimension as the left side
685    if (!TsUtils.isSameDimension(assignmentExpr.right)) {
686      return false;
687    }
688
689    // Return true if all boundary checks are passed
690    return true;
691  }
692
693  /**
694   * Fixes array binding pattern assignments by generating appropriate autofix suggestions.
695   * @param assignmentExpr - The binary expression for the assignment.
696   * @param isArrayOrTuple - Flag indicating if the assignment is for an array or tuple.
697   * @returns Array of autofix suggestions or undefined.
698   */
699  fixArrayBindingPatternAssignment(
700    assignmentExpr: ts.BinaryExpression,
701    isArrayOrTuple: boolean
702  ): Autofix[] | undefined {
703    if (!Autofixer.passBoundaryCheckForArrayAssignment(assignmentExpr, isArrayOrTuple)) {
704      return undefined;
705    }
706    // Collect variable names from array literal expression
707    const variableNames: string[] = [];
708    if (ts.isArrayLiteralExpression(assignmentExpr.left)) {
709      assignmentExpr.left.elements.forEach((element) => {
710        variableNames.push(element.getText());
711      });
712    }
713
714    const sourceFile = assignmentExpr.getSourceFile();
715    let newArrayName: string | undefined = '';
716    const isIdentifierOrElementAccess =
717      ts.isIdentifier(assignmentExpr.right) || ts.isElementAccessExpression(assignmentExpr.right as ts.Node);
718    if (isIdentifierOrElementAccess) {
719      newArrayName = assignmentExpr.right.getText();
720    } else {
721      newArrayName = TsUtils.generateUniqueName(this.destructArrayNameGenerator, sourceFile);
722    }
723    if (!newArrayName) {
724      return undefined;
725    }
726
727    // Generate the text for destructuring assignments
728    const destructElementText = this.genDestructElementTextForArrayAssignment(
729      variableNames,
730      newArrayName,
731      this.printer,
732      sourceFile
733    );
734
735    return this.genAutofixForArrayAssignment(
736      assignmentExpr,
737      newArrayName,
738      destructElementText,
739      isIdentifierOrElementAccess
740    );
741  }
742
743  /**
744   * Creates a mapping of variable declarations and needParentheses for object properties based on the provided binary expression.
745   * @param binaryExpr - The binary expression containing the object literal.
746   * @returns An object containing the variable declaration map and needParentheses indicating if property initializers are object literals.
747   */
748  private static genTsVarDeclMapAndFlags(binaryExpr: ts.BinaryExpression): {
749    tsVarDeclMap: Map<string, string>;
750    needParentheses: boolean[];
751  } {
752    const tsVarDeclMap: Map<string, string> = new Map();
753    const needParentheses: boolean[] = [];
754
755    // Check if the left side of the binary expression is an object literal
756    if (ts.isObjectLiteralExpression(binaryExpr.left)) {
757      binaryExpr.left.properties.forEach((property) => {
758        // Handle property assignments with initializer
759        if (ts.isPropertyAssignment(property)) {
760          tsVarDeclMap.set(property.name?.getText(), property.initializer.getText());
761          needParentheses.push(ts.isObjectLiteralExpression(property.initializer));
762        } else if (ts.isShorthandPropertyAssignment(property)) {
763          tsVarDeclMap.set(property.name?.getText(), property.name.getText());
764          needParentheses.push(false);
765        }
766      });
767    }
768
769    return { tsVarDeclMap, needParentheses };
770  }
771
772  /**
773   * Generates the text for destructuring assignments based on the variable declaration map and needParentheses.
774   * @param tsVarDeclMap - Map of variable names to property names.
775   * @param needParentheses - Array of needParentheses indicating if property initializers are object literals.
776   * @param newObjName - The name of the new object to use for destructuring.
777   * @param binaryExpr - The binary expression representing the destructuring.
778   * @param printer - TypeScript printer instance for printing nodes.
779   * @returns The generated text for destructuring assignments.
780   */
781  private genDestructElementTextForObjAssignment(
782    tsVarDeclMap: Map<string, string>,
783    needParentheses: boolean[],
784    newObjName: string,
785    binaryExpr: ts.BinaryExpression,
786    printer: ts.Printer
787  ): string {
788    let destructElementText: string = '';
789    let index: number = 0;
790
791    // Iterate through the variable declaration map to generate destructuring assignments
792    tsVarDeclMap.forEach((propertyName, variableName) => {
793      // Create property access expression for the new object
794      const propAccessExpr = ts.factory.createPropertyAccessExpression(
795        ts.factory.createIdentifier(newObjName),
796        ts.factory.createIdentifier(variableName)
797      );
798      // Create binary expression for assignment
799      const assignmentExpr = ts.factory.createBinaryExpression(
800        ts.factory.createIdentifier(propertyName),
801        ts.factory.createToken(ts.SyntaxKind.EqualsToken),
802        propAccessExpr
803      );
804      // Create statement for the assignment expression, with or without parentheses based on the flag
805      const statement = needParentheses[index] ?
806        ts.factory.createExpressionStatement(ts.factory.createParenthesizedExpression(assignmentExpr)) :
807        ts.factory.createExpressionStatement(assignmentExpr);
808
809      // Append the generated text for the destructuring assignment
810      destructElementText +=
811        printer.printNode(ts.EmitHint.Unspecified, statement, binaryExpr.getSourceFile()) + this.getNewLine();
812
813      index++;
814    });
815
816    return destructElementText;
817  }
818
819  /**
820   * Creates the replacement text for the variable declaration name.
821   * @param binaryExpr - The binary expression containing the object literal or call expression.
822   * @param newObjName - The new object name to be used in the replacement.
823   * @param printer - TypeScript printer instance for printing nodes.
824   * @returns The replacement text for the variable declaration name.
825   */
826  private static genDeclNameReplaceTextForObjAssignment(
827    binaryExpr: ts.BinaryExpression,
828    newObjName: string,
829    printer: ts.Printer
830  ): string {
831    let declNameReplaceText = '';
832
833    // create variableDeclList and get declNameReplaceText text
834    const variableDecl = ts.factory.createVariableDeclaration(
835      ts.factory.createIdentifier(newObjName),
836      undefined,
837      undefined,
838      binaryExpr.right
839    );
840    const variableDeclList = ts.factory.createVariableDeclarationList([variableDecl], ts.NodeFlags.Let);
841    declNameReplaceText = printer.printNode(ts.EmitHint.Unspecified, variableDeclList, binaryExpr.getSourceFile());
842
843    return declNameReplaceText;
844  }
845
846  /**
847   * Creates autofix suggestions for object literal destructuring assignments.
848   * @param binaryExpr - The binary expression containing the destructuring assignment.
849   * @param declNameReplaceText - Replacement text for the variable declaration name.
850   * @param destructElementText - Generated text for destructuring assignments.
851   * @returns Array of autofix suggestions or undefined if no fixes are needed.
852   */
853  private createAutofixForObjAssignment(
854    binaryExpr: ts.BinaryExpression,
855    declNameReplaceText: string,
856    destructElementText: string,
857    isIdentifier: boolean
858  ): Autofix[] | undefined {
859    let declNameReplaceTextAutofix: Autofix;
860    let destructElementReplaceTextAutofix: Autofix;
861    let autofix: Autofix[] | undefined;
862
863    // Check if the right side of the binary expression is identifer
864    if (isIdentifier) {
865      destructElementReplaceTextAutofix = {
866        replacementText: destructElementText,
867        start: binaryExpr.parent.parent.getStart(),
868        end: binaryExpr.parent.parent.getEnd()
869      };
870      autofix = [destructElementReplaceTextAutofix];
871    } else {
872      declNameReplaceTextAutofix = {
873        replacementText: declNameReplaceText,
874        start: binaryExpr.parent.getStart(),
875        end: binaryExpr.parent.getEnd()
876      };
877      destructElementReplaceTextAutofix = {
878        replacementText: this.getNewLine() + destructElementText,
879        start: binaryExpr.parent.parent.getEnd(),
880        end: binaryExpr.parent.parent.getEnd()
881      };
882      autofix = [declNameReplaceTextAutofix, destructElementReplaceTextAutofix];
883    }
884
885    return autofix;
886  }
887
888  /**
889   * Checks if the given assignment expression passes boundary checks for object assignments.
890   *
891   * @param binaryExpr - The binary expression representing the assignment to check.
892   * @returns A boolean indicating if the assignment passes the boundary checks.
893   */
894  private static passBoundaryCheckForObjAssignment(binaryExpr: ts.BinaryExpression): boolean {
895    // Check for spread operator in destructuring assignment on the left side
896    if (TsUtils.destructuringAssignmentHasSpreadOperator(binaryExpr.left as ts.ObjectLiteralExpression)) {
897      return false;
898    }
899
900    if (TsUtils.destructuringAssignmentHasDefaultValue(binaryExpr.left as ts.ObjectLiteralExpression)) {
901      return false;
902    }
903
904    // Check if the right side is an object literal expression with no properties
905    if (ts.isObjectLiteralExpression(binaryExpr.right) && binaryExpr.right.properties.length === 0) {
906      return false;
907    }
908
909    // Return true if all boundary checks are passed
910    return true;
911  }
912
913  /**
914   * Fixes object literal expression destructuring assignments by generating autofix suggestions.
915   * @param binaryExpr - The binary expression representing the destructuring assignment.
916   * @returns Array of autofix suggestions or undefined if no fixes are needed.
917   */
918  fixObjectLiteralExpressionDestructAssignment(binaryExpr: ts.BinaryExpression): Autofix[] | undefined {
919    if (!Autofixer.passBoundaryCheckForObjAssignment(binaryExpr)) {
920      return undefined;
921    }
922    // Create a mapping of variable declarations and needParentheses
923    const { tsVarDeclMap, needParentheses } = Autofixer.genTsVarDeclMapAndFlags(binaryExpr);
924
925    const sourceFile = binaryExpr.getSourceFile();
926    let newObjName: string | undefined = '';
927    // Generate a unique name if right expression is not identifer
928    const isIdentifier = ts.isIdentifier(binaryExpr.right);
929    if (isIdentifier) {
930      newObjName = binaryExpr.right?.getText();
931    } else {
932      newObjName = TsUtils.generateUniqueName(this.destructObjNameGenerator, sourceFile);
933    }
934    if (!newObjName) {
935      return undefined;
936    }
937    // Create the text for destructuring elements
938    const destructElementText = this.genDestructElementTextForObjAssignment(
939      tsVarDeclMap,
940      needParentheses,
941      newObjName,
942      binaryExpr,
943      this.printer
944    );
945
946    // Create the replacement text for the variable declaration name
947    const declNameReplaceText = Autofixer.genDeclNameReplaceTextForObjAssignment(binaryExpr, newObjName, this.printer);
948
949    // Generate autofix suggestions
950    return this.createAutofixForObjAssignment(binaryExpr, declNameReplaceText, destructElementText, isIdentifier);
951  }
952
953  fixLiteralAsPropertyNamePropertyAssignment(node: ts.PropertyAssignment): Autofix[] | undefined {
954    const contextualType = this.typeChecker.getContextualType(node.parent);
955    if (contextualType === undefined) {
956      return undefined;
957    }
958
959    const symbol = this.utils.getPropertySymbol(contextualType, node);
960    if (symbol === undefined) {
961      return undefined;
962    }
963
964    return this.renameSymbolAsIdentifier(symbol);
965  }
966
967  fixLiteralAsPropertyNamePropertyName(node: ts.PropertyName, enumMember?: ts.EnumMember): Autofix[] | undefined {
968    const symbol = this.typeChecker.getSymbolAtLocation(node);
969    if (symbol === undefined) {
970      return undefined;
971    }
972
973    return this.renameSymbolAsIdentifier(symbol, enumMember);
974  }
975
976  renameAsObjectElementAccessExpression(node: ts.ElementAccessExpression): Autofix[] | undefined {
977    const parenExpr = ts.isParenthesizedExpression(node.expression) ? node.expression : undefined;
978    const asExpr = parenExpr && ts.isAsExpression(parenExpr.expression) ? parenExpr.expression : undefined;
979    if (!asExpr) {
980      return undefined;
981    }
982
983    const argument = node.argumentExpression;
984    const propertyName = ts.isStringLiteral(argument) ? argument.text : undefined;
985    if (!propertyName) {
986      return undefined;
987    }
988
989    const realObj = asExpr.expression;
990    const type = this.typeChecker.getTypeAtLocation(realObj);
991    const property = this.typeChecker.getPropertyOfType(type, propertyName);
992    if (!property) {
993      return undefined;
994    }
995
996    return [
997      {
998        replacementText: realObj.getText() + '.' + propertyName,
999        start: node.getStart(),
1000        end: node.getEnd()
1001      }
1002    ];
1003  }
1004
1005  fixPropertyAccessByIndex(node: ts.ElementAccessExpression): Autofix[] | undefined {
1006    if (ts.isParenthesizedExpression(node.expression) && ts.isAsExpression(node.expression.expression)) {
1007      const assertedType = this.typeChecker.getTypeAtLocation(node.expression.expression.type);
1008      if (this.typeChecker.typeToString(assertedType) === 'object') {
1009        return this.renameAsObjectElementAccessExpression(node);
1010      }
1011    }
1012
1013    const symbol = this.typeChecker.getSymbolAtLocation(node.argumentExpression);
1014    if (symbol === undefined) {
1015      return undefined;
1016    }
1017
1018    return this.renameSymbolAsIdentifier(symbol);
1019  }
1020
1021  private renameSymbolAsIdentifier(symbol: ts.Symbol, enumMember?: ts.EnumMember): Autofix[] | undefined {
1022    if (this.renameSymbolAsIdentifierCache.has(symbol)) {
1023      return this.renameSymbolAsIdentifierCache.get(symbol);
1024    }
1025
1026    if (!TsUtils.isPropertyOfInternalClassOrInterface(symbol)) {
1027      this.renameSymbolAsIdentifierCache.set(symbol, undefined);
1028      return undefined;
1029    }
1030
1031    const newName = this.utils.findIdentifierNameForSymbol(symbol, enumMember);
1032    if (newName === undefined) {
1033      this.renameSymbolAsIdentifierCache.set(symbol, undefined);
1034      return undefined;
1035    }
1036
1037    let result: Autofix[] | undefined = [];
1038    this.symbolCache.getReferences(symbol).forEach((node) => {
1039      if (result === undefined) {
1040        return;
1041      }
1042
1043      let autofix: Autofix[] | undefined;
1044      if (
1045        ts.isPropertyDeclaration(node) ||
1046        ts.isPropertyAssignment(node) ||
1047        ts.isPropertySignature(node) ||
1048        ts.isEnumMember(node)
1049      ) {
1050        autofix = Autofixer.renamePropertyName(node.name, newName);
1051      } else if (ts.isElementAccessExpression(node)) {
1052        autofix = Autofixer.renameElementAccessExpression(node, newName);
1053      }
1054
1055      if (autofix === undefined) {
1056        result = undefined;
1057        return;
1058      }
1059
1060      result.push(...autofix);
1061    });
1062    if (!result?.length) {
1063      result = undefined;
1064    }
1065
1066    this.renameSymbolAsIdentifierCache.set(symbol, result);
1067    return result;
1068  }
1069
1070  addDefaultModuleToPath(parts: string[], importDeclNode: ts.ImportDeclaration): Autofix[] | undefined {
1071    void this;
1072    const moduleSpecifier = importDeclNode.moduleSpecifier;
1073
1074    /*
1075     * check the current file's path
1076     * get the parent directory name of the "src" directory
1077     */
1078
1079    const moduleName = TsUtils.getModuleName(importDeclNode);
1080    const newPathParts = [moduleName ?? DEFAULT_MODULE_NAME, SRC_AND_MAIN, ...parts];
1081    const newPath = newPathParts.join(PATH_SEPARATOR);
1082    const newPathString = '\'' + newPath + '\'';
1083
1084    return [{ start: moduleSpecifier.getStart(), end: moduleSpecifier.getEnd(), replacementText: newPathString }];
1085  }
1086
1087  fixImportPath(parts: string[], index: number, importDeclNode: ts.ImportDeclaration): Autofix[] | undefined {
1088    void this;
1089    const moduleSpecifier = importDeclNode.moduleSpecifier;
1090
1091    const beforeEts = parts.slice(0, index);
1092    const afterEts = parts.slice(index, parts.length);
1093    const newPathParts = [...beforeEts, SRC_AND_MAIN, ...afterEts];
1094
1095    const newPath = newPathParts.join(PATH_SEPARATOR);
1096    const newPathString = '\'' + newPath + '\'';
1097
1098    return [{ start: moduleSpecifier.getStart(), end: moduleSpecifier.getEnd(), replacementText: newPathString }];
1099  }
1100
1101  private static renamePropertyName(node: ts.PropertyName, newName: string): Autofix[] | undefined {
1102    if (ts.isComputedPropertyName(node)) {
1103      return undefined;
1104    }
1105
1106    if (ts.isMemberName(node)) {
1107      if (ts.idText(node) !== newName) {
1108        return undefined;
1109      }
1110
1111      return [];
1112    }
1113
1114    return [{ replacementText: newName, start: node.getStart(), end: node.getEnd() }];
1115  }
1116
1117  private static renameElementAccessExpression(
1118    node: ts.ElementAccessExpression,
1119    newName: string
1120  ): Autofix[] | undefined {
1121    const argExprKind = node.argumentExpression.kind;
1122    if (argExprKind !== ts.SyntaxKind.NumericLiteral && argExprKind !== ts.SyntaxKind.StringLiteral) {
1123      return undefined;
1124    }
1125
1126    return [
1127      {
1128        replacementText: node.expression.getText() + '.' + newName,
1129        start: node.getStart(),
1130        end: node.getEnd()
1131      }
1132    ];
1133  }
1134
1135  fixFunctionExpression(
1136    funcExpr: ts.FunctionExpression,
1137    // eslint-disable-next-line default-param-last
1138    retType: ts.TypeNode | undefined = funcExpr.type,
1139    modifiers: readonly ts.Modifier[] | undefined,
1140    isGenerator: boolean,
1141    hasUnfixableReturnType: boolean
1142  ): Autofix[] | undefined {
1143    const hasThisKeyword = scopeContainsThis(funcExpr.body);
1144    const isCalledRecursively = this.utils.isFunctionCalledRecursively(funcExpr);
1145    if (isGenerator || hasThisKeyword || isCalledRecursively || hasUnfixableReturnType) {
1146      return undefined;
1147    }
1148
1149    let arrowFunc: ts.Expression = ts.factory.createArrowFunction(
1150      modifiers,
1151      funcExpr.typeParameters,
1152      funcExpr.parameters,
1153      retType,
1154      ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
1155      funcExpr.body
1156    );
1157    if (Autofixer.needsParentheses(funcExpr)) {
1158      arrowFunc = ts.factory.createParenthesizedExpression(arrowFunc);
1159    }
1160    const text = this.printer.printNode(ts.EmitHint.Unspecified, arrowFunc, funcExpr.getSourceFile());
1161    return [{ start: funcExpr.getStart(), end: funcExpr.getEnd(), replacementText: text }];
1162  }
1163
1164  private static isNodeInWhileOrIf(node: ts.Node): boolean {
1165    return (
1166      node.kind === ts.SyntaxKind.WhileStatement ||
1167      node.kind === ts.SyntaxKind.DoStatement ||
1168      node.kind === ts.SyntaxKind.IfStatement
1169    );
1170  }
1171
1172  private static isNodeInForLoop(node: ts.Node): boolean {
1173    return (
1174      node.kind === ts.SyntaxKind.ForInStatement ||
1175      node.kind === ts.SyntaxKind.ForOfStatement ||
1176      node.kind === ts.SyntaxKind.ForStatement
1177    );
1178  }
1179
1180  private static parentInFor(node: ts.Node): ts.Node | undefined {
1181    let parentNode = node.parent;
1182    while (parentNode) {
1183      if (Autofixer.isNodeInForLoop(parentNode)) {
1184        return parentNode;
1185      }
1186      parentNode = parentNode.parent;
1187    }
1188    return undefined;
1189  }
1190
1191  private static parentInCaseOrWhile(varDeclList: ts.VariableDeclarationList): boolean {
1192    let parentNode: ts.Node = varDeclList.parent;
1193    while (parentNode) {
1194      if (parentNode.kind === ts.SyntaxKind.CaseClause || Autofixer.isNodeInWhileOrIf(parentNode)) {
1195        return false;
1196      }
1197      parentNode = parentNode.parent;
1198    }
1199    return true;
1200  }
1201
1202  private static isFunctionLikeDeclarationKind(node: ts.Node): boolean {
1203    switch (node.kind) {
1204      case ts.SyntaxKind.FunctionDeclaration:
1205      case ts.SyntaxKind.MethodDeclaration:
1206      case ts.SyntaxKind.Constructor:
1207      case ts.SyntaxKind.GetAccessor:
1208      case ts.SyntaxKind.SetAccessor:
1209      case ts.SyntaxKind.FunctionExpression:
1210      case ts.SyntaxKind.ArrowFunction:
1211        return true;
1212      default:
1213        return false;
1214    }
1215  }
1216
1217  private static findVarScope(node: ts.Node): ts.Node {
1218    while (node !== undefined) {
1219      if (node.kind === ts.SyntaxKind.Block || node.kind === ts.SyntaxKind.SourceFile) {
1220        break;
1221      }
1222      // eslint-disable-next-line no-param-reassign
1223      node = node.parent;
1224    }
1225    return node;
1226  }
1227
1228  private static varHasScope(node: ts.Node, scope: ts.Node): boolean {
1229    while (node !== undefined) {
1230      if (node === scope) {
1231        return true;
1232      }
1233      // eslint-disable-next-line no-param-reassign
1234      node = node.parent;
1235    }
1236    return false;
1237  }
1238
1239  private static varInFunctionForScope(node: ts.Node, scope: ts.Node): boolean {
1240    while (node !== undefined) {
1241      if (Autofixer.isFunctionLikeDeclarationKind(node)) {
1242        break;
1243      }
1244      // eslint-disable-next-line no-param-reassign
1245      node = node.parent;
1246    }
1247    // node now Function like declaration
1248
1249    // node need to check that function like declaration is in scope
1250    if (Autofixer.varHasScope(node, scope)) {
1251      // var use is in function scope, which is in for scope
1252      return true;
1253    }
1254    return false;
1255  }
1256
1257  private static selfDeclared(decl: ts.Node, ident: ts.Node): boolean {
1258    // Do not check the same node
1259    if (ident === decl) {
1260      return false;
1261    }
1262
1263    while (ident !== undefined) {
1264      if (ident.kind === ts.SyntaxKind.VariableDeclaration) {
1265        const declName = (ident as ts.VariableDeclaration).name;
1266        if (declName === decl) {
1267          return true;
1268        }
1269      }
1270      // eslint-disable-next-line no-param-reassign
1271      ident = ident.parent;
1272    }
1273    return false;
1274  }
1275
1276  private static analizeTDZ(decl: ts.VariableDeclaration, identifiers: ts.Node[]): boolean {
1277    for (const ident of identifiers) {
1278      if (Autofixer.selfDeclared(decl.name, ident)) {
1279        return false;
1280      }
1281      if (ident.pos < decl.pos) {
1282        return false;
1283      }
1284    }
1285    return true;
1286  }
1287
1288  private static analizeScope(decl: ts.VariableDeclaration, identifiers: ts.Node[]): boolean {
1289    const scope = Autofixer.findVarScope(decl);
1290    if (scope === undefined) {
1291      return false;
1292    } else if (scope.kind === ts.SyntaxKind.Block) {
1293      for (const ident of identifiers) {
1294        if (!Autofixer.varHasScope(ident, scope)) {
1295          return false;
1296        }
1297      }
1298    } else if (scope.kind === ts.SyntaxKind.SourceFile) {
1299      // Do nothing
1300    } else {
1301      // Unreachable, but check it
1302      return false;
1303    }
1304    return true;
1305  }
1306
1307  private static analizeFor(decl: ts.VariableDeclaration, identifiers: ts.Node[]): boolean {
1308    const forNode = Autofixer.parentInFor(decl);
1309    if (forNode) {
1310      // analize that var is initialized
1311      if (forNode.kind === ts.SyntaxKind.ForInStatement || forNode.kind === ts.SyntaxKind.ForOfStatement) {
1312        const typedForNode = forNode as ts.ForInOrOfStatement;
1313        const forVarDeclarations = (typedForNode.initializer as ts.VariableDeclarationList).declarations;
1314        if (forVarDeclarations.length !== 1) {
1315          return false;
1316        }
1317        const forVarDecl = forVarDeclarations[0];
1318
1319        // our goal to skip declarations in for of/in initializer
1320        if (forVarDecl !== decl && decl.initializer === undefined) {
1321          return false;
1322        }
1323      } else if (decl.initializer === undefined) {
1324        return false;
1325      }
1326
1327      // analize that var uses are only in function block
1328      for (const ident of identifiers) {
1329        if (ident !== decl && !Autofixer.varHasScope(ident, forNode)) {
1330          return false;
1331        }
1332      }
1333
1334      // analize that var is not in function
1335      for (const ident of identifiers) {
1336        if (ident !== decl && Autofixer.varInFunctionForScope(ident, forNode)) {
1337          return false;
1338        }
1339      }
1340    }
1341    return true;
1342  }
1343
1344  private checkVarDeclarations(varDeclList: ts.VariableDeclarationList): boolean {
1345    for (const decl of varDeclList.declarations) {
1346      const symbol = this.typeChecker.getSymbolAtLocation(decl.name);
1347      if (!symbol) {
1348        return false;
1349      }
1350
1351      const identifiers = this.symbolCache.getReferences(symbol);
1352
1353      const declLength = symbol.declarations?.length;
1354      if (!declLength || declLength >= 2) {
1355        return false;
1356      }
1357
1358      // Check for var use in tdz oe self declaration
1359      if (!Autofixer.analizeTDZ(decl, identifiers)) {
1360        return false;
1361      }
1362
1363      // Has use outside scope of declaration?
1364      if (!Autofixer.analizeScope(decl, identifiers)) {
1365        return false;
1366      }
1367
1368      // For analisys
1369      if (!Autofixer.analizeFor(decl, identifiers)) {
1370        return false;
1371      }
1372
1373      if (symbol.getName() === 'let') {
1374        return false;
1375      }
1376    }
1377    return true;
1378  }
1379
1380  private canAutofixNoVar(varDeclList: ts.VariableDeclarationList): boolean {
1381    if (!Autofixer.parentInCaseOrWhile(varDeclList)) {
1382      return false;
1383    }
1384
1385    if (!this.checkVarDeclarations(varDeclList)) {
1386      return false;
1387    }
1388
1389    return true;
1390  }
1391
1392  fixVarDeclaration(node: ts.VariableDeclarationList): Autofix[] | undefined {
1393    const newNode = ts.factory.createVariableDeclarationList(node.declarations, ts.NodeFlags.Let);
1394    const text = this.printer.printNode(ts.EmitHint.Unspecified, newNode, node.getSourceFile());
1395    return this.canAutofixNoVar(node) ?
1396      [{ start: node.getStart(), end: node.getEnd(), replacementText: text }] :
1397      undefined;
1398  }
1399
1400  private getFixReturnTypeArrowFunction(funcLikeDecl: ts.FunctionLikeDeclaration, typeNode: ts.TypeNode): string {
1401    if (!funcLikeDecl.body) {
1402      return '';
1403    }
1404    const node = ts.factory.createArrowFunction(
1405      undefined,
1406      funcLikeDecl.typeParameters,
1407      funcLikeDecl.parameters,
1408      typeNode,
1409      ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
1410      funcLikeDecl.body
1411    );
1412    return this.printer.printNode(ts.EmitHint.Unspecified, node, funcLikeDecl.getSourceFile());
1413  }
1414
1415  fixMissingReturnType(funcLikeDecl: ts.FunctionLikeDeclaration, typeNode: ts.TypeNode): Autofix[] {
1416    if (ts.isArrowFunction(funcLikeDecl)) {
1417      const text = this.getFixReturnTypeArrowFunction(funcLikeDecl, typeNode);
1418      const startPos = funcLikeDecl.getStart();
1419      const endPos = funcLikeDecl.getEnd();
1420
1421      return [{ start: startPos, end: endPos, replacementText: text }];
1422    }
1423    const text = ': ' + this.printer.printNode(ts.EmitHint.Unspecified, typeNode, funcLikeDecl.getSourceFile());
1424    const pos = Autofixer.getReturnTypePosition(funcLikeDecl);
1425    return [{ start: pos, end: pos, replacementText: text }];
1426  }
1427
1428  dropTypeOnVarDecl(varDecl: ts.VariableDeclaration): Autofix[] {
1429    const newVarDecl = ts.factory.createVariableDeclaration(varDecl.name, undefined, undefined, undefined);
1430    const text = this.printer.printNode(ts.EmitHint.Unspecified, newVarDecl, varDecl.getSourceFile());
1431    return [{ start: varDecl.getStart(), end: varDecl.getEnd(), replacementText: text }];
1432  }
1433
1434  fixTypeAssertion(typeAssertion: ts.TypeAssertion): Autofix[] {
1435    const asExpr = ts.factory.createAsExpression(typeAssertion.expression, typeAssertion.type);
1436    const text = this.nonCommentPrinter.printNode(ts.EmitHint.Unspecified, asExpr, typeAssertion.getSourceFile());
1437    return [{ start: typeAssertion.getStart(), end: typeAssertion.getEnd(), replacementText: text }];
1438  }
1439
1440  fixCommaOperator(tsNode: ts.Node): Autofix[] {
1441    const tsExprNode = tsNode as ts.BinaryExpression;
1442    const text = this.recursiveCommaOperator(tsExprNode);
1443    return [{ start: tsExprNode.parent.getFullStart(), end: tsExprNode.parent.getEnd(), replacementText: text }];
1444  }
1445
1446  private recursiveCommaOperator(tsExprNode: ts.BinaryExpression): string {
1447    let text = '';
1448    if (tsExprNode.operatorToken.kind !== ts.SyntaxKind.CommaToken) {
1449      return tsExprNode.getFullText() + ';';
1450    }
1451
1452    if (tsExprNode.left.kind === ts.SyntaxKind.BinaryExpression) {
1453      text += this.recursiveCommaOperator(tsExprNode.left as ts.BinaryExpression);
1454      text += this.getNewLine() + tsExprNode.right.getFullText() + ';';
1455    } else {
1456      const leftText = tsExprNode.left.getFullText();
1457      const rightText = tsExprNode.right.getFullText();
1458      text = leftText + ';' + this.getNewLine() + rightText + ';';
1459    }
1460
1461    return text;
1462  }
1463
1464  private getEnumMembers(node: ts.Node, enumDeclsInFile: ts.Declaration[], result: Autofix[] | undefined): void {
1465    if (result === undefined || !ts.isEnumDeclaration(node)) {
1466      return;
1467    }
1468
1469    if (result.length) {
1470      result.push({ start: node.getStart(), end: node.getEnd(), replacementText: '' });
1471      return;
1472    }
1473
1474    const members: ts.EnumMember[] = [];
1475    for (const decl of enumDeclsInFile) {
1476      for (const member of (decl as ts.EnumDeclaration).members) {
1477        if (
1478          member.initializer &&
1479          member.initializer.kind !== ts.SyntaxKind.NumericLiteral &&
1480          member.initializer.kind !== ts.SyntaxKind.StringLiteral
1481        ) {
1482          result = undefined;
1483          return;
1484        }
1485      }
1486      members.push(...(decl as ts.EnumDeclaration).members);
1487    }
1488
1489    const fullEnum = ts.factory.createEnumDeclaration(node.modifiers, node.name, members);
1490    const fullText = this.printer.printNode(ts.EmitHint.Unspecified, fullEnum, node.getSourceFile());
1491    result.push({ start: node.getStart(), end: node.getEnd(), replacementText: fullText });
1492  }
1493
1494  fixEnumMerging(enumSymbol: ts.Symbol, enumDeclsInFile: ts.Declaration[]): Autofix[] | undefined {
1495    if (this.enumMergingCache.has(enumSymbol)) {
1496      return this.enumMergingCache.get(enumSymbol);
1497    }
1498
1499    if (enumDeclsInFile.length <= 1) {
1500      this.enumMergingCache.set(enumSymbol, undefined);
1501      return undefined;
1502    }
1503
1504    let result: Autofix[] | undefined = [];
1505    this.symbolCache.getReferences(enumSymbol).forEach((node) => {
1506      this.getEnumMembers(node, enumDeclsInFile, result);
1507    });
1508    if (!result?.length) {
1509      result = undefined;
1510    }
1511
1512    this.enumMergingCache.set(enumSymbol, result);
1513    return result;
1514  }
1515
1516  private static getReturnTypePosition(funcLikeDecl: ts.FunctionLikeDeclaration): number {
1517    if (funcLikeDecl.body) {
1518
1519      /*
1520       * Find position of the first node or token that follows parameters.
1521       * After that, iterate over child nodes in reverse order, until found
1522       * first closing parenthesis.
1523       */
1524      const postParametersPosition = ts.isArrowFunction(funcLikeDecl) ?
1525        funcLikeDecl.equalsGreaterThanToken.getStart() :
1526        funcLikeDecl.body.getStart();
1527
1528      const children = funcLikeDecl.getChildren();
1529      for (let i = children.length - 1; i >= 0; i--) {
1530        const child = children[i];
1531        if (child.kind === ts.SyntaxKind.CloseParenToken && child.getEnd() <= postParametersPosition) {
1532          return child.getEnd();
1533        }
1534      }
1535    }
1536
1537    // Shouldn't get here.
1538    return -1;
1539  }
1540
1541  private static needsParentheses(node: ts.FunctionExpression): boolean {
1542    const parent = node.parent;
1543    return (
1544      ts.isPrefixUnaryExpression(parent) ||
1545      ts.isPostfixUnaryExpression(parent) ||
1546      ts.isPropertyAccessExpression(parent) ||
1547      ts.isElementAccessExpression(parent) ||
1548      ts.isTypeOfExpression(parent) ||
1549      ts.isVoidExpression(parent) ||
1550      ts.isAwaitExpression(parent) ||
1551      ts.isCallExpression(parent) && node === parent.expression ||
1552      ts.isBinaryExpression(parent) && !isAssignmentOperator(parent.operatorToken)
1553    );
1554  }
1555
1556  fixCtorParameterProperties(
1557    ctorDecl: ts.ConstructorDeclaration,
1558    paramTypes: ts.TypeNode[] | undefined
1559  ): Autofix[] | undefined {
1560    if (paramTypes === undefined) {
1561      return undefined;
1562    }
1563
1564    const fieldInitStmts: ts.Statement[] = [];
1565    const newFieldPos = ctorDecl.getStart();
1566    const autofixes: Autofix[] = [{ start: newFieldPos, end: newFieldPos, replacementText: '' }];
1567
1568    for (let i = 0; i < ctorDecl.parameters.length; i++) {
1569      this.fixCtorParameterPropertiesProcessParam(ctorDecl.parameters[i], paramTypes[i], fieldInitStmts, autofixes);
1570    }
1571
1572    // Note: Bodyless ctors can't have parameter properties.
1573    if (ctorDecl.body) {
1574      const beforeFieldStmts: ts.Statement[] = [];
1575      const afterFieldStmts: ts.Statement[] = [];
1576      const hasSuperExpressionStatement: boolean = this.hasSuperExpression(
1577        ctorDecl.body,
1578        beforeFieldStmts,
1579        afterFieldStmts
1580      );
1581      let finalStmts: ts.Statement[] = [];
1582      if (hasSuperExpressionStatement) {
1583        finalStmts = beforeFieldStmts.concat(fieldInitStmts).concat(afterFieldStmts);
1584      } else {
1585        finalStmts = fieldInitStmts.concat(ctorDecl.body.statements);
1586      }
1587      const newBody = ts.factory.createBlock(finalStmts, true);
1588      const newBodyText = this.printer.printNode(ts.EmitHint.Unspecified, newBody, ctorDecl.getSourceFile());
1589      autofixes.push({ start: ctorDecl.body.getStart(), end: ctorDecl.body.getEnd(), replacementText: newBodyText });
1590    }
1591
1592    return autofixes;
1593  }
1594
1595  private hasSuperExpression(
1596    body: ts.Block,
1597    beforeFieldStmts: ts.Statement[],
1598    afterFieldStmts: ts.Statement[]
1599  ): boolean {
1600    void this;
1601    let hasSuperExpressionStatement = false;
1602    ts.forEachChild(body, (node) => {
1603      if (this.isSuperCallStmt(node as ts.Statement)) {
1604        hasSuperExpressionStatement = true;
1605        beforeFieldStmts.push(node as ts.Statement);
1606      } else if (hasSuperExpressionStatement) {
1607        afterFieldStmts.push(node as ts.Statement);
1608      } else {
1609        beforeFieldStmts.push(node as ts.Statement);
1610      }
1611    });
1612    return hasSuperExpressionStatement;
1613  }
1614
1615  private isSuperCallStmt(node: ts.Statement): boolean {
1616    void this;
1617    if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) {
1618      const expr = node.expression.expression;
1619      return expr.kind === ts.SyntaxKind.SuperKeyword;
1620    }
1621    return false;
1622  }
1623
1624  private fixCtorParameterPropertiesProcessParam(
1625    param: ts.ParameterDeclaration,
1626    paramType: ts.TypeNode,
1627    fieldInitStmts: ts.Statement[],
1628    autofixes: Autofix[]
1629  ): void {
1630    // Parameter property can not be a destructuring parameter.
1631    if (!ts.isIdentifier(param.name)) {
1632      return;
1633    }
1634
1635    if (this.utils.hasAccessModifier(param)) {
1636      const propIdent = ts.factory.createIdentifier(param.name.text);
1637      const modifiers = ts.getModifiers(param);
1638      const paramModifiers = modifiers?.filter((x) => {
1639        return x.kind !== ts.SyntaxKind.OverrideKeyword;
1640      });
1641
1642      const newFieldNode = ts.factory.createPropertyDeclaration(
1643        paramModifiers,
1644        propIdent,
1645        param.questionToken,
1646        paramType,
1647        undefined
1648      );
1649      const newFieldText =
1650        this.printer.printNode(ts.EmitHint.Unspecified, newFieldNode, param.getSourceFile()) + this.getNewLine();
1651      autofixes[0].replacementText += newFieldText;
1652
1653      const newParamDecl = ts.factory.createParameterDeclaration(
1654        undefined,
1655        undefined,
1656        param.name,
1657        param.questionToken,
1658        param.type,
1659        param.initializer
1660      );
1661      const newParamText = this.printer.printNode(ts.EmitHint.Unspecified, newParamDecl, param.getSourceFile());
1662      autofixes.push({ start: param.getStart(), end: param.getEnd(), replacementText: newParamText });
1663
1664      fieldInitStmts.push(
1665        ts.factory.createExpressionStatement(
1666          ts.factory.createAssignment(
1667            ts.factory.createPropertyAccessExpression(ts.factory.createThis(), propIdent),
1668            propIdent
1669          )
1670        )
1671      );
1672    }
1673  }
1674
1675  fixPrivateIdentifier(node: ts.PrivateIdentifier): Autofix[] | undefined {
1676    const classMember = this.typeChecker.getSymbolAtLocation(node);
1677    if (!classMember || (classMember.getFlags() & ts.SymbolFlags.ClassMember) === 0 || !classMember.valueDeclaration) {
1678      return undefined;
1679    }
1680
1681    if (this.privateIdentifierCache.has(classMember)) {
1682      return this.privateIdentifierCache.get(classMember);
1683    }
1684
1685    const memberDecl = classMember.valueDeclaration as ts.ClassElement;
1686    const parentDecl = memberDecl.parent;
1687    if (!ts.isClassLike(parentDecl) || this.utils.classMemberHasDuplicateName(memberDecl, parentDecl, true)) {
1688      this.privateIdentifierCache.set(classMember, undefined);
1689      return undefined;
1690    }
1691
1692    let result: Autofix[] | undefined = [];
1693    this.symbolCache.getReferences(classMember).forEach((ident) => {
1694      if (ts.isPrivateIdentifier(ident)) {
1695        result!.push(this.fixSinglePrivateIdentifier(ident));
1696      }
1697    });
1698    if (!result.length) {
1699      result = undefined;
1700    }
1701
1702    this.privateIdentifierCache.set(classMember, result);
1703    return result;
1704  }
1705
1706  private isFunctionDeclarationFirst(tsFunctionDeclaration: ts.FunctionDeclaration): boolean {
1707    if (tsFunctionDeclaration.name === undefined) {
1708      return false;
1709    }
1710
1711    const symbol = this.typeChecker.getSymbolAtLocation(tsFunctionDeclaration.name);
1712    if (symbol === undefined) {
1713      return false;
1714    }
1715
1716    let minPos = tsFunctionDeclaration.pos;
1717    this.symbolCache.getReferences(symbol).forEach((ident) => {
1718      if (ident.pos < minPos) {
1719        minPos = ident.pos;
1720      }
1721    });
1722
1723    return minPos >= tsFunctionDeclaration.pos;
1724  }
1725
1726  fixNestedFunction(tsFunctionDeclaration: ts.FunctionDeclaration): Autofix[] | undefined {
1727    const isGenerator = tsFunctionDeclaration.asteriskToken !== undefined;
1728    const hasThisKeyword =
1729      tsFunctionDeclaration.body === undefined ? false : scopeContainsThis(tsFunctionDeclaration.body);
1730    const canBeFixed = !isGenerator && !hasThisKeyword;
1731    if (!canBeFixed) {
1732      return undefined;
1733    }
1734
1735    const name = tsFunctionDeclaration.name?.escapedText;
1736    const type = tsFunctionDeclaration.type;
1737    const body = tsFunctionDeclaration.body;
1738    if (!name || !type || !body) {
1739      return undefined;
1740    }
1741
1742    // Check only illegal decorators, cause all decorators for function declaration are illegal
1743    if (ts.getIllegalDecorators(tsFunctionDeclaration)) {
1744      return undefined;
1745    }
1746
1747    if (!this.isFunctionDeclarationFirst(tsFunctionDeclaration)) {
1748      return undefined;
1749    }
1750
1751    const typeParameters = tsFunctionDeclaration.typeParameters;
1752    const parameters = tsFunctionDeclaration.parameters;
1753    const modifiers = ts.getModifiers(tsFunctionDeclaration);
1754
1755    const token = ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken);
1756    const typeDecl = ts.factory.createFunctionTypeNode(typeParameters, parameters, type);
1757    const arrowFunc = ts.factory.createArrowFunction(modifiers, typeParameters, parameters, type, token, body);
1758
1759    const declaration: ts.VariableDeclaration = ts.factory.createVariableDeclaration(
1760      name,
1761      undefined,
1762      typeDecl,
1763      arrowFunc
1764    );
1765    const list: ts.VariableDeclarationList = ts.factory.createVariableDeclarationList([declaration], ts.NodeFlags.Let);
1766
1767    const statement = ts.factory.createVariableStatement(modifiers, list);
1768    const text = this.printer.printNode(ts.EmitHint.Unspecified, statement, tsFunctionDeclaration.getSourceFile());
1769    return [{ start: tsFunctionDeclaration.getStart(), end: tsFunctionDeclaration.getEnd(), replacementText: text }];
1770  }
1771
1772  fixMultipleStaticBlocks(nodes: ts.Node[]): Autofix[] | undefined {
1773    const autofix: Autofix[] | undefined = [];
1774    let body = (nodes[0] as ts.ClassStaticBlockDeclaration).body;
1775    let bodyStatements: ts.Statement[] = [];
1776    bodyStatements = bodyStatements.concat(body.statements);
1777    for (let i = 1; i < nodes.length; i++) {
1778      bodyStatements = bodyStatements.concat((nodes[i] as ts.ClassStaticBlockDeclaration).body.statements);
1779      autofix[i] = { start: nodes[i].getStart(), end: nodes[i].getEnd(), replacementText: '' };
1780    }
1781    body = ts.factory.createBlock(bodyStatements, true);
1782    // static blocks shouldn't have modifiers
1783    const statickBlock = ts.factory.createClassStaticBlockDeclaration(body);
1784    const text = this.printer.printNode(ts.EmitHint.Unspecified, statickBlock, nodes[0].getSourceFile());
1785    autofix[0] = { start: nodes[0].getStart(), end: nodes[0].getEnd(), replacementText: text };
1786    return autofix;
1787  }
1788
1789  private fixSinglePrivateIdentifier(ident: ts.PrivateIdentifier): Autofix {
1790    if (
1791      ts.isPropertyDeclaration(ident.parent) ||
1792      ts.isMethodDeclaration(ident.parent) ||
1793      ts.isGetAccessorDeclaration(ident.parent) ||
1794      ts.isSetAccessorDeclaration(ident.parent)
1795    ) {
1796      // Note: 'private' modifier should always be first.
1797      const mods = ts.getModifiers(ident.parent);
1798      const newMods: ts.Modifier[] = [ts.factory.createModifier(ts.SyntaxKind.PrivateKeyword)];
1799      if (mods) {
1800        for (const mod of mods) {
1801          newMods.push(ts.factory.createModifier(mod.kind));
1802        }
1803      }
1804
1805      const newName = ident.text.slice(1, ident.text.length);
1806      const newDecl = Autofixer.replacePrivateIdentInDeclarationName(newMods, newName, ident.parent);
1807      const text = this.printer.printNode(ts.EmitHint.Unspecified, newDecl, ident.getSourceFile());
1808      return { start: ident.parent.getStart(), end: ident.parent.getEnd(), replacementText: text };
1809    }
1810
1811    return {
1812      start: ident.getStart(),
1813      end: ident.getEnd(),
1814      replacementText: ident.text.slice(1, ident.text.length)
1815    };
1816  }
1817
1818  private static replacePrivateIdentInDeclarationName(
1819    mods: ts.Modifier[],
1820    name: string,
1821    oldDecl: ts.PropertyDeclaration | ts.MethodDeclaration | ts.GetAccessorDeclaration | ts.SetAccessorDeclaration
1822  ): ts.Declaration {
1823    if (ts.isPropertyDeclaration(oldDecl)) {
1824      return ts.factory.createPropertyDeclaration(
1825        mods,
1826        ts.factory.createIdentifier(name),
1827        oldDecl.questionToken ?? oldDecl.exclamationToken,
1828        oldDecl.type,
1829        oldDecl.initializer
1830      );
1831    } else if (ts.isMethodDeclaration(oldDecl)) {
1832      return ts.factory.createMethodDeclaration(
1833        mods,
1834        oldDecl.asteriskToken,
1835        ts.factory.createIdentifier(name),
1836        oldDecl.questionToken,
1837        oldDecl.typeParameters,
1838        oldDecl.parameters,
1839        oldDecl.type,
1840        oldDecl.body
1841      );
1842    } else if (ts.isGetAccessorDeclaration(oldDecl)) {
1843      return ts.factory.createGetAccessorDeclaration(
1844        mods,
1845        ts.factory.createIdentifier(name),
1846        oldDecl.parameters,
1847        oldDecl.type,
1848        oldDecl.body
1849      );
1850    }
1851    return ts.factory.createSetAccessorDeclaration(
1852      mods,
1853      ts.factory.createIdentifier(name),
1854      oldDecl.parameters,
1855      oldDecl.body
1856    );
1857  }
1858
1859  fixRecordObjectLiteral(objectLiteralExpr: ts.ObjectLiteralExpression): Autofix[] | undefined {
1860    const autofix: Autofix[] = [];
1861
1862    for (const prop of objectLiteralExpr.properties) {
1863      if (!prop.name) {
1864        return undefined;
1865      }
1866      if (this.utils.isValidRecordObjectLiteralKey(prop.name)) {
1867        // Skip property with a valid property key.
1868        continue;
1869      }
1870      if (!ts.isIdentifier(prop.name)) {
1871        // Can only fix identifier name.
1872        return undefined;
1873      }
1874
1875      const stringLiteralName = ts.factory.createStringLiteralFromNode(prop.name, true);
1876      const text = this.printer.printNode(ts.EmitHint.Unspecified, stringLiteralName, prop.name.getSourceFile());
1877      autofix.push({ start: prop.name.getStart(), end: prop.name.getEnd(), replacementText: text });
1878    }
1879
1880    return autofix;
1881  }
1882
1883  fixUntypedObjectLiteral(
1884    objectLiteralExpr: ts.ObjectLiteralExpression,
1885    objectLiteralType: ts.Type | undefined
1886  ): Autofix[] | undefined {
1887    if (objectLiteralType) {
1888
1889      /*
1890       * Special case for object literal of Record type: fix object's property names
1891       * by replacing identifiers with string literals.
1892       */
1893      if (this.utils.isStdRecordType(this.utils.getNonNullableType(objectLiteralType))) {
1894        return this.fixRecordObjectLiteral(objectLiteralExpr);
1895      }
1896
1897      // Here, we only fix object literal that doesn't have a contextual type.
1898      return undefined;
1899    }
1900
1901    if (Autofixer.hasUnfixableProperty(objectLiteralExpr)) {
1902      return undefined;
1903    }
1904
1905    const enclosingStmt = TsUtils.getEnclosingTopLevelStatement(objectLiteralExpr);
1906    if (!enclosingStmt) {
1907      return undefined;
1908    }
1909
1910    if (Autofixer.hasMethodsOrAccessors(objectLiteralExpr)) {
1911      return this.fixObjectLiteralAsClass(objectLiteralExpr, undefined, enclosingStmt);
1912    }
1913    return this.fixUntypedObjectLiteralAsInterface(objectLiteralExpr, enclosingStmt);
1914  }
1915
1916  private static hasUnfixableProperty(objectLiteralExpr: ts.ObjectLiteralExpression): boolean {
1917    return objectLiteralExpr.properties.some((prop) => {
1918      return ts.isSpreadAssignment(prop) || !ts.isIdentifier(prop.name);
1919    });
1920  }
1921
1922  private static hasMethodsOrAccessors(objectLiteralExpr: ts.ObjectLiteralExpression): boolean {
1923    return objectLiteralExpr.properties.some((prop) => {
1924      return ts.isMethodDeclaration(prop) || ts.isAccessor(prop);
1925    });
1926  }
1927
1928  private fixUntypedObjectLiteralAsInterface(
1929    objectLiteralExpr: ts.ObjectLiteralExpression,
1930    enclosingStmt: ts.Node
1931  ): Autofix[] | undefined {
1932    const newInterfaceProps = this.getInterfacePropertiesFromObjectLiteral(objectLiteralExpr, enclosingStmt);
1933    if (!newInterfaceProps) {
1934      return undefined;
1935    }
1936
1937    const srcFile = objectLiteralExpr.getSourceFile();
1938    const newInterfaceName = TsUtils.generateUniqueName(this.objectLiteralInterfaceNameGenerator, srcFile);
1939    if (!newInterfaceName) {
1940      return undefined;
1941    }
1942
1943    return [
1944      this.createNewInterfaceForObjectLiteral(srcFile, newInterfaceName, newInterfaceProps, enclosingStmt.getStart()),
1945      this.fixObjectLiteralExpression(srcFile, newInterfaceName, objectLiteralExpr)
1946    ];
1947  }
1948
1949  private getInterfacePropertiesFromObjectLiteral(
1950    objectLiteralExpr: ts.ObjectLiteralExpression,
1951    enclosingStmt: ts.Node
1952  ): ts.PropertySignature[] | undefined {
1953    const interfaceProps: ts.PropertySignature[] = [];
1954    for (const prop of objectLiteralExpr.properties) {
1955      const interfaceProp = this.getInterfacePropertyFromObjectLiteralElement(prop, enclosingStmt);
1956      if (!interfaceProp) {
1957        return undefined;
1958      }
1959      interfaceProps.push(interfaceProp);
1960    }
1961    return interfaceProps;
1962  }
1963
1964  private getInterfacePropertyFromObjectLiteralElement(
1965    prop: ts.ObjectLiteralElementLike,
1966    enclosingStmt: ts.Node
1967  ): ts.PropertySignature | undefined {
1968    // Can't fix if property is not a key-value pair, or the property name is a computed value.
1969    if (!ts.isPropertyAssignment(prop) || ts.isComputedPropertyName(prop.name)) {
1970      return undefined;
1971    }
1972
1973    const propType = this.typeChecker.getTypeAtLocation(prop);
1974
1975    // Can't capture generic type parameters of enclosing declarations.
1976    if (this.utils.hasGenericTypeParameter(propType)) {
1977      return undefined;
1978    }
1979
1980    if (TsUtils.typeIsCapturedFromEnclosingLocalScope(propType, enclosingStmt)) {
1981      return undefined;
1982    }
1983
1984    const propTypeNode = this.typeChecker.typeToTypeNode(propType, undefined, ts.NodeBuilderFlags.None);
1985    if (!propTypeNode || !this.utils.isSupportedType(propTypeNode)) {
1986      return undefined;
1987    }
1988
1989    const newProp: ts.PropertySignature = ts.factory.createPropertySignature(
1990      undefined,
1991      prop.name,
1992      undefined,
1993      propTypeNode
1994    );
1995    return newProp;
1996  }
1997
1998  private createNewInterfaceForObjectLiteral(
1999    srcFile: ts.SourceFile,
2000    interfaceName: string,
2001    members: ts.TypeElement[],
2002    pos: number
2003  ): Autofix {
2004    const newInterfaceDecl = ts.factory.createInterfaceDeclaration(
2005      undefined,
2006      interfaceName,
2007      undefined,
2008      undefined,
2009      members
2010    );
2011    const text = this.printer.printNode(ts.EmitHint.Unspecified, newInterfaceDecl, srcFile) + this.getNewLine();
2012    return { start: pos, end: pos, replacementText: text };
2013  }
2014
2015  private fixObjectLiteralExpression(
2016    srcFile: ts.SourceFile,
2017    newInterfaceName: string,
2018    objectLiteralExpr: ts.ObjectLiteralExpression
2019  ): Autofix {
2020
2021    /*
2022     * If object literal is initializing a variable or property,
2023     * then simply add new 'contextual' type to the declaration.
2024     * Otherwise, cast object literal to newly created interface type.
2025     */
2026    if (
2027      (ts.isVariableDeclaration(objectLiteralExpr.parent) ||
2028        ts.isPropertyDeclaration(objectLiteralExpr.parent) ||
2029        ts.isParameter(objectLiteralExpr.parent)) &&
2030      !objectLiteralExpr.parent.type
2031    ) {
2032      const text = ': ' + newInterfaceName;
2033      const pos = Autofixer.getDeclarationTypePositionForObjectLiteral(objectLiteralExpr.parent);
2034      return { start: pos, end: pos, replacementText: text };
2035    }
2036
2037    const newTypeRef = ts.factory.createTypeReferenceNode(newInterfaceName);
2038    let newExpr: ts.Expression = ts.factory.createAsExpression(
2039      ts.factory.createObjectLiteralExpression(objectLiteralExpr.properties),
2040      newTypeRef
2041    );
2042    if (!ts.isParenthesizedExpression(objectLiteralExpr.parent)) {
2043      newExpr = ts.factory.createParenthesizedExpression(newExpr);
2044    }
2045    const text = this.printer.printNode(ts.EmitHint.Unspecified, newExpr, srcFile);
2046    return { start: objectLiteralExpr.getStart(), end: objectLiteralExpr.getEnd(), replacementText: text };
2047  }
2048
2049  private static getDeclarationTypePositionForObjectLiteral(
2050    decl: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration
2051  ): number {
2052    if (ts.isPropertyDeclaration(decl)) {
2053      return (decl.questionToken || decl.exclamationToken || decl.name).getEnd();
2054    } else if (ts.isParameter(decl)) {
2055      return (decl.questionToken || decl.name).getEnd();
2056    }
2057    return (decl.exclamationToken || decl.name).getEnd();
2058  }
2059
2060  private fixObjectLiteralAsClass(
2061    objectLiteralExpr: ts.ObjectLiteralExpression,
2062    typeDecl: ts.ClassDeclaration | ts.InterfaceDeclaration | undefined,
2063    enclosingStmt: ts.Node,
2064    typeNode?: ts.TypeReferenceNode
2065  ): Autofix[] | undefined {
2066    if (this.utils.nodeCapturesValueFromEnclosingLocalScope(objectLiteralExpr, enclosingStmt)) {
2067      return undefined;
2068    }
2069
2070    const srcFile = objectLiteralExpr.getSourceFile();
2071    const newClassName = TsUtils.generateUniqueName(this.objectLiteralClassNameGenerator, srcFile);
2072    if (!newClassName) {
2073      return undefined;
2074    }
2075    const newInitInterfaceName = TsUtils.generateUniqueName(this.objectLiteralInitInterfaceNameGenerator, srcFile);
2076    if (!newInitInterfaceName) {
2077      return undefined;
2078    }
2079
2080    const classDeclAndCtorInitProps = this.createClassDeclForObjectLiteral(
2081      objectLiteralExpr,
2082      enclosingStmt,
2083      { className: newClassName, initInterfaceName: newInitInterfaceName },
2084      typeDecl,
2085      typeNode
2086    );
2087    if (!classDeclAndCtorInitProps) {
2088      return undefined;
2089    }
2090    const { classDecl, ctorInitProps } = classDeclAndCtorInitProps;
2091    let classDeclText =
2092      this.printer.printNode(ts.EmitHint.Unspecified, classDecl, srcFile) + this.getNewLine() + this.getNewLine();
2093
2094    const ctorArgs: ts.Expression[] = [];
2095    if (ctorInitProps.length) {
2096      classDeclText += this.createInitInterfaceForObjectLiteral(srcFile, newInitInterfaceName, classDecl);
2097      classDeclText += this.getNewLine() + this.getNewLine();
2098      ctorArgs.push(ts.factory.createObjectLiteralExpression(ctorInitProps, ctorInitProps.length > 1));
2099    }
2100    const newExpr = ts.factory.createNewExpression(ts.factory.createIdentifier(newClassName), undefined, ctorArgs);
2101    const newExprText = this.printer.printNode(ts.EmitHint.Unspecified, newExpr, srcFile);
2102    const ctorCallAutofix = {
2103      start: objectLiteralExpr.getStart(),
2104      end: objectLiteralExpr.getEnd(),
2105      replacementText: newExprText
2106    };
2107    const classDeclPos = enclosingStmt.getStart();
2108    return [{ start: classDeclPos, end: classDeclPos, replacementText: classDeclText }, ctorCallAutofix];
2109  }
2110
2111  private createClassDeclForObjectLiteral(
2112    objectLiteralExpr: ts.ObjectLiteralExpression,
2113    enclosingStmt: ts.Node,
2114    names: { className: string; initInterfaceName: string },
2115    typeDecl: ts.ClassDeclaration | ts.InterfaceDeclaration | undefined,
2116    typeNode?: ts.TypeReferenceNode
2117  ): { classDecl: ts.ClassDeclaration; ctorInitProps: ts.PropertyAssignment[] } | undefined {
2118    const { className, initInterfaceName } = names;
2119    const classFields: ts.PropertyDeclaration[] = [];
2120    const classMethods: (ts.MethodDeclaration | ts.AccessorDeclaration)[] = [];
2121    const ctorBodyStmts: ts.Statement[] = [];
2122    const ctorInitProps: ts.PropertyAssignment[] = [];
2123
2124    Autofixer.addSuperCallToObjectLiteralConstructor(typeDecl, ctorBodyStmts);
2125
2126    for (const prop of objectLiteralExpr.properties) {
2127      if (ts.isSpreadAssignment(prop) || !ts.isIdentifier(prop.name)) {
2128        return undefined;
2129      }
2130      if (ts.isMethodDeclaration(prop) || ts.isAccessor(prop)) {
2131        classMethods.push(prop);
2132        continue;
2133      }
2134      const created = this.createClassPropertyForObjectLiteral({
2135        prop,
2136        enclosingStmt,
2137        classFields,
2138        ctorBodyStmts,
2139        ctorInitProps
2140      });
2141      if (!created) {
2142        return undefined;
2143      }
2144    }
2145
2146    const classElements: ts.ClassElement[] = [...classFields];
2147    if (ctorInitProps.length) {
2148      classElements.push(Autofixer.createClassConstructorForObjectLiteral(initInterfaceName, ctorBodyStmts));
2149    }
2150    classElements.push(...classMethods);
2151
2152    const heritageClauses = Autofixer.createHeritageClausesForObjectLiteralClass(typeDecl, typeNode);
2153
2154    return {
2155      classDecl: ts.factory.createClassDeclaration(undefined, className, undefined, heritageClauses, classElements),
2156      ctorInitProps
2157    };
2158  }
2159
2160  private static addSuperCallToObjectLiteralConstructor(
2161    typeDecl: ts.ClassDeclaration | ts.InterfaceDeclaration | undefined,
2162    ctorBodyStmts: ts.Statement[]
2163  ): void {
2164    if (typeDecl && ts.isClassDeclaration(typeDecl)) {
2165      const superCall = ts.factory.createExpressionStatement(
2166        ts.factory.createCallExpression(ts.factory.createSuper(), undefined, [])
2167      );
2168      ctorBodyStmts.push(superCall);
2169    }
2170  }
2171
2172  private createClassPropertyForObjectLiteral(
2173    createClassPropParams: CreateClassPropertyForObjectLiteralParams
2174  ): boolean {
2175    const { prop, enclosingStmt, classFields, ctorBodyStmts, ctorInitProps } = createClassPropParams;
2176    if (!ts.isIdentifier(prop.name)) {
2177      return false;
2178    }
2179    const propType = this.typeChecker.getTypeAtLocation(prop);
2180
2181    // Can't capture generic type parameters of enclosing declarations.
2182    if (this.utils.hasGenericTypeParameter(propType)) {
2183      return false;
2184    }
2185
2186    if (TsUtils.typeIsCapturedFromEnclosingLocalScope(propType, enclosingStmt)) {
2187      return false;
2188    }
2189
2190    const propTypeNode = this.typeChecker.typeToTypeNode(propType, undefined, ts.NodeBuilderFlags.None);
2191    if (!propTypeNode || !this.utils.isSupportedType(propTypeNode)) {
2192      return false;
2193    }
2194
2195    const propName = ts.factory.createIdentifier(prop.name.text);
2196    classFields.push(ts.factory.createPropertyDeclaration(undefined, propName, undefined, propTypeNode, undefined));
2197    ctorBodyStmts.push(
2198      ts.factory.createExpressionStatement(
2199        ts.factory.createBinaryExpression(
2200          ts.factory.createPropertyAccessExpression(ts.factory.createThis(), propName),
2201          ts.factory.createToken(ts.SyntaxKind.EqualsToken),
2202          ts.factory.createPropertyAccessExpression(
2203            ts.factory.createIdentifier(OBJECT_LITERAL_CLASS_CONSTRUCTOR_PARAM_NAME),
2204            propName
2205          )
2206        )
2207      )
2208    );
2209    ctorInitProps.push(ts.isPropertyAssignment(prop) ? prop : ts.factory.createPropertyAssignment(prop.name, propName));
2210    return true;
2211  }
2212
2213  private static createHeritageClausesForObjectLiteralClass(
2214    typeDecl: ts.ClassDeclaration | ts.InterfaceDeclaration | undefined,
2215    typeNode?: ts.TypeReferenceNode
2216  ): ts.HeritageClause[] | undefined {
2217    if (!typeDecl?.name) {
2218      return undefined;
2219    }
2220
2221    const heritageTypeExpression = typeNode ?
2222      Autofixer.entityNameToExpression(typeNode.typeName) :
2223      ts.factory.createIdentifier(typeDecl.name.text);
2224
2225    return [
2226      ts.factory.createHeritageClause(
2227        ts.isClassDeclaration(typeDecl) ? ts.SyntaxKind.ExtendsKeyword : ts.SyntaxKind.ImplementsKeyword,
2228        [ts.factory.createExpressionWithTypeArguments(heritageTypeExpression, undefined)]
2229      )
2230    ];
2231  }
2232
2233  private static entityNameToExpression(name: ts.EntityName): ts.Expression {
2234    if (ts.isQualifiedName(name)) {
2235      return ts.factory.createPropertyAccessExpression(Autofixer.entityNameToExpression(name.left), name.right);
2236    }
2237    return ts.factory.createIdentifier(name.text);
2238  }
2239
2240  private static createClassConstructorForObjectLiteral(
2241    newInitInterfaceName: string,
2242    ctorBodyStmts: ts.Statement[]
2243  ): ts.ConstructorDeclaration {
2244    const ctorParams: ts.ParameterDeclaration[] = [];
2245    ctorParams.push(
2246      ts.factory.createParameterDeclaration(
2247        undefined,
2248        undefined,
2249        OBJECT_LITERAL_CLASS_CONSTRUCTOR_PARAM_NAME,
2250        undefined,
2251        ts.factory.createTypeReferenceNode(newInitInterfaceName)
2252      )
2253    );
2254    return ts.factory.createConstructorDeclaration(undefined, ctorParams, ts.factory.createBlock(ctorBodyStmts, true));
2255  }
2256
2257  private createInitInterfaceForObjectLiteral(
2258    srcFile: ts.SourceFile,
2259    interfaceName: string,
2260    newClassDecl: ts.ClassDeclaration
2261  ): string {
2262    const props: ts.PropertySignature[] = [];
2263    newClassDecl.members.forEach((prop) => {
2264      if (ts.isPropertyDeclaration(prop)) {
2265        props.push(ts.factory.createPropertySignature(undefined, prop.name, undefined, prop.type));
2266      }
2267    });
2268    const newInterfaceDecl = ts.factory.createInterfaceDeclaration(
2269      undefined,
2270      interfaceName,
2271      undefined,
2272      undefined,
2273      props
2274    );
2275    return this.printer.printNode(ts.EmitHint.Unspecified, newInterfaceDecl, srcFile);
2276  }
2277
2278  fixTypedObjectLiteral(
2279    objectLiteralExpr: ts.ObjectLiteralExpression,
2280    objectLiteralType: ts.Type | undefined
2281  ): Autofix[] | undefined {
2282    // Here we only try to fix typed object literal. Other case is handled by 'fixUntypedObjectLiteral' method.
2283
2284    if (!objectLiteralType || !this.utils.validateObjectLiteralType(objectLiteralType)) {
2285      return undefined;
2286    }
2287
2288    const typeDecl = TsUtils.getDeclaration(objectLiteralType.getSymbol());
2289    if (!typeDecl || !ts.isClassDeclaration(typeDecl) && !ts.isInterfaceDeclaration(typeDecl) || !typeDecl.name) {
2290      return undefined;
2291    }
2292
2293    if (Autofixer.hasUnfixableProperty(objectLiteralExpr)) {
2294      return undefined;
2295    }
2296
2297    if (this.hasMethodOverridingProperty(objectLiteralExpr, objectLiteralType)) {
2298      return undefined;
2299    }
2300
2301    const enclosingStmt = TsUtils.getEnclosingTopLevelStatement(objectLiteralExpr);
2302    if (!enclosingStmt) {
2303      return undefined;
2304    }
2305
2306    const typeNode = (objectLiteralExpr.parent as ts.VariableDeclaration).type as ts.TypeReferenceNode | undefined;
2307    return this.fixObjectLiteralAsClass(objectLiteralExpr, typeDecl, enclosingStmt, typeNode);
2308  }
2309
2310  private hasMethodOverridingProperty(
2311    objectLiteralExpr: ts.ObjectLiteralExpression,
2312    objectLiteralType: ts.Type
2313  ): boolean {
2314    const typeProps = this.typeChecker.getPropertiesOfType(objectLiteralType);
2315    for (const objProp of objectLiteralExpr.properties) {
2316      if (
2317        ts.isPropertyAssignment(objProp) &&
2318        typeProps.some((typeProp) => {
2319          const typePropDecl = TsUtils.getDeclaration(typeProp);
2320          return (
2321            !!typePropDecl &&
2322            (ts.isMethodSignature(typePropDecl) || ts.isMethodDeclaration(typePropDecl)) &&
2323            typePropDecl.name === objProp.name
2324          );
2325        })
2326      ) {
2327        return true;
2328      }
2329
2330      if (
2331        ts.isMethodDeclaration(objProp) &&
2332        typeProps.some((typeProp) => {
2333          const typePropDecl = TsUtils.getDeclaration(typeProp);
2334          return (
2335            !!typePropDecl &&
2336            (ts.isPropertyDeclaration(typePropDecl) || ts.isPropertySignature(typePropDecl)) &&
2337            typePropDecl.name.getText() === objProp.name.getText()
2338          );
2339        })
2340      ) {
2341        return true;
2342      }
2343    }
2344
2345    return false;
2346  }
2347
2348  fixShorthandPropertyAssignment(prop: ts.ShorthandPropertyAssignment): Autofix[] {
2349    const newName = ts.factory.createIdentifier(prop.name.text);
2350    const newProp = ts.factory.createPropertyAssignment(newName, newName);
2351    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, newProp, prop.getSourceFile());
2352    return [{ start: prop.getStart(), end: prop.getEnd(), replacementText }];
2353  }
2354
2355  /*
2356   * In case of type alias initialized with type literal, replace
2357   * entire type alias with identical interface declaration.
2358   */
2359  private proceedTypeAliasDeclaration(typeLiteral: ts.TypeLiteralNode): Autofix[] | undefined {
2360    if (ts.isTypeAliasDeclaration(typeLiteral.parent)) {
2361      const typeAlias = typeLiteral.parent;
2362      const newInterfaceDecl = ts.factory.createInterfaceDeclaration(
2363        typeAlias.modifiers,
2364        typeAlias.name,
2365        typeAlias.typeParameters,
2366        undefined,
2367        typeLiteral.members
2368      );
2369      const text = this.printer.printNode(ts.EmitHint.Unspecified, newInterfaceDecl, typeLiteral.getSourceFile());
2370      return [{ start: typeAlias.getStart(), end: typeAlias.getEnd(), replacementText: text }];
2371    }
2372    return undefined;
2373  }
2374
2375  fixTypeliteral(typeLiteral: ts.TypeLiteralNode): Autofix[] | undefined {
2376    const typeAliasAutofix = this.proceedTypeAliasDeclaration(typeLiteral);
2377    if (typeAliasAutofix) {
2378      return typeAliasAutofix;
2379    }
2380
2381    /*
2382     * Create new interface declaration with members of type literal
2383     * and put the interface name in place of the type literal.
2384     */
2385    const srcFile = typeLiteral.getSourceFile();
2386    const enclosingStmt = TsUtils.getEnclosingTopLevelStatement(typeLiteral);
2387    if (!enclosingStmt) {
2388      return undefined;
2389    }
2390
2391    if (this.utils.nodeCapturesValueFromEnclosingLocalScope(typeLiteral, enclosingStmt)) {
2392      return undefined;
2393    }
2394
2395    const newInterfaceName = TsUtils.generateUniqueName(this.typeLiteralInterfaceNameGenerator, srcFile);
2396    if (!newInterfaceName) {
2397      return undefined;
2398    }
2399    const newInterfacePos = enclosingStmt.getStart();
2400    const newInterfaceDecl = ts.factory.createInterfaceDeclaration(
2401      undefined,
2402      newInterfaceName,
2403      undefined,
2404      undefined,
2405      typeLiteral.members
2406    );
2407    const interfaceText =
2408      this.printer.printNode(ts.EmitHint.Unspecified, newInterfaceDecl, srcFile) + this.getNewLine();
2409
2410    return [
2411      { start: newInterfacePos, end: newInterfacePos, replacementText: interfaceText },
2412      { start: typeLiteral.getStart(), end: typeLiteral.getEnd(), replacementText: newInterfaceName }
2413    ];
2414  }
2415
2416  removeNode(node: ts.Node): Autofix[] {
2417    void this;
2418    return [{ start: node.getStart(), end: node.getEnd(), replacementText: '' }];
2419  }
2420
2421  replaceNode(node: ts.Node, replacementText: string): Autofix[] {
2422    void this;
2423    return [{ start: node.getStart(), end: node.getEnd(), replacementText }];
2424  }
2425
2426  removeImportSpecifier(
2427    specToRemove: ts.ImportSpecifier,
2428    importDeclaration: ts.ImportDeclaration
2429  ): Autofix[] | undefined {
2430    if (!importDeclaration) {
2431      return undefined;
2432    }
2433
2434    const importClause = importDeclaration.importClause;
2435    if (!importClause?.namedBindings || !ts.isNamedImports(importClause.namedBindings)) {
2436      return undefined;
2437    }
2438
2439    const namedBindings = importClause.namedBindings;
2440    const allSpecifiers = namedBindings.elements;
2441    const remainingSpecifiers = allSpecifiers.filter((spec) => {
2442      return spec !== specToRemove;
2443    });
2444
2445    // If none are valid, remove all named imports.
2446    if (remainingSpecifiers.length === 0) {
2447      if (importClause.name) {
2448        const start = importClause.name.end;
2449        const end = namedBindings.end;
2450        return [{ start, end, replacementText: '' }];
2451      }
2452      return this.removeNode(importDeclaration);
2453    }
2454
2455    const specIndex = allSpecifiers.findIndex((spec) => {
2456      return spec === specToRemove;
2457    });
2458    const isLast = specIndex === allSpecifiers.length - 1;
2459    const isFirst = specIndex === 0;
2460
2461    let start = specToRemove.getStart();
2462    let end = specToRemove.getEnd();
2463
2464    if (!isLast) {
2465      end = allSpecifiers[specIndex + 1].getStart();
2466    } else if (!isFirst) {
2467      const prev = allSpecifiers[specIndex - 1];
2468      start = prev.getEnd();
2469    }
2470
2471    return [{ start, end, replacementText: '' }];
2472  }
2473
2474  removeDefaultImport(importDecl: ts.ImportDeclaration, defaultImport: ts.Identifier): Autofix[] | undefined {
2475    const importClause = importDecl.importClause;
2476    if (!importClause || !defaultImport) {
2477      return undefined;
2478    }
2479
2480    const namedBindings = importClause.namedBindings;
2481
2482    if (!namedBindings) {
2483      return this.removeNode(importDecl);
2484    }
2485    const start = defaultImport.getStart();
2486    const end = namedBindings.getStart();
2487
2488    return [{ start, end, replacementText: '' }];
2489  }
2490
2491  fixSendableExplicitFieldType(node: ts.PropertyDeclaration): Autofix[] | undefined {
2492    const initializer = node.initializer;
2493    if (initializer === undefined) {
2494      return undefined;
2495    }
2496
2497    const propType = this.typeChecker.getTypeAtLocation(node);
2498    const propTypeNode = this.typeChecker.typeToTypeNode(propType, undefined, ts.NodeBuilderFlags.None);
2499    if (!propTypeNode || !this.utils.isSupportedType(propTypeNode)) {
2500      return undefined;
2501    }
2502
2503    const pos = (node.questionToken || node.exclamationToken || node.name).getEnd();
2504    const text = ': ' + this.printer.printNode(ts.EmitHint.Unspecified, propTypeNode, node.getSourceFile());
2505    return [{ start: pos, end: pos, replacementText: text }];
2506  }
2507
2508  addClassSendableDecorator(
2509    hClause: ts.HeritageClause,
2510    typeExpr: ts.ExpressionWithTypeArguments
2511  ): Autofix[] | undefined {
2512    const decl = hClause.parent;
2513    if (this.sendableDecoratorCache.has(decl)) {
2514      return this.sendableDecoratorCache.get(decl);
2515    }
2516    if (hClause.token === ts.SyntaxKind.ExtendsKeyword && !this.utils.isValidSendableClassExtends(typeExpr)) {
2517      return undefined;
2518    }
2519    const result = this.addSendableDecorator(decl);
2520    this.sendableDecoratorCache.set(decl, result);
2521    return result;
2522  }
2523
2524  addSendableDecorator(node: ts.Node): Autofix[] {
2525    void this;
2526    const text = '@' + SENDABLE_DECORATOR + this.getNewLine();
2527    const pos = node.getStart();
2528    return [{ start: pos, end: pos, replacementText: text }];
2529  }
2530
2531  fixGlobalThisSet(decl: ts.BinaryExpression): Autofix[] | undefined {
2532    void this;
2533    const left = decl.left;
2534    const right = decl.right;
2535    if (
2536      ts.isPropertyAccessExpression(left) &&
2537      ts.isIdentifier(left.expression) &&
2538      left.expression.text === 'globalThis'
2539    ) {
2540      const propertyName = left.name.text;
2541      const value = right.getText();
2542      const replacementText = `${SPECIAL_LIB_NAME}.globalThis.set("${propertyName}", ${value})`;
2543      return [{ start: decl.getStart(), end: decl.getEnd(), replacementText: replacementText }];
2544    }
2545    return undefined;
2546  }
2547
2548  fixVoidOperator(voidExpr: ts.VoidExpression): Autofix[] {
2549    let newExpr = voidExpr.expression;
2550
2551    if (Autofixer.needParenthesesForVoidOperator(newExpr)) {
2552      newExpr = ts.factory.createParenthesizedExpression(newExpr);
2553    }
2554
2555    const funcBody = ts.factory.createBlock(
2556      [
2557        ts.factory.createExpressionStatement(newExpr),
2558        ts.factory.createReturnStatement(ts.factory.createIdentifier(UNDEFINED_NAME))
2559      ],
2560      true
2561    );
2562
2563    const arrowFunc = ts.factory.createArrowFunction(
2564      undefined,
2565      undefined,
2566      [],
2567      undefined,
2568      ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
2569      funcBody
2570    );
2571
2572    const callExpr = ts.factory.createCallExpression(
2573      ts.factory.createParenthesizedExpression(arrowFunc),
2574      undefined,
2575      undefined
2576    );
2577
2578    const text = this.printer.printNode(ts.EmitHint.Unspecified, callExpr, voidExpr.getSourceFile());
2579    return [{ start: voidExpr.getStart(), end: voidExpr.getEnd(), replacementText: text }];
2580  }
2581
2582  private static needParenthesesForVoidOperator(expr: ts.Expression): boolean {
2583    return ts.isObjectLiteralExpression(expr) || ts.isFunctionExpression(expr) || ts.isClassExpression(expr);
2584  }
2585
2586  fixRegularExpressionLiteral(node: ts.RegularExpressionLiteral | ts.CallExpression): Autofix[] | undefined {
2587    const srcFile = node.getSourceFile();
2588    let patternNode: ts.Expression | undefined;
2589    let flag: string | undefined;
2590
2591    if (ts.isRegularExpressionLiteral(node)) {
2592      const literalText = node.getText();
2593      const parts = Autofixer.extractRegexParts(literalText);
2594      patternNode = ts.factory.createStringLiteral(parts.pattern);
2595      flag = parts.flag;
2596    } else if (ts.isCallExpression(node)) {
2597      const args = node.arguments;
2598      if (args.length === 0 || args.length > 2) {
2599        return undefined;
2600      }
2601      const patternArg = args[0];
2602      if (!this.isStaticStringExpression(patternArg)) {
2603        return undefined;
2604      }
2605      patternNode = patternArg;
2606
2607      if (args.length > 1) {
2608        const flagArg = args[1];
2609        if (ts.isStringLiteral(flagArg)) {
2610          flag = flagArg.text;
2611        } else {
2612          return undefined;
2613        }
2614      }
2615    } else {
2616      return undefined;
2617    }
2618
2619    const newArgs: ts.Expression[] = [patternNode];
2620    if (flag !== undefined) {
2621      newArgs.push(ts.factory.createStringLiteral(flag));
2622    }
2623    const newExpression = ts.factory.createNewExpression(ts.factory.createIdentifier('RegExp'), undefined, newArgs);
2624
2625    const text = this.printer.printNode(ts.EmitHint.Unspecified, newExpression, srcFile);
2626    return [
2627      {
2628        start: node.getStart(),
2629        end: node.getEnd(),
2630        replacementText: text
2631      }
2632    ];
2633  }
2634
2635  private isStaticStringExpression(node: ts.Node): boolean {
2636    if (ts.isStringLiteral(node)) {
2637      return true;
2638    }
2639    if (ts.isBinaryExpression(node)) {
2640      return (
2641        node.operatorToken.kind === ts.SyntaxKind.PlusToken &&
2642        this.isStaticStringExpression(node.left) &&
2643        this.isStaticStringExpression(node.right)
2644      );
2645    }
2646    if (ts.isCallExpression(node)) {
2647      const expression = node.expression;
2648      if (
2649        ts.isPropertyAccessExpression(expression) &&
2650        expression.name.text === 'concat' &&
2651        this.isStaticStringExpression(expression.expression)
2652      ) {
2653        return node.arguments.every((arg) => {
2654          return this.isStaticStringExpression(arg);
2655        });
2656      }
2657    }
2658    return false;
2659  }
2660
2661  private static extractRegexParts(literalText: string): {
2662    pattern: string;
2663    flag: string | undefined;
2664  } {
2665    let pattern: string = '';
2666    let flag: string | undefined;
2667    const lastSlashIndex = literalText.lastIndexOf('/');
2668    const afterLastSlash = literalText.slice(lastSlashIndex + 1);
2669    if (afterLastSlash !== '') {
2670      pattern = literalText.slice(1, lastSlashIndex);
2671      flag = afterLastSlash;
2672    } else {
2673      pattern = literalText.slice(1, lastSlashIndex);
2674    }
2675    return { pattern, flag };
2676  }
2677
2678  /*
2679   * "unsafe" (result is not common subset) autofixes
2680   */
2681
2682  // to use special lib functions it's need to import it
2683  static SPECIAL_LIB_NAME = 'specialAutofixLib';
2684
2685  // autofix for '**', '**=' operations and 'math.pow()' call
2686  fixExponent(exponentNode: ts.Node): Autofix[] | undefined {
2687    let autofix: Autofix[] = [];
2688    let replaceText: Autofix = { replacementText: '', start: 0, end: 0 };
2689
2690    // ts.BinaryExpression
2691    let callArgs: ts.Expression[] | undefined;
2692    if (exponentNode.kind === ts.SyntaxKind.CallExpression) {
2693      callArgs = [...(exponentNode as ts.CallExpression).arguments];
2694    } else if (exponentNode.kind === ts.SyntaxKind.BinaryExpression) {
2695      callArgs = [(exponentNode as ts.BinaryExpression).left, (exponentNode as ts.BinaryExpression).right];
2696    } else {
2697      // if we get here - it was an error!
2698      return undefined;
2699    }
2700
2701    const newCall = ts.factory.createCallExpression(
2702      ts.factory.createPropertyAccessExpression(
2703        ts.factory.createIdentifier('Math'),
2704        ts.factory.createIdentifier('pow')
2705      ),
2706      undefined,
2707      callArgs
2708    );
2709
2710    if (
2711      exponentNode.kind === ts.SyntaxKind.BinaryExpression &&
2712      (exponentNode as ts.BinaryExpression).operatorToken.kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken
2713    ) {
2714      const newAssignment = ts.factory.createAssignment((exponentNode as ts.BinaryExpression).left, newCall);
2715      replaceText = {
2716        replacementText: this.printer.printNode(ts.EmitHint.Unspecified, newAssignment, exponentNode.getSourceFile()),
2717        start: exponentNode.getStart(),
2718        end: exponentNode.getEnd()
2719      };
2720    } else {
2721      replaceText = {
2722        replacementText: this.printer.printNode(ts.EmitHint.Unspecified, newCall, exponentNode.getSourceFile()),
2723        start: exponentNode.getStart(),
2724        end: exponentNode.getEnd()
2725      };
2726    }
2727
2728    autofix = [replaceText];
2729    return autofix;
2730  }
2731
2732  fixNativeBidirectionalBinding(
2733    expr: ts.NonNullExpression,
2734    interfacesNeedToImport: Set<string>
2735  ): Autofix[] | undefined {
2736    if (!ts.isNonNullExpression(expr.expression)) {
2737      return undefined;
2738    }
2739    const originalExpr = expr.expression.expression;
2740    const doubleDollarIdentifier = ts.factory.createIdentifier(DOUBLE_DOLLAR_IDENTIFIER);
2741    interfacesNeedToImport.add(DOUBLE_DOLLAR_IDENTIFIER);
2742    const callExpr = ts.factory.createCallExpression(doubleDollarIdentifier, undefined, [originalExpr]);
2743    const text = this.printer.printNode(ts.EmitHint.Unspecified, callExpr, expr.getSourceFile());
2744    return [{ start: expr.getStart(), end: expr.getEnd(), replacementText: text }];
2745  }
2746
2747  fixCustomBidirectionalBinding(
2748    originalExpr: ts.ObjectLiteralExpression,
2749    currentParam: ts.Identifier,
2750    customParam: ts.Identifier
2751  ): Autofix[] | undefined {
2752    const assignment1 = ts.factory.createPropertyAssignment(
2753      customParam,
2754      ts.factory.createPropertyAccessExpression(ts.factory.createThis(), currentParam)
2755    );
2756    const value = ts.factory.createIdentifier(VALUE_IDENTIFIER);
2757    const block = ts.factory.createBlock(
2758      [
2759        ts.factory.createExpressionStatement(
2760          ts.factory.createBinaryExpression(
2761            ts.factory.createPropertyAccessExpression(ts.factory.createThis(), currentParam),
2762            ts.factory.createToken(ts.SyntaxKind.EqualsToken),
2763            value
2764          )
2765        )
2766      ],
2767      true
2768    );
2769    const parameter = ts.factory.createParameterDeclaration(
2770      undefined,
2771      undefined,
2772      value,
2773      undefined,
2774      undefined,
2775      undefined
2776    );
2777    const arrowFunc = ts.factory.createArrowFunction(
2778      undefined,
2779      undefined,
2780      [parameter],
2781      undefined,
2782      ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
2783      block
2784    );
2785    const assignment2 = ts.factory.createPropertyAssignment(
2786      ts.factory.createIdentifier('$' + customParam.getText()),
2787      arrowFunc
2788    );
2789    const newExpr = ts.factory.createObjectLiteralExpression([assignment1, assignment2], true);
2790    let text = this.printer.printNode(ts.EmitHint.Unspecified, newExpr, originalExpr.getSourceFile());
2791    const startPos = this.sourceFile.getLineAndCharacterOfPosition(originalExpr.parent.getStart()).character;
2792    text = this.adjustIndentation(text, startPos);
2793    return [{ start: originalExpr.getStart(), end: originalExpr.getEnd(), replacementText: text }];
2794  }
2795
2796  fixDoubleDollar(dollarExpr: ts.PropertyAccessExpression, interfacesNeedToImport: Set<string>): Autofix[] {
2797    const dollarValue = dollarExpr.name.escapedText as string;
2798    const dollarValueExpr = ts.factory.createPropertyAccessExpression(
2799      ts.factory.createThis(),
2800      ts.factory.createIdentifier(dollarValue)
2801    );
2802    const doubleDollarIdentifier = ts.factory.createIdentifier(DOUBLE_DOLLAR_IDENTIFIER);
2803    interfacesNeedToImport.add(DOUBLE_DOLLAR_IDENTIFIER);
2804    const callExpr = ts.factory.createCallExpression(doubleDollarIdentifier, undefined, [dollarValueExpr]);
2805    const text = this.printer.printNode(ts.EmitHint.Unspecified, callExpr, dollarExpr.getSourceFile());
2806    return [{ start: dollarExpr.getStart(), end: dollarExpr.getEnd(), replacementText: text }];
2807  }
2808
2809  fixDollarBind(property: ts.PropertyAssignment): Autofix[] {
2810    const identifier = property.initializer;
2811    const paramName = identifier.getText().substring(1);
2812    const newPropInit = ts.factory.createPropertyAccessExpression(
2813      ts.factory.createThis(),
2814      ts.factory.createIdentifier(paramName)
2815    );
2816    const text = this.printer.printNode(ts.EmitHint.Unspecified, newPropInit, property.getSourceFile());
2817    return [{ start: identifier.getStart(), end: identifier.getEnd(), replacementText: text }];
2818  }
2819
2820  fixExtendDecorator(
2821    extendDecorator: ts.Decorator,
2822    preserveDecorator: boolean,
2823    interfacesNeedToImport: Set<string>
2824  ): Autofix[] | undefined {
2825    if (!ts.isCallExpression(extendDecorator.expression)) {
2826      return undefined;
2827    }
2828    const funcDecl = extendDecorator.parent;
2829    if (!ts.isFunctionDeclaration(funcDecl)) {
2830      return undefined;
2831    }
2832    const block = funcDecl.body;
2833    const parameters: ts.MemberName[] = [];
2834    const values: ts.Expression[][] = [];
2835    const statements = block?.statements;
2836    Autofixer.getParamsAndValues(statements, parameters, values);
2837    const returnStatement = ts.factory.createReturnStatement(ts.factory.createThis());
2838    const newBlock = Autofixer.createBlock(parameters, values, ts.factory.createThis(), returnStatement);
2839    const componentName = extendDecorator.expression.arguments[0]?.getText();
2840    if (!componentName) {
2841      return undefined;
2842    }
2843    const typeName = componentName + ATTRIBUTE_SUFFIX;
2844    interfacesNeedToImport.add(typeName);
2845    const parameDecl = ts.factory.createParameterDeclaration(
2846      undefined,
2847      undefined,
2848      ts.factory.createIdentifier(THIS_IDENTIFIER),
2849      undefined,
2850      ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(typeName), undefined),
2851      undefined
2852    );
2853    const returnType = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(THIS_IDENTIFIER), undefined);
2854    const newFuncDecl = Autofixer.createFunctionDeclaration(funcDecl, undefined, parameDecl, returnType, newBlock);
2855    let text = this.printer.printNode(ts.EmitHint.Unspecified, newFuncDecl, funcDecl.getSourceFile());
2856    if (preserveDecorator) {
2857      text = '@' + CustomDecoratorName.AnimatableExtend + this.getNewLine() + text;
2858    }
2859    return [{ start: funcDecl.getStart(), end: funcDecl.getEnd(), replacementText: text }];
2860  }
2861
2862  fixEntryDecorator(entryDecorator: ts.Decorator): Autofix[] | undefined {
2863    if (!ts.isCallExpression(entryDecorator.expression)) {
2864      return undefined;
2865    }
2866
2867    const args = entryDecorator.expression.arguments;
2868    if (args.length !== 1) {
2869      return undefined;
2870    }
2871
2872    const parentNode = entryDecorator.parent;
2873    const arg = args[0];
2874    let getLocalStorageStatement: ts.VariableStatement | undefined;
2875
2876    if (ts.isIdentifier(arg) || ts.isNewExpression(arg) || ts.isCallExpression(arg)) {
2877      getLocalStorageStatement = Autofixer.createGetLocalStorageLambdaStatement(arg);
2878    } else if (ts.isObjectLiteralExpression(arg)) {
2879      getLocalStorageStatement = Autofixer.processEntryAnnotationObjectLiteralExpression(arg);
2880    }
2881
2882    if (getLocalStorageStatement !== undefined) {
2883      let text = this.printer.printNode(ts.EmitHint.Unspecified, getLocalStorageStatement, parentNode.getSourceFile());
2884      const fixedEntryDecorator = Autofixer.createFixedEntryDecorator();
2885      const fixedEntryDecoratorText = this.printer.printNode(
2886        ts.EmitHint.Unspecified,
2887        fixedEntryDecorator,
2888        parentNode.getSourceFile()
2889      );
2890      text = text + this.getNewLine() + fixedEntryDecoratorText;
2891      return [{ start: entryDecorator.getStart(), end: entryDecorator.getEnd(), replacementText: text }];
2892    }
2893    return undefined;
2894  }
2895
2896  private static createFixedEntryDecorator(): ts.Decorator {
2897    const storageProperty = ts.factory.createPropertyAssignment(
2898      ts.factory.createIdentifier(ENTRY_STORAGE_PROPERITY),
2899      ts.factory.createStringLiteral(GET_LOCAL_STORAGE_FUNC_NAME)
2900    );
2901    const objectLiteralExpr = ts.factory.createObjectLiteralExpression([storageProperty], false);
2902    const callExpr = ts.factory.createCallExpression(ts.factory.createIdentifier(ENTRY_DECORATOR_NAME), undefined, [
2903      objectLiteralExpr
2904    ]);
2905    return ts.factory.createDecorator(callExpr);
2906  }
2907
2908  private static processEntryAnnotationObjectLiteralExpression(
2909    expression: ts.ObjectLiteralExpression
2910  ): ts.VariableStatement | undefined {
2911    const objectProperties = expression.properties;
2912    if (objectProperties.length !== 1) {
2913      return undefined;
2914    }
2915    const objectProperty = objectProperties[0];
2916    if (!ts.isPropertyAssignment(objectProperty)) {
2917      return undefined;
2918    }
2919    if (ts.isIdentifier(objectProperty.name)) {
2920      if (objectProperty.name.escapedText !== ENTRY_STORAGE_PROPERITY) {
2921        return undefined;
2922      }
2923      const properityInitializer = objectProperty.initializer;
2924      if (
2925        ts.isIdentifier(properityInitializer) ||
2926        ts.isNewExpression(properityInitializer) ||
2927        ts.isCallExpression(properityInitializer) ||
2928        ts.isPropertyAccessExpression(properityInitializer)
2929      ) {
2930        return Autofixer.createGetLocalStorageLambdaStatement(properityInitializer);
2931      }
2932    }
2933    return undefined;
2934  }
2935
2936  private static createGetLocalStorageLambdaStatement(expression: ts.Expression): ts.VariableStatement {
2937    const variable = ts.factory.createVariableDeclaration(
2938      ts.factory.createIdentifier(GET_LOCAL_STORAGE_FUNC_NAME),
2939      undefined,
2940      undefined,
2941      ts.factory.createArrowFunction(
2942        undefined,
2943        undefined,
2944        [],
2945        ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(LOCAL_STORAGE_TYPE_NAME), undefined),
2946        ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
2947        expression
2948      )
2949    );
2950    const declarationList = ts.factory.createVariableDeclarationList([variable], ts.NodeFlags.Const);
2951    return ts.factory.createVariableStatement(undefined, declarationList);
2952  }
2953
2954  fixProvideDecorator(provideDecorator: ts.Decorator): Autofix[] | undefined {
2955    const callExpr = provideDecorator.expression as ts.CallExpression;
2956    const args = callExpr.arguments;
2957    const parentNode = provideDecorator.parent;
2958    const arg = args[0];
2959    let provideAnnotationFixed: ts.Decorator | undefined;
2960    if (ts.isStringLiteral(arg)) {
2961      provideAnnotationFixed = Autofixer.createProvideDecorator(arg);
2962    }
2963    if (ts.isObjectLiteralExpression(arg)) {
2964      const properties = arg.properties;
2965      const property = properties[0] as ts.PropertyAssignment;
2966      const propertyInitializer = property.initializer as ts.StringLiteral;
2967      provideAnnotationFixed = Autofixer.createProvideDecorator(propertyInitializer, true);
2968    }
2969    if (provideAnnotationFixed !== undefined) {
2970      const text = this.printer.printNode(ts.EmitHint.Unspecified, provideAnnotationFixed, parentNode.getSourceFile());
2971      return [{ start: provideDecorator.getStart(), end: provideDecorator.getEnd(), replacementText: text }];
2972    }
2973    return undefined;
2974  }
2975
2976  private static createProvideDecorator(
2977    alias: ts.StringLiteral,
2978    allowOverride: boolean | undefined = undefined
2979  ): ts.Decorator {
2980    const properties: ts.PropertyAssignment[] = [];
2981    properties.push(
2982      ts.factory.createPropertyAssignment(ts.factory.createIdentifier(PROVIDE_ALIAS_PROPERTY_NAME), alias)
2983    );
2984    if (allowOverride !== undefined && allowOverride) {
2985      properties.push(
2986        ts.factory.createPropertyAssignment(
2987          ts.factory.createIdentifier(PROVIDE_ALLOW_OVERRIDE_PROPERTY_NAME),
2988          ts.factory.createTrue()
2989        )
2990      );
2991    }
2992    const objectLiteralExpr = ts.factory.createObjectLiteralExpression(properties, false);
2993    const callExpr = ts.factory.createCallExpression(ts.factory.createIdentifier(PROVIDE_DECORATOR_NAME), undefined, [
2994      objectLiteralExpr
2995    ]);
2996    return ts.factory.createDecorator(callExpr);
2997  }
2998
2999  private static createFunctionDeclaration(
3000    funcDecl: ts.FunctionDeclaration,
3001    typeParameters: ts.TypeParameterDeclaration[] | undefined,
3002    paramDecl: ts.ParameterDeclaration,
3003    returnType: ts.TypeNode,
3004    block: ts.Block
3005  ): ts.FunctionDeclaration {
3006    return ts.factory.createFunctionDeclaration(
3007      undefined,
3008      undefined,
3009      funcDecl.name,
3010      typeParameters,
3011      [paramDecl, ...funcDecl.parameters],
3012      returnType,
3013      block
3014    );
3015  }
3016
3017  private static createBlock(
3018    parameters: ts.MemberName[],
3019    values: ts.Expression[][],
3020    arg: ts.Expression,
3021    returnStatement?: ts.Statement
3022  ): ts.Block {
3023    const statements: ts.Statement[] = [];
3024    for (let i = 0; i < parameters.length; i++) {
3025      const parameter = parameters[i];
3026      const value = values[i];
3027      const statement = ts.factory.createExpressionStatement(
3028        ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(arg, parameter), undefined, value)
3029      );
3030      statements.push(statement);
3031    }
3032    if (returnStatement) {
3033      statements.push(returnStatement);
3034    }
3035    const block = ts.factory.createBlock(statements, true);
3036    return block;
3037  }
3038
3039  private static traverseNodes(node: ts.Node, parameters: ts.MemberName[], values: ts.Expression[][]): void {
3040    const callExpressions: ts.CallExpression[] = Autofixer.extractCallExpressions(node);
3041    callExpressions.forEach((callExpression) => {
3042      if (ts.isPropertyAccessExpression(callExpression.expression)) {
3043        const propertyAccess = callExpression.expression;
3044        parameters.push(propertyAccess.name);
3045      } else if (ts.isIdentifier(callExpression.expression)) {
3046        parameters.push(callExpression.expression);
3047      }
3048      values.push(Array.from(callExpression.arguments));
3049    });
3050  }
3051
3052  private static extractCallExpressions(node: ts.Node): ts.CallExpression[] {
3053    const callExpressions: ts.CallExpression[] = [];
3054    let current: ts.Node | undefined;
3055    if (ts.isExpressionStatement(node)) {
3056      if (ts.isCallExpression(node.expression)) {
3057        current = node.expression;
3058      }
3059    }
3060
3061    if (ts.isPropertyAssignment(node)) {
3062      if (ts.isCallExpression(node.initializer)) {
3063        current = node.initializer;
3064      }
3065    }
3066
3067    while (current) {
3068      if (ts.isCallExpression(current)) {
3069        if (
3070          (ts.isPropertyAccessExpression(current.parent) ||
3071            ts.isExpressionStatement(current.parent) ||
3072            ts.isPropertyAssignment(current.parent)) &&
3073          (ts.isPropertyAccessExpression(current.expression) || ts.isIdentifier(current.expression))
3074        ) {
3075          callExpressions.push(current);
3076        }
3077      }
3078
3079      if (ts.isCallExpression(current) || ts.isPropertyAccessExpression(current)) {
3080        current = current.expression;
3081      } else {
3082        break;
3083      }
3084    }
3085    return callExpressions;
3086  }
3087
3088  private static getParamsAndValues(
3089    statements: ts.NodeArray<ts.Statement> | undefined,
3090    parameters: ts.MemberName[],
3091    values: ts.Expression[][]
3092  ): void {
3093    if (!statements) {
3094      return;
3095    }
3096    for (let i = 0; i < statements.length; i++) {
3097      const statement = statements[i];
3098      const tempParas: ts.MemberName[] = [];
3099      const tempVals: ts.Expression[][] = [];
3100      Autofixer.traverseNodes(statement, tempParas, tempVals);
3101      if (
3102        ts.isExpressionStatement(statement) &&
3103        ts.isCallExpression(statement.expression) &&
3104        ts.isPropertyAccessExpression(statement.expression.expression)
3105      ) {
3106        tempParas.reverse();
3107        tempVals.reverse();
3108      }
3109      for (let j = 0; j < tempParas.length; j++) {
3110        parameters.push(tempParas[j]);
3111        values.push(tempVals[j]);
3112      }
3113    }
3114  }
3115
3116  private getVariableName(node: ts.Node): string | undefined {
3117    let variableName: string | undefined;
3118
3119    switch (node.kind) {
3120      case ts.SyntaxKind.BinaryExpression: {
3121        const binaryExpr = node as ts.BinaryExpression;
3122        if (binaryExpr.operatorToken.kind !== ts.SyntaxKind.EqualsToken) {
3123          return undefined;
3124        }
3125
3126        variableName = binaryExpr.left.getText();
3127        break;
3128      }
3129      case ts.SyntaxKind.VariableDeclaration: {
3130        const variableDecl = node as ts.VariableDeclaration;
3131        variableName = variableDecl.name.getText();
3132        break;
3133      }
3134      case ts.SyntaxKind.ExpressionStatement: {
3135        variableName = TsUtils.generateUniqueName(this.tmpVariableNameGenerator, this.sourceFile);
3136        break;
3137      }
3138      default: {
3139        return undefined;
3140      }
3141    }
3142
3143    return variableName;
3144  }
3145
3146  private getNewNodesForIncrDecr(variableName: string, operator: number): IncrementDecrementNodeInfo | undefined {
3147    let update: string | undefined;
3148    let updateNode: ts.BinaryExpression | undefined;
3149
3150    switch (operator) {
3151      case ts.SyntaxKind.MinusMinusToken: {
3152        const { varAssignText, addOrDecrOperation } = this.createNewIncrDecrNodes(
3153          variableName,
3154          ts.SyntaxKind.MinusToken
3155        );
3156        update = varAssignText;
3157        updateNode = addOrDecrOperation;
3158        break;
3159      }
3160      case ts.SyntaxKind.PlusPlusToken: {
3161        const { varAssignText, addOrDecrOperation } = this.createNewIncrDecrNodes(
3162          variableName,
3163          ts.SyntaxKind.PlusToken
3164        );
3165        update = varAssignText;
3166        updateNode = addOrDecrOperation;
3167        break;
3168      }
3169      default:
3170        return undefined;
3171    }
3172
3173    return { varAssignText: update, addOrDecrOperation: updateNode };
3174  }
3175
3176  fixUnaryIncrDecr(
3177    node: ts.PrefixUnaryExpression | ts.PostfixUnaryExpression,
3178    pan: ts.PropertyAccessExpression
3179  ): Autofix[] | undefined {
3180    const parent = node.parent;
3181    const grandParent = parent.parent;
3182
3183    const { expression, name } = pan;
3184    const { operator } = node;
3185    const isVariableDeclaration = ts.isVariableDeclaration(node.parent);
3186
3187    const variableName = this.getVariableName(node.parent);
3188
3189    if (!variableName) {
3190      return undefined;
3191    }
3192
3193    const updateNodes = this.getNewNodesForIncrDecr(variableName, operator);
3194
3195    if (!updateNodes?.varAssignText || !updateNodes.addOrDecrOperation) {
3196      return undefined;
3197    }
3198
3199    const replacementText = this.getReplacementTextForPrefixAndPostfixUnary(
3200      node,
3201      updateNodes,
3202      expression,
3203      name,
3204      variableName
3205    );
3206
3207    if (!replacementText) {
3208      return undefined;
3209    }
3210
3211    if (isVariableDeclaration) {
3212      const start = grandParent.getStart();
3213      const end = grandParent.getEnd();
3214      return [{ replacementText, start, end }];
3215    }
3216
3217    const start = parent.getStart();
3218    const end = parent.getEnd();
3219    return [{ replacementText, start, end }];
3220  }
3221
3222  private getReplacementTextForPrefixAndPostfixUnary(
3223    node: ts.Node,
3224    updateNodes: IncrementDecrementNodeInfo,
3225    expression: ts.LeftHandSideExpression,
3226    name: ts.MemberName,
3227    variableName: string
3228  ): string | undefined {
3229    const { varAssignText, addOrDecrOperation } = updateNodes;
3230    const converted: ts.Node = this.createGetPropertyForIncrDecr(expression.getText(), name.text);
3231    let convertedAssigned = '';
3232    if (ts.isBinaryExpression(node.parent) && node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
3233      convertedAssigned = this.wrapPropertyAccessInBinaryExpr(variableName, converted);
3234    } else {
3235      convertedAssigned = this.wrapPropertyAccessInVariableDeclaration(variableName, converted);
3236    }
3237    let replacementText = '';
3238
3239    switch (node.kind) {
3240      case ts.SyntaxKind.PrefixUnaryExpression: {
3241        const assign = this.createSetProperty(
3242          expression.getText(),
3243          name.text,
3244          ts.factory.createIdentifier(variableName)
3245        );
3246        replacementText = `${convertedAssigned}\n${varAssignText}\n${assign}\n`;
3247        break;
3248      }
3249      case ts.SyntaxKind.PostfixUnaryExpression: {
3250        const assign = this.createSetProperty(expression.getText(), name.text, addOrDecrOperation as ts.Expression);
3251        replacementText = `${convertedAssigned}\n${assign}\n${varAssignText}\n`;
3252        break;
3253      }
3254      default: {
3255        return undefined;
3256      }
3257    }
3258
3259    return replacementText;
3260  }
3261
3262  private wrapPropertyAccessInVariableDeclaration(variableName: string, wrappedNode: ts.Node): string {
3263    const node = ts.factory.createVariableDeclarationList(
3264      [
3265        ts.factory.createVariableDeclaration(
3266          ts.factory.createIdentifier(variableName),
3267          undefined,
3268          undefined,
3269          wrappedNode as ts.Expression
3270        )
3271      ],
3272      ts.NodeFlags.Let
3273    );
3274
3275    return this.printer.printNode(ts.EmitHint.Unspecified, node, this.sourceFile);
3276  }
3277
3278  private wrapPropertyAccessInBinaryExpr(variableName: string, wrappedNode: ts.Node): string {
3279    const node = ts.factory.createBinaryExpression(
3280      ts.factory.createIdentifier(variableName),
3281      ts.SyntaxKind.EqualsToken,
3282      wrappedNode as ts.Expression
3283    );
3284
3285    return this.printer.printNode(ts.EmitHint.Unspecified, node, this.sourceFile);
3286  }
3287
3288  private createGetPropertyForIncrDecr(expression: string, name: string): ts.Node {
3289    void this;
3290    return ts.factory.createCallExpression(
3291      ts.factory.createPropertyAccessExpression(
3292        ts.factory.createCallExpression(
3293          ts.factory.createPropertyAccessExpression(
3294            ts.factory.createIdentifier(expression),
3295            ts.factory.createIdentifier(GET_PROPERTY)
3296          ),
3297          undefined,
3298          [ts.factory.createStringLiteral(name)]
3299        ),
3300        ts.factory.createIdentifier(TO_NUMBER)
3301      ),
3302      undefined,
3303      []
3304    );
3305  }
3306
3307  private createNewIncrDecrNodes(variableName: string, token: number): IncrementDecrementNodeInfo {
3308    const update = ts.factory.createBinaryExpression(
3309      ts.factory.createIdentifier(variableName),
3310      ts.factory.createToken(token),
3311      ts.factory.createNumericLiteral('1')
3312    );
3313
3314    const node = ts.factory.createBinaryExpression(
3315      ts.factory.createIdentifier(variableName),
3316      ts.factory.createToken(ts.SyntaxKind.EqualsToken),
3317      update
3318    );
3319
3320    return {
3321      addOrDecrOperation: update,
3322      varAssignText: this.printer.printNode(ts.EmitHint.Unspecified, node, this.sourceFile)
3323    };
3324  }
3325
3326  private createSetProperty(expression: string, field: string, value: ts.Expression): string {
3327    const node = ts.factory.createCallExpression(
3328      ts.factory.createPropertyAccessExpression(
3329        ts.factory.createIdentifier(expression),
3330        ts.factory.createIdentifier(SET_PROPERTY)
3331      ),
3332      undefined,
3333      [ts.factory.createIdentifier(field), value]
3334    );
3335
3336    return this.printer.printNode(ts.EmitHint.Unspecified, node, this.sourceFile);
3337  }
3338
3339  fixVariableDeclaration(node: ts.VariableDeclaration, isEnum: boolean): Autofix[] | undefined {
3340    const initializer = node.initializer;
3341    const name = node.name;
3342    const sym = this.typeChecker.getSymbolAtLocation(name);
3343    if (!sym) {
3344      return undefined;
3345    }
3346
3347    const type = this.typeChecker.getTypeOfSymbolAtLocation(sym, name);
3348    const typeText = this.typeChecker.typeToString(type);
3349    const typeFlags = type.flags;
3350
3351    if (!TsUtils.isNumberLike(type, typeText, isEnum)) {
3352      return undefined;
3353    }
3354
3355    let typeNode: ts.TypeNode;
3356    if (typeText === STRINGLITERAL_NUMBER || (typeFlags & ts.TypeFlags.NumberLiteral) !== 0 || isEnum) {
3357      typeNode = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
3358    } else if (typeText === STRINGLITERAL_NUMBER_ARRAY) {
3359      typeNode = ts.factory.createArrayTypeNode(ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword));
3360    } else {
3361      return undefined;
3362    }
3363
3364    const newVarDecl = ts.factory.createVariableDeclaration(name, undefined, typeNode, initializer);
3365    const parent = node.parent;
3366    if (!ts.isVariableDeclarationList(parent)) {
3367      return undefined;
3368    }
3369    const text = this.printer.printNode(ts.EmitHint.Unspecified, newVarDecl, node.getSourceFile());
3370    return [{ start: node.getStart(), end: node.getEnd(), replacementText: text }];
3371  }
3372
3373  fixPropertyDeclarationNumericSemanticsArray(node: ts.PropertyDeclaration): Autofix[] {
3374    const newProperty = ts.factory.createPropertyDeclaration(
3375      node.modifiers,
3376      node.name,
3377      undefined,
3378      ts.factory.createArrayTypeNode(ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)),
3379      node.initializer
3380    );
3381
3382    const replacementText = this.nonCommentPrinter.printNode(
3383      ts.EmitHint.Unspecified,
3384      newProperty,
3385      node.getSourceFile()
3386    );
3387
3388    return [
3389      {
3390        start: node.getStart(),
3391        end: node.getEnd(),
3392        replacementText: replacementText
3393      }
3394    ];
3395  }
3396
3397  /**
3398   * Transforms a call expression invoking an imported function or method into its interop equivalent.
3399   * - For direct calls like foo() or bar(123), transforms to foo.invoke() or bar.invoke(ESValue.wrap(123))
3400   * - For property access calls like foo.bar(123), transforms to foo.invokeMethod('bar', ESValue.wrap(123))
3401   * @param expression The call expression node to transform.
3402   * @returns Autofix array or undefined.
3403   */
3404  fixInteropInvokeExpression(expression: ts.CallExpression): Autofix[] | undefined {
3405    const callee = expression.expression;
3406    const args = this.createArgs(expression.arguments);
3407
3408    let replacement: ts.CallExpression;
3409
3410    if (ts.isPropertyAccessExpression(callee)) {
3411      // For expressions like foo.bar(123) => foo.invokeMethod('bar', ...)
3412      replacement = ts.factory.createCallExpression(
3413        ts.factory.createPropertyAccessExpression(callee.expression, ts.factory.createIdentifier(INVOKE_METHOD)),
3414        undefined,
3415        [ts.factory.createStringLiteral(callee.name.getText()), ...args || []]
3416      );
3417    } else if (ts.isIdentifier(callee)) {
3418      // For expressions like foo() or bar(123) => foo.invoke(...) or bar.invoke(...)
3419      replacement = ts.factory.createCallExpression(
3420        ts.factory.createPropertyAccessExpression(callee, ts.factory.createIdentifier(INVOKE)),
3421        undefined,
3422        args
3423      );
3424    } else {
3425      return undefined;
3426    }
3427
3428    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, replacement, expression.getSourceFile());
3429    return [{ start: expression.getStart(), end: expression.getEnd(), replacementText }];
3430  }
3431
3432  fixInteropInstantiateExpression(
3433    express: ts.NewExpression,
3434    args: ts.NodeArray<ts.Expression> | undefined
3435  ): Autofix[] | undefined {
3436    const statements = ts.factory.createCallExpression(
3437      ts.factory.createPropertyAccessExpression(
3438        ts.factory.createIdentifier(express.expression.getText()),
3439        ts.factory.createIdentifier(INSTANTIATE)
3440      ),
3441      undefined,
3442      this.createArgs(args)
3443    );
3444    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, statements, express.getSourceFile());
3445    return [{ start: express.getStart(), end: express.getEnd(), replacementText: replacementText }];
3446  }
3447
3448  createArgs(args: ts.NodeArray<ts.Expression> | undefined): ts.Expression[] | undefined {
3449    void this;
3450    if (args && args.length > 0) {
3451      return args.map((arg) => {
3452        return ts.factory.createCallExpression(
3453          ts.factory.createPropertyAccessExpression(
3454            ts.factory.createIdentifier(ES_VALUE),
3455            ts.factory.createIdentifier(WRAP)
3456          ),
3457          undefined,
3458          [ts.factory.createIdentifier(arg.getText())]
3459        );
3460      });
3461    }
3462    return undefined;
3463  }
3464
3465  fixParameter(param: ts.ParameterDeclaration): Autofix[] {
3466    const newParam = ts.factory.createParameterDeclaration(
3467      param.modifiers,
3468      param.dotDotDotToken,
3469      param.name,
3470      param.questionToken,
3471      ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
3472      param.initializer
3473    );
3474    const text = this.printer.printNode(ts.EmitHint.Unspecified, newParam, param.getSourceFile());
3475    return [
3476      {
3477        start: param.getStart(),
3478        end: param.getEnd(),
3479        replacementText: text
3480      }
3481    ];
3482  }
3483
3484  fixPropertyDeclaration(node: ts.PropertyDeclaration): Autofix[] | undefined {
3485    const initializer = node.initializer;
3486    if (initializer === undefined) {
3487      return undefined;
3488    }
3489    const propType = this.typeChecker.getTypeAtLocation(node);
3490    let propTypeNode: ts.TypeNode;
3491    if (propType.flags & ts.TypeFlags.NumberLike) {
3492      propTypeNode = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
3493    } else {
3494      const inferredTypeNode = this.typeChecker.typeToTypeNode(propType, undefined, ts.NodeBuilderFlags.None);
3495
3496      if (!inferredTypeNode || !this.utils.isSupportedType(inferredTypeNode)) {
3497        return undefined;
3498      }
3499      propTypeNode = inferredTypeNode;
3500    }
3501
3502    const questionOrExclamationToken = node.questionToken ?? node.exclamationToken ?? undefined;
3503    const newPropDecl = ts.factory.createPropertyDeclaration(
3504      node.modifiers,
3505      node.name,
3506      questionOrExclamationToken,
3507      propTypeNode,
3508      initializer
3509    );
3510
3511    const text = this.nonCommentPrinter.printNode(ts.EmitHint.Unspecified, newPropDecl, node.getSourceFile());
3512    return [{ start: node.getStart(), end: node.getEnd(), replacementText: text }];
3513  }
3514
3515  fixFunctionDeclarationly(
3516    callExpr: ts.CallExpression,
3517    resolvedTypeArgs: ts.NodeArray<ts.TypeNode>
3518  ): Autofix[] | undefined {
3519    if (callExpr.typeArguments && callExpr.typeArguments.length > 0) {
3520      return undefined;
3521    }
3522    const newCallExpr = ts.factory.createCallExpression(callExpr.expression, resolvedTypeArgs, callExpr.arguments);
3523    const text = this.printer.printNode(ts.EmitHint.Unspecified, newCallExpr, callExpr.getSourceFile());
3524    return [
3525      {
3526        start: callExpr.getStart(),
3527        end: callExpr.getEnd(),
3528        replacementText: text
3529      }
3530    ];
3531  }
3532
3533  checkEnumMemberNameConflict(tsEnumMember: ts.EnumMember, autofix: Autofix[] | undefined): void {
3534    if (!autofix?.length) {
3535      return;
3536    }
3537
3538    const parentEnum = tsEnumMember.parent;
3539    if (!this.hasNameConflict(parentEnum, tsEnumMember, autofix)) {
3540      return;
3541    }
3542
3543    const existingNames = this.collectExistingNames(parentEnum, tsEnumMember);
3544    this.adjustAutofixNames(autofix, existingNames);
3545  }
3546
3547  hasNameConflict(parentEnum: ts.EnumDeclaration, tsEnumMember: ts.EnumMember, autofix: Autofix[]): boolean {
3548    void this;
3549    return parentEnum.members.some((member) => {
3550      return (
3551        member !== tsEnumMember &&
3552        (ts.isStringLiteral(member.name) || member.name.getText() === autofix[0].replacementText)
3553      );
3554    });
3555  }
3556
3557  collectExistingNames(parentEnum: ts.EnumDeclaration, tsEnumMember: ts.EnumMember): Set<string> {
3558    void this;
3559    return new Set(
3560      parentEnum.members.
3561        filter((m) => {
3562          return m !== tsEnumMember;
3563        }).
3564        map((m) => {
3565          const nameNode = m.name;
3566          if (ts.isStringLiteral(nameNode)) {
3567            const fix = this.fixLiteralAsPropertyNamePropertyName(nameNode);
3568            return fix?.[0]?.replacementText || nameNode.text;
3569          }
3570          return nameNode.getText();
3571        })
3572    );
3573  }
3574
3575  adjustAutofixNames(autofix: Autofix[], existingNames: Set<string>): void {
3576    void this;
3577    const baseName = autofix[0].replacementText;
3578    let newName = baseName;
3579    let counter = 1;
3580
3581    while (existingNames.has(newName)) {
3582      newName = `${baseName}_${counter++}`;
3583    }
3584
3585    autofix.forEach((fix) => {
3586      fix.replacementText = newName;
3587    });
3588  }
3589
3590  removeImport(ident: ts.Identifier, importSpecifier: ts.ImportSpecifier): Autofix[] | undefined {
3591    const namedImports = importSpecifier.parent;
3592    const importClause = namedImports.parent;
3593    const importDeclaration = importClause.parent;
3594    if (!importDeclaration || !importClause) {
3595      return undefined;
3596    }
3597
3598    if (namedImports.elements.length === 1 && !importClause.name) {
3599      return this.removeNode(importDeclaration);
3600    }
3601
3602    if (namedImports.elements.length <= 0) {
3603      return undefined;
3604    }
3605
3606    const specifiers = namedImports.elements.filter((specifier) => {
3607      return specifier.name.text !== ident.text;
3608    });
3609
3610    const newClause = ts.factory.createImportClause(
3611      importClause.isTypeOnly,
3612      importClause.name,
3613      ts.factory.createNamedImports(specifiers)
3614    );
3615
3616    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, newClause, ident.getSourceFile());
3617    return [{ start: importClause.getStart(), end: importClause.getEnd(), replacementText }];
3618  }
3619
3620  fixInterfaceImport(
3621    interfacesNeedToImport: Set<string>,
3622    interfacesAlreadyImported: Set<string>,
3623    sourceFile: ts.SourceFile
3624  ): Autofix[] {
3625    const importSpecifiers: ts.ImportSpecifier[] = [];
3626    interfacesNeedToImport.forEach((interfaceName) => {
3627      if (interfacesAlreadyImported.has(interfaceName)) {
3628        return;
3629      }
3630      const identifier = ts.factory.createIdentifier(interfaceName);
3631      importSpecifiers.push(ts.factory.createImportSpecifier(false, undefined, identifier));
3632    });
3633    const importDeclaration = ts.factory.createImportDeclaration(
3634      undefined,
3635      ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports(importSpecifiers)),
3636      ts.factory.createStringLiteral(ARKUI_PACKAGE_NAME, true),
3637      undefined
3638    );
3639
3640    const leadingComments = ts.getLeadingCommentRanges(sourceFile.getFullText(), 0);
3641    let annotationEndLine = 0;
3642    let annotationEndPos = 0;
3643    if (leadingComments && leadingComments.length > 0) {
3644      annotationEndPos = leadingComments[leadingComments.length - 1].end;
3645      annotationEndLine = sourceFile.getLineAndCharacterOfPosition(annotationEndPos).line;
3646    }
3647
3648    let text = Autofixer.formatImportStatement(
3649      this.printer.printNode(ts.EmitHint.Unspecified, importDeclaration, sourceFile)
3650    );
3651    if (annotationEndPos !== 0) {
3652      text = this.getNewLine() + this.getNewLine() + text;
3653    }
3654
3655    const codeStartLine = sourceFile.getLineAndCharacterOfPosition(sourceFile.getStart()).line;
3656    for (let i = 2; i > codeStartLine - annotationEndLine; i--) {
3657      text = text + this.getNewLine();
3658    }
3659    return [{ start: annotationEndPos, end: annotationEndPos, replacementText: text }];
3660  }
3661
3662  private static formatImportStatement(stmt: string): string {
3663    return stmt.replace(/\{([^}]+)\}/, (match, importList) => {
3664      const items = importList.split(',').map((item) => {
3665        return item.trim();
3666      });
3667
3668      if (items.length > 1) {
3669        const formattedList = items
3670          .map((item) => {
3671            return `  ${item.trim()},`;
3672          })
3673          .join('\n');
3674        return `{\n${formattedList}\n}`;
3675      }
3676      return `{${importList}}`;
3677    });
3678  }
3679
3680
3681  fixStylesDecoratorGlobal(
3682    funcDecl: ts.FunctionDeclaration,
3683    calls: ts.Identifier[],
3684    needImport: Set<string>
3685  ): Autofix[] | undefined {
3686    const block = funcDecl.body;
3687    const parameters: ts.MemberName[] = [];
3688    const values: ts.Expression[][] = [];
3689    const statements = block?.statements;
3690    Autofixer.getParamsAndValues(statements, parameters, values);
3691    const newBlock = Autofixer.createBlock(parameters, values, ts.factory.createIdentifier(INSTANCE_IDENTIFIER));
3692    const parameDecl = ts.factory.createParameterDeclaration(
3693      undefined,
3694      undefined,
3695      ts.factory.createIdentifier(INSTANCE_IDENTIFIER),
3696      undefined,
3697      ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(COMMON_METHOD_IDENTIFIER), undefined),
3698      undefined
3699    );
3700    const returnType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
3701    const newFuncDecl = Autofixer.createFunctionDeclaration(funcDecl, undefined, parameDecl, returnType, newBlock);
3702    needImport.add(COMMON_METHOD_IDENTIFIER);
3703    const text = this.printer.printNode(ts.EmitHint.Unspecified, newFuncDecl, funcDecl.getSourceFile());
3704    const autofix = [{ start: funcDecl.getStart(), end: funcDecl.getEnd(), replacementText: text }];
3705    this.addAutofixFromCalls(calls, autofix, funcDecl.name as ts.Identifier);
3706    return autofix;
3707  }
3708
3709  fixStylesDecoratorStruct(
3710    methodDecl: ts.MethodDeclaration,
3711    calls: ts.Identifier[],
3712    needImport: Set<string>
3713  ): Autofix[] | undefined {
3714    const block = methodDecl.body;
3715    const parameters: ts.MemberName[] = [];
3716    const values: ts.Expression[][] = [];
3717    const statements = block?.statements;
3718    const type = ts.factory.createTypeReferenceNode(
3719      ts.factory.createIdentifier(CustomDecoratorName.CustomStyles),
3720      undefined
3721    );
3722    Autofixer.getParamsAndValues(statements, parameters, values);
3723    const newBlock = Autofixer.createBlock(parameters, values, ts.factory.createIdentifier(INSTANCE_IDENTIFIER));
3724    const parameDecl = ts.factory.createParameterDeclaration(
3725      undefined,
3726      undefined,
3727      ts.factory.createIdentifier(INSTANCE_IDENTIFIER),
3728      undefined,
3729      ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(COMMON_METHOD_IDENTIFIER), undefined),
3730      undefined
3731    );
3732    const arrowFunc = ts.factory.createArrowFunction(
3733      undefined,
3734      undefined,
3735      [parameDecl],
3736      ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword),
3737      ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
3738      newBlock
3739    );
3740    const newModifiers = ts.getModifiers(methodDecl)?.filter((modifier) => {
3741      return !(ts.isDecorator(modifier) && TsUtils.getDecoratorName(modifier) === CustomDecoratorName.Styles);
3742    });
3743    const expr = ts.factory.createPropertyDeclaration(newModifiers, methodDecl.name, undefined, type, arrowFunc);
3744    needImport.add(COMMON_METHOD_IDENTIFIER);
3745    let text = this.printer.printNode(ts.EmitHint.Unspecified, expr, methodDecl.getSourceFile());
3746    const startPos = this.sourceFile.getLineAndCharacterOfPosition(methodDecl.getStart()).character;
3747    text = this.adjustIndentation(text, startPos);
3748    const autofix = [{ start: methodDecl.getStart(), end: methodDecl.getEnd(), replacementText: text }];
3749    const argument = ts.factory.createPropertyAccessExpression(
3750      ts.factory.createThis(),
3751      methodDecl.name as ts.Identifier
3752    );
3753    this.addAutofixFromCalls(calls, autofix, argument);
3754    return autofix;
3755  }
3756
3757  private adjustIndentation(text: string, startPos: number): string {
3758    const lines = text.split(this.getNewLine());
3759    if (lines.length <= 1) {
3760      return text;
3761    }
3762
3763    const firstLine = lines[0];
3764    const secondLine = lines[1];
3765    const currentIndent = secondLine.match(/^\s*/)?.[0].length || 0;
3766    const indentBase = startPos - (currentIndent - INDENT_STEP);
3767
3768    const middleLines = lines.slice(1, -1).map((line) => {
3769      if (indentBase > 0) {
3770        return ' '.repeat(indentBase) + line;
3771      }
3772      return line;
3773    });
3774
3775    const lastLine = ' '.repeat(startPos) + lines[lines.length - 1];
3776    return [firstLine, ...middleLines, lastLine].join(this.getNewLine());
3777  }
3778
3779  private addAutofixFromCalls(calls: ts.Identifier[], autofix: Autofix[], argument: ts.Expression): void {
3780    calls.forEach((call) => {
3781      const callExpr = ts.factory.createCallExpression(
3782        ts.factory.createIdentifier(APPLY_STYLES_IDENTIFIER),
3783        undefined,
3784        [argument]
3785      );
3786
3787      const start: number = call.getStart();
3788      let end: number = 0;
3789      const expr = call.parent;
3790      if (ts.isCallExpression(expr)) {
3791        end = expr.getEnd();
3792      }
3793      if (ts.isPropertyAccessExpression(expr) && ts.isCallExpression(expr.parent)) {
3794        end = expr.parent.getEnd();
3795      }
3796      if (end === 0) {
3797        return;
3798      }
3799      const text = this.printer.printNode(ts.EmitHint.Unspecified, callExpr, call.getSourceFile());
3800      autofix.push({ start: start, end: end, replacementText: text });
3801    });
3802  }
3803
3804  fixStateStyles(
3805    object: ts.ObjectLiteralExpression,
3806    startNode: ts.Node,
3807    needImport: Set<string>
3808  ): Autofix[] | undefined {
3809    const properties = object.properties;
3810    const assignments: ts.PropertyAssignment[] = [];
3811    for (let i = 0; i < properties.length; i++) {
3812      const property = properties[i];
3813      const stateStyle = property.name;
3814      if (!stateStyle || !ts.isIdentifier(stateStyle) || !ts.isPropertyAssignment(property)) {
3815        return undefined;
3816      }
3817      if (!ts.isObjectLiteralExpression(property.initializer)) {
3818        assignments.push(property);
3819        continue;
3820      }
3821      const propAssignments = property.initializer.properties;
3822      const parameters: ts.MemberName[] = [];
3823      const values: ts.Expression[][] = [];
3824      for (let j = 0; j < propAssignments.length; j++) {
3825        const propAssignment = propAssignments[j];
3826        const tempParas: ts.MemberName[] = [];
3827        const tempVals: ts.Expression[][] = [];
3828        Autofixer.traverseNodes(propAssignment, tempParas, tempVals);
3829        if (
3830          ts.isPropertyAssignment(propAssignment) &&
3831          ts.isCallExpression(propAssignment.initializer) &&
3832          ts.isPropertyAccessExpression(propAssignment.initializer.expression) &&
3833          ts.isCallExpression(propAssignment.initializer.expression.expression)
3834        ) {
3835          tempParas.reverse();
3836          tempVals.reverse();
3837        }
3838        for (let k = 0; k < tempParas.length; k++) {
3839          parameters.push(tempParas[k]);
3840          values.push(tempVals[k]);
3841        }
3842      }
3843      const assignment = Autofixer.createPropertyAssignment(parameters, values, stateStyle);
3844      assignments.push(assignment);
3845    }
3846    needImport.add(COMMON_METHOD_IDENTIFIER);
3847    const newExpr = ts.factory.createObjectLiteralExpression(assignments, true);
3848    let text = this.printer.printNode(ts.EmitHint.Unspecified, newExpr, object.getSourceFile());
3849    const startPos = this.sourceFile.getLineAndCharacterOfPosition(startNode.getStart()).character - 1;
3850    text = this.adjustIndentation(text, startPos);
3851    return [{ start: object.getStart(), end: object.getEnd(), replacementText: text }];
3852  }
3853
3854  private static createPropertyAssignment(
3855    stateParam: ts.MemberName[],
3856    sateValue: ts.Expression[][],
3857    stateStyle: ts.Identifier
3858  ): ts.PropertyAssignment {
3859    const block = Autofixer.createBlock(stateParam, sateValue, ts.factory.createIdentifier(INSTANCE_IDENTIFIER));
3860    const parameterDecl = ts.factory.createParameterDeclaration(
3861      undefined,
3862      undefined,
3863      ts.factory.createIdentifier(INSTANCE_IDENTIFIER),
3864      undefined,
3865      ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(COMMON_METHOD_IDENTIFIER), undefined),
3866      undefined
3867    );
3868    const voidToken = ts.factory.createToken(ts.SyntaxKind.VoidKeyword);
3869    const arrowToken = ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken);
3870    const property = ts.factory.createPropertyAssignment(
3871      stateStyle,
3872      ts.factory.createArrowFunction(undefined, undefined, [parameterDecl], voidToken, arrowToken, block)
3873    );
3874    return property;
3875  }
3876
3877  fixDataObservation(classDecls: ts.ClassDeclaration[]): Autofix[] | undefined {
3878    const autofixes: Autofix[] = [];
3879    classDecls.forEach((classDecl) => {
3880      const observedDecorator = ts.factory.createDecorator(ts.factory.createIdentifier(CustomDecoratorName.Observed));
3881      const sourceFile = classDecl.getSourceFile();
3882      const text = this.printer.printNode(ts.EmitHint.Unspecified, observedDecorator, sourceFile) + this.getNewLine();
3883      const autofix = { start: classDecl.getStart(), end: classDecl.getStart(), replacementText: text };
3884      autofixes.push(autofix);
3885    });
3886    return autofixes.length !== 0 ? autofixes : undefined;
3887  }
3888
3889  fixInteropTsType(
3890    binaryExpr: ts.BinaryExpression,
3891    lhs: ts.PropertyAccessExpression,
3892    rhs: ts.Expression
3893  ): Autofix[] | undefined {
3894    void this;
3895    const base = lhs.expression.getText();
3896    const prop = lhs.name.text;
3897    const replacementText = `${base}.setProperty('${prop}',ESValue.wrap(${rhs.getText()}))`;
3898
3899    return [{ start: binaryExpr.getStart(), end: binaryExpr.getEnd(), replacementText }];
3900  }
3901
3902  /**
3903   * Autofix for `foo instanceof Foo` → `foo.isInstanceOf(Foo)`.
3904   *
3905   * @param node The binary `instanceof` expression node.
3906   * @returns A single Autofix replacing the entire `foo instanceof Foo` text.
3907   */
3908  fixInteropJsInstanceOfExpression(node: ts.BinaryExpression): Autofix[] {
3909    // left-hand and right-hand operands of the `instanceof`
3910    const leftExpr = node.left;
3911    const rightExpr = node.right;
3912
3913    // build: leftExpr.isInstanceOf(rightExpr)
3914    const callExpr = ts.factory.createCallExpression(
3915      ts.factory.createPropertyAccessExpression(leftExpr, ts.factory.createIdentifier(IS_INSTANCE_OF)),
3916      undefined,
3917      [rightExpr]
3918    );
3919
3920    // render back to source text
3921    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, callExpr, node.getSourceFile());
3922
3923    return [{ replacementText, start: node.getStart(), end: node.getEnd() }];
3924  }
3925
3926  createReplacementForJsIndirectImportPropertyAccessExpression(node: ts.PropertyAccessExpression): Autofix[] {
3927    // Bypass eslint-check
3928    void this;
3929
3930    const objName = node.expression.getText();
3931    const propName = node.name.getText();
3932
3933    let start = node.getStart();
3934    let end = node.getEnd();
3935    let replacementText = `${objName}.${GET_PROPERTY}('${propName}')`;
3936
3937    // Check if there is an "as number" type assertion in the statement
3938    if (ts.isAsExpression(node.parent) && node.parent.type.kind === ts.SyntaxKind.NumberKeyword) {
3939      replacementText += '.toNumber()';
3940      start = node.parent.getStart();
3941      end = node.parent.getEnd();
3942    }
3943
3944    return [{ replacementText, start, end }];
3945  }
3946
3947  createReplacementForJsImportPropertyAccessExpression(node: ts.PropertyAccessExpression): Autofix[] {
3948    const objName = node.expression.getText();
3949    const propName = node.name.getText();
3950
3951    const start = node.getStart();
3952    const end = node.getEnd();
3953
3954    const typeTag = this.utils.findTypeOfNodeForConversion(node);
3955    const replacement = `${objName}.${GET_PROPERTY}('${propName}')${typeTag}`;
3956
3957    return [{ replacementText: replacement, start, end }];
3958  }
3959
3960  /**
3961   * Converts a JS element access (e.g. `arr[index]`) into the corresponding
3962   * interop call:
3963   *   - On assignment (`arr[index] = value`), emits `arr.setProperty(index, ESValue.wrap(value))`
3964   *   - On read, emits `arr.getProperty(index)` plus any type conversion suffix
3965   *
3966   * @param elementAccessExpr The original `ElementAccessExpression` node.
3967   * @returns An array with a single `Autofix` describing the replacement range and text.
3968   */
3969  fixJsImportElementAccessExpression(elementAccessExpr: ts.ElementAccessExpression): Autofix[] {
3970    const parent = elementAccessExpr.parent;
3971
3972    const isAssignment =
3973      parent !== undefined && ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken;
3974
3975    // array identifier (e.g. "arr")
3976    const identifierNode = elementAccessExpr.expression as ts.Identifier;
3977
3978    let replacementText: string;
3979    if (isAssignment) {
3980      // arr.setProperty(index, ESValue.wrap(value))
3981      const wrapped = ts.factory.createCallExpression(
3982        ts.factory.createPropertyAccessExpression(
3983          ts.factory.createIdentifier(ES_VALUE),
3984          ts.factory.createIdentifier(WRAP)
3985        ),
3986        undefined,
3987        [parent.right]
3988      );
3989
3990      const callExpr = ts.factory.createCallExpression(
3991        ts.factory.createPropertyAccessExpression(identifierNode, ts.factory.createIdentifier(SET_PROPERTY)),
3992        undefined,
3993        [elementAccessExpr.argumentExpression, wrapped]
3994      );
3995
3996      replacementText = this.printer.printNode(ts.EmitHint.Unspecified, callExpr, elementAccessExpr.getSourceFile());
3997    } else {
3998      // arr.getProperty(index) plus conversion
3999      const callExpr = ts.factory.createCallExpression(
4000        ts.factory.createPropertyAccessExpression(identifierNode, ts.factory.createIdentifier(GET_PROPERTY)),
4001        undefined,
4002        [elementAccessExpr.argumentExpression]
4003      );
4004
4005      replacementText =
4006        this.printer.printNode(ts.EmitHint.Unspecified, callExpr, elementAccessExpr.getSourceFile()) +
4007        this.utils.findTypeOfNodeForConversion(elementAccessExpr);
4008    }
4009
4010    const start = isAssignment ? (parent as ts.Node).getStart() : elementAccessExpr.getStart();
4011    const end = isAssignment ? (parent as ts.Node).getEnd() : elementAccessExpr.getEnd();
4012
4013    return [{ replacementText, start, end }];
4014  }
4015
4016  /**
4017   * Replace each loop‐variable reference (e.g. `element`) with
4018   * `array.getProperty(i)` plus appropriate conversion.
4019   *
4020   * @param identifier The Identifier node of the loop variable usage.
4021   * @param arrayName  The name of the array being iterated.
4022   */
4023  fixInteropArrayElementUsage(identifier: ts.Identifier, arrayName: string): Autofix {
4024    // arr.getProperty(i)
4025    const callExpr = ts.factory.createCallExpression(
4026      ts.factory.createPropertyAccessExpression(
4027        ts.factory.createIdentifier(arrayName),
4028        ts.factory.createIdentifier(GET_PROPERTY)
4029      ),
4030      undefined,
4031      [ts.factory.createIdentifier('i')]
4032    );
4033
4034    // Print and append proper conversion suffix
4035    const printed = this.printer.printNode(ts.EmitHint.Unspecified, callExpr, identifier.getSourceFile());
4036    const replacementText = printed + this.utils.findTypeOfNodeForConversion(identifier);
4037
4038    return { replacementText, start: identifier.getStart(), end: identifier.getEnd() };
4039  }
4040
4041  fixSharedArrayBufferConstructor(node: ts.NewExpression): Autofix[] | undefined {
4042    void this;
4043
4044    // Ensure it's a constructor call to SharedArrayBuffer
4045    if (!ts.isIdentifier(node.expression) || node.expression.text !== ESLIB_SHAREDARRAYBUFFER) {
4046      return undefined;
4047    }
4048
4049    // Construct replacement
4050    const replacementText = 'ArrayBuffer';
4051
4052    return [{ replacementText, start: node.expression.getStart(), end: node.expression.getEnd() }];
4053  }
4054
4055  fixSharedArrayBufferTypeReference(node: ts.TypeReferenceNode): Autofix[] | undefined {
4056    void this;
4057
4058    if (!ts.isIdentifier(node.typeName) || node.typeName.text !== ESLIB_SHAREDARRAYBUFFER) {
4059      return undefined;
4060    }
4061
4062    const replacementText = 'ArrayBuffer';
4063
4064    return [{ replacementText, start: node.getStart(), end: node.getEnd() }];
4065  }
4066
4067  /**
4068   * Converts a `for...of` over an interop array into
4069   * an index-based `for` loop using `getProperty("length")`.
4070   *
4071   * @param node The `ForOfStatement` node to fix.
4072   * @returns A single Autofix for the loop header replacement.
4073   */
4074  fixInteropArrayForOf(node: ts.ForOfStatement): Autofix {
4075    const iterableName = node.expression.getText();
4076
4077    const initializer = ts.factory.createVariableDeclarationList(
4078      [
4079        ts.factory.createVariableDeclaration(
4080          ts.factory.createIdentifier('i'),
4081          undefined,
4082          undefined,
4083          ts.factory.createNumericLiteral('0')
4084        )
4085      ],
4086      ts.NodeFlags.Let
4087    );
4088
4089    const lengthAccess = ts.factory.createCallExpression(
4090      ts.factory.createPropertyAccessExpression(
4091        ts.factory.createIdentifier(iterableName),
4092        ts.factory.createIdentifier(GET_PROPERTY)
4093      ),
4094      undefined,
4095      [ts.factory.createStringLiteral(LENGTH)]
4096    );
4097    const condition = ts.factory.createBinaryExpression(
4098      ts.factory.createIdentifier('i'),
4099      ts.SyntaxKind.LessThanToken,
4100      lengthAccess
4101    );
4102
4103    const incrementor = ts.factory.createPrefixUnaryExpression(
4104      ts.SyntaxKind.PlusPlusToken,
4105      ts.factory.createIdentifier('i')
4106    );
4107
4108    // Render just the "(initializer; condition; incrementor)" text:
4109    const headerText = [
4110      this.printer.printNode(ts.EmitHint.Unspecified, initializer, node.getSourceFile()),
4111      '; ',
4112      this.printer.printNode(ts.EmitHint.Unspecified, condition, node.getSourceFile()),
4113      '; ',
4114      this.printer.printNode(ts.EmitHint.Unspecified, incrementor, node.getSourceFile())
4115    ].join('');
4116
4117    // Only replace from the start of the initializer to the end of the 'of' expression
4118    const start = node.initializer.getStart();
4119    const end = node.expression.getEnd();
4120
4121    return { start, end, replacementText: headerText };
4122  }
4123
4124  fixAppStorageCallExpression(callExpr: ts.CallExpression): Autofix[] | undefined {
4125    const varDecl = Autofixer.findParentVariableDeclaration(callExpr);
4126    if (!varDecl || varDecl.type) {
4127      return undefined;
4128    }
4129
4130    const updatedVarDecl = ts.factory.updateVariableDeclaration(
4131      varDecl,
4132      varDecl.name,
4133      undefined,
4134      ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
4135      varDecl.initializer
4136    );
4137
4138    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, updatedVarDecl, varDecl.getSourceFile());
4139
4140    return [
4141      {
4142        replacementText,
4143        start: varDecl.getStart(),
4144        end: varDecl.getEnd()
4145      }
4146    ];
4147  }
4148
4149  private static findParentVariableDeclaration(node: ts.Node): ts.VariableDeclaration | undefined {
4150    while (node) {
4151      if (ts.isVariableDeclaration(node)) {
4152        return node;
4153      }
4154      node = node.parent;
4155    }
4156    return undefined;
4157  }
4158
4159  private static createVariableForInteropImport(
4160    newVarName: string,
4161    interopObject: string,
4162    interopMethod: string,
4163    interopPropertyOrModule: string
4164  ): ts.VariableStatement {
4165    const newVarDecl = ts.factory.createVariableStatement(
4166      undefined,
4167      ts.factory.createVariableDeclarationList(
4168        [
4169          ts.factory.createVariableDeclaration(
4170            ts.factory.createIdentifier(newVarName),
4171            undefined,
4172            undefined,
4173            ts.factory.createCallExpression(
4174              ts.factory.createPropertyAccessExpression(
4175                ts.factory.createIdentifier(interopObject),
4176                ts.factory.createIdentifier(interopMethod)
4177              ),
4178              undefined,
4179              [ts.factory.createStringLiteral(interopPropertyOrModule)]
4180            )
4181          )
4182        ],
4183        ts.NodeFlags.Let
4184      )
4185    );
4186    return newVarDecl;
4187  }
4188
4189  private static getOriginalNameAtSymbol(symbolName: string, symbol?: ts.Symbol): string {
4190    if (symbol) {
4191      const originalDeclaration = symbol.declarations?.[0];
4192      let originalName = '';
4193      if (originalDeclaration) {
4194        const isReturnNameOnSomeCase =
4195          ts.isFunctionDeclaration(originalDeclaration) ||
4196          ts.isClassDeclaration(originalDeclaration) ||
4197          ts.isInterfaceDeclaration(originalDeclaration) ||
4198          ts.isEnumDeclaration(originalDeclaration);
4199        if (isReturnNameOnSomeCase) {
4200          originalName = originalDeclaration.name?.text || symbolName;
4201        } else if (ts.isVariableDeclaration(originalDeclaration)) {
4202          originalName = originalDeclaration.name.getText();
4203        }
4204      }
4205      return originalName;
4206    }
4207    return '';
4208  }
4209
4210  private fixInterOpImportJsProcessNode(node: ts.Node): string | undefined {
4211    if (ts.isIdentifier(node)) {
4212      return node.text;
4213    } else if (ts.isCallExpression(node)) {
4214      const newArgs = this.createArgs(node.arguments);
4215      const callee = node.expression;
4216      switch (callee.kind) {
4217        case ts.SyntaxKind.PropertyAccessExpression: {
4218          const propertyAccessExpr = node.expression as ts.PropertyAccessExpression;
4219          const newCallExpr = this.createJSInvokeCallExpression(propertyAccessExpr.expression, INVOKE_METHOD, [
4220            ts.factory.createStringLiteral(propertyAccessExpr.name.text),
4221            ...newArgs || []
4222          ]);
4223
4224          if (!newCallExpr) {
4225            return undefined;
4226          }
4227          return this.printer.printNode(ts.EmitHint.Unspecified, newCallExpr, node.getSourceFile());
4228        }
4229        default: {
4230          const callExpr = this.createJSInvokeCallExpression(node.expression, INVOKE, [...newArgs || []]);
4231
4232          if (!callExpr) {
4233            return undefined;
4234          }
4235
4236          return this.printer.printNode(ts.EmitHint.Unspecified, callExpr, node.getSourceFile());
4237        }
4238      }
4239    } else if (ts.isPropertyAccessExpression(node)) {
4240      const base = this.fixInterOpImportJsProcessNode(node.expression);
4241      if (!base) {
4242        return undefined;
4243      }
4244      const propName = node.name.text;
4245      return `${base}.${GET_PROPERTY}('${propName}')`;
4246    } else if (ts.isNewExpression(node)) {
4247      const newArgs = this.createArgs(node.arguments);
4248      const newCallExpr = this.createJSInvokeCallExpression(node.expression, INSTANTIATE, [...newArgs || []]);
4249
4250      if (!newCallExpr) {
4251        return undefined;
4252      }
4253      return this.printer.printNode(ts.EmitHint.Unspecified, newCallExpr, node.getSourceFile());
4254    }
4255    return undefined;
4256  }
4257
4258  fixInterOpImportJs(
4259    importDecl: ts.ImportDeclaration,
4260    importClause: ts.ImportClause,
4261    moduleSpecifier: string,
4262    defaultSymbol?: ts.Symbol
4263  ): Autofix[] | undefined {
4264    let statements: string[] = [];
4265    statements = this.constructAndSaveimportDecl2Arrays(importDecl, moduleSpecifier, undefined, statements, true);
4266    if (importClause.name) {
4267      const symbolName = importClause.name.text;
4268      const originalName = Autofixer.getOriginalNameAtSymbol(symbolName, defaultSymbol);
4269      statements = this.constructAndSaveimportDecl2Arrays(importDecl, symbolName, originalName, statements, false);
4270    }
4271    const namedBindings = importClause.namedBindings;
4272    if (namedBindings && ts.isNamedImports(namedBindings)) {
4273      namedBindings.elements.map((element) => {
4274        const symbolName = element.name.text;
4275        const originalName = element.propertyName ? element.propertyName.text : symbolName;
4276        statements = this.constructAndSaveimportDecl2Arrays(importDecl, symbolName, originalName, statements, false);
4277        return statements;
4278      });
4279    }
4280    if (statements.length <= 0) {
4281      return undefined;
4282    }
4283    let lastImportEnd = this.lastImportEndMap.get(this.sourceFile.fileName);
4284    if (!lastImportEnd) {
4285      lastImportEnd = this.getLastImportEnd();
4286      this.lastImportEndMap.set(this.sourceFile.fileName, lastImportEnd);
4287    }
4288    return [
4289      { start: importDecl.getStart(), end: importDecl.getEnd(), replacementText: '' },
4290      {
4291        start: lastImportEnd,
4292        end: lastImportEnd,
4293        replacementText: statements.join(this.getNewLine()) + this.getNewLine()
4294      }
4295    ];
4296  }
4297
4298  private constructAndSaveimportDecl2Arrays(
4299    importDecl: ts.ImportDeclaration,
4300    symbolName: string,
4301    originalName: string | undefined,
4302    statements: string[],
4303    isLoad: boolean
4304  ): string[] {
4305    if (isLoad) {
4306      const newVarName = TsUtils.generateUniqueName(this.importVarNameGenerator, this.sourceFile);
4307      if (!newVarName) {
4308        return [];
4309      }
4310      this.modVarName = newVarName;
4311    }
4312    const propertyName = originalName || symbolName;
4313    const constructDeclInfo: string[] = isLoad ?
4314      [this.modVarName, ES_VALUE, LOAD] :
4315      [symbolName, this.modVarName, GET_PROPERTY];
4316    const newVarDecl = Autofixer.createVariableForInteropImport(
4317      constructDeclInfo[0],
4318      constructDeclInfo[1],
4319      constructDeclInfo[2],
4320      propertyName
4321    );
4322    const text = this.printer.printNode(ts.EmitHint.Unspecified, newVarDecl, importDecl.getSourceFile());
4323    statements.push(TsUtils.removeOrReplaceQuotes(text, true));
4324    return statements;
4325  }
4326
4327  private getLastImportEnd(): number {
4328    let lastImportEnd = 0;
4329    this.sourceFile.statements.forEach((statement) => {
4330      if (ts.isImportDeclaration(statement)) {
4331        lastImportEnd = statement.getEnd();
4332      }
4333    });
4334    return lastImportEnd;
4335  }
4336
4337  fixInteropPropertyAccessExpression(express: ts.PropertyAccessExpression): Autofix[] | undefined {
4338    let text: string = '';
4339    const statements = ts.factory.createCallExpression(
4340      ts.factory.createPropertyAccessExpression(express.expression, ts.factory.createIdentifier(GET_PROPERTY)),
4341      undefined,
4342      [ts.factory.createStringLiteral(express.name.getText())]
4343    );
4344    text = this.printer.printNode(ts.EmitHint.Unspecified, statements, express.getSourceFile());
4345    return [{ start: express.getStart(), end: express.getEnd(), replacementText: text }];
4346  }
4347
4348  fixInteropBinaryExpression(express: ts.BinaryExpression): Autofix[] | undefined {
4349    const left = express.left;
4350    const right = express.right;
4351    let objectName = '';
4352    let propertyName = '';
4353    if (ts.isPropertyAccessExpression(left)) {
4354      objectName = left.expression.getText();
4355      propertyName = left.name.text;
4356    } else {
4357      return undefined;
4358    }
4359    const statements = ts.factory.createCallExpression(
4360      ts.factory.createPropertyAccessExpression(
4361        ts.factory.createIdentifier(objectName),
4362        ts.factory.createIdentifier(SET_PROPERTY)
4363      ),
4364      undefined,
4365      [
4366        ts.factory.createStringLiteral(propertyName),
4367        ts.factory.createCallExpression(
4368          ts.factory.createPropertyAccessExpression(
4369            ts.factory.createIdentifier(ES_VALUE),
4370            ts.factory.createIdentifier(WRAP)
4371          ),
4372          undefined,
4373          [ts.factory.createIdentifier(right.getText())]
4374        )
4375      ]
4376    );
4377    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, statements, express.getSourceFile());
4378    return [{ start: express.getStart(), end: express.getEnd(), replacementText: replacementText }];
4379  }
4380
4381  fixInteropAsExpression(expression: ts.AsExpression): Autofix[] | undefined {
4382    const castMap: Partial<Record<ts.SyntaxKind, string>> = {
4383      [ts.SyntaxKind.StringKeyword]: 'toString',
4384      [ts.SyntaxKind.NumberKeyword]: 'toNumber',
4385      [ts.SyntaxKind.BooleanKeyword]: 'toBoolean',
4386      [ts.SyntaxKind.BigIntKeyword]: 'toBigInt'
4387    };
4388
4389    const castMethod = castMap[expression.type.kind];
4390    if (!castMethod) {
4391      return undefined;
4392    }
4393    const express = expression.expression;
4394    if (!ts.isPropertyAccessExpression(express)) {
4395      return undefined;
4396    }
4397
4398    const propertyAccess = ts.factory.createCallExpression(
4399      ts.factory.createPropertyAccessExpression(express.expression, ts.factory.createIdentifier(GET_PROPERTY)),
4400      undefined,
4401      [ts.factory.createStringLiteral(express.name.getText())]
4402    );
4403
4404    const finalCall = ts.factory.createCallExpression(
4405      ts.factory.createPropertyAccessExpression(propertyAccess, ts.factory.createIdentifier(castMethod)),
4406      undefined,
4407      []
4408    );
4409
4410    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, finalCall, expression.getSourceFile());
4411
4412    return [
4413      {
4414        start: expression.getStart(),
4415        end: expression.getEnd(),
4416        replacementText
4417      }
4418    ];
4419  }
4420
4421  fixInteropOperators(expr: ts.Expression): Autofix[] | undefined {
4422    if (ts.isPropertyAccessExpression(expr)) {
4423      return this.fixPropertyAccessToNumber(expr);
4424    }
4425
4426    if (ts.isIdentifier(expr)) {
4427      const symbol = this.utils.trueSymbolAtLocation(expr);
4428
4429      if (this.utils.isJsImport(expr)) {
4430        const toNumberCall = ts.factory.createCallExpression(
4431          ts.factory.createPropertyAccessExpression(expr, ts.factory.createIdentifier(TO_NUMBER)),
4432          undefined,
4433          []
4434        );
4435
4436        const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, toNumberCall, expr.getSourceFile());
4437
4438        return [
4439          {
4440            start: expr.getStart(),
4441            end: expr.getEnd(),
4442            replacementText
4443          }
4444        ];
4445      }
4446
4447      const decl = symbol?.declarations?.find(ts.isVariableDeclaration);
4448      if (decl?.initializer && ts.isPropertyAccessExpression(decl.initializer)) {
4449        return this.fixPropertyAccessToNumber(decl.initializer);
4450      }
4451    }
4452
4453    return undefined;
4454  }
4455
4456  private fixPropertyAccessToNumber(expr: ts.PropertyAccessExpression): Autofix[] {
4457    const getPropCall = ts.factory.createCallExpression(
4458      ts.factory.createPropertyAccessExpression(expr.expression, ts.factory.createIdentifier(GET_PROPERTY)),
4459      undefined,
4460      [ts.factory.createStringLiteral(expr.name.getText())]
4461    );
4462
4463    const toNumberCall = ts.factory.createCallExpression(
4464      ts.factory.createPropertyAccessExpression(getPropCall, ts.factory.createIdentifier(TO_NUMBER)),
4465      undefined,
4466      []
4467    );
4468
4469    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, toNumberCall, expr.getSourceFile());
4470
4471    return [
4472      {
4473        start: expr.getStart(),
4474        end: expr.getEnd(),
4475        replacementText
4476      }
4477    ];
4478  }
4479
4480  fixInteropArrayElementAccessExpression(express: ts.ElementAccessExpression): Autofix[] | undefined {
4481    const statements = ts.factory.createCallExpression(
4482      ts.factory.createPropertyAccessExpression(express.expression, ts.factory.createIdentifier(GET_PROPERTY)),
4483      undefined,
4484      [express.argumentExpression]
4485    );
4486    const text = this.printer.printNode(ts.EmitHint.Unspecified, statements, express.getSourceFile());
4487    return [{ start: express.getStart(), end: express.getEnd(), replacementText: text }];
4488  }
4489
4490  fixInteropArrayBinaryExpression(express: ts.BinaryExpression): Autofix[] | undefined {
4491    const left = express.left as ts.ElementAccessExpression;
4492    const right = express.right;
4493    const statements = ts.factory.createCallExpression(
4494      ts.factory.createPropertyAccessExpression(
4495        ts.factory.createIdentifier(left.expression.getText()),
4496        ts.factory.createIdentifier(SET_PROPERTY)
4497      ),
4498      undefined,
4499      [
4500        left.argumentExpression,
4501        ts.factory.createCallExpression(
4502          ts.factory.createPropertyAccessExpression(
4503            ts.factory.createIdentifier(ES_VALUE),
4504            ts.factory.createIdentifier(WRAP)
4505          ),
4506          undefined,
4507          [ts.factory.createIdentifier(right.getText())]
4508        )
4509      ]
4510    );
4511    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, statements, express.getSourceFile());
4512    return [{ start: express.getStart(), end: express.getEnd(), replacementText: replacementText }];
4513  }
4514
4515  fixInterOpImportJsOnTypeOf(typeofExpress: ts.TypeOfExpression): Autofix[] | undefined {
4516    const node = typeofExpress.expression;
4517    const start = typeofExpress.getStart();
4518    const end = typeofExpress.getEnd();
4519    const processed = this.fixInterOpImportJsProcessNode(node);
4520    if (!processed) {
4521      return undefined;
4522    }
4523    const replacementText = `${processed}.typeOf()`;
4524    return [{ start, end, replacementText }];
4525  }
4526
4527  fixInteropInterfaceConvertNum(express: ts.PrefixUnaryExpression): Autofix[] | undefined {
4528    const createConversionExpression = (propertyAccess: ts.PropertyAccessExpression): ts.Expression => {
4529      const getPropertyCall = ts.factory.createCallExpression(
4530        ts.factory.createPropertyAccessExpression(
4531          ts.factory.createIdentifier(propertyAccess.expression.getText()),
4532          ts.factory.createIdentifier(GET_PROPERTY)
4533        ),
4534        undefined,
4535        [ts.factory.createStringLiteral(propertyAccess.name.getText())]
4536      );
4537
4538      return ts.factory.createCallExpression(
4539        ts.factory.createPropertyAccessExpression(getPropertyCall, ts.factory.createIdentifier(TO_NUMBER)),
4540        undefined,
4541        []
4542      );
4543    };
4544
4545    let replacementExpression: ts.Expression | undefined;
4546    if (ts.isPropertyAccessExpression(express.operand)) {
4547      replacementExpression = createConversionExpression(express.operand);
4548    } else if (
4549      ts.isParenthesizedExpression(express.operand) &&
4550      ts.isPropertyAccessExpression(express.operand.expression)
4551    ) {
4552      replacementExpression = ts.factory.createParenthesizedExpression(
4553        createConversionExpression(express.operand.expression)
4554      );
4555    }
4556
4557    if (!replacementExpression) {
4558      return undefined;
4559    }
4560
4561    const text = this.printer.printNode(ts.EmitHint.Unspecified, replacementExpression, express.getSourceFile());
4562
4563    return [{ start: express.operand.getStart(), end: express.operand.getEnd(), replacementText: text }];
4564  }
4565
4566  fixImportClause(tsImportClause: ts.ImportClause): Autofix[] {
4567    const newImportClause = ts.factory.createImportClause(
4568      tsImportClause.isTypeOnly,
4569      tsImportClause.name,
4570      tsImportClause.namedBindings
4571    );
4572    const replacementText = this.printer.printNode(
4573      ts.EmitHint.Unspecified,
4574      newImportClause,
4575      tsImportClause.getSourceFile()
4576    );
4577    return [{ start: tsImportClause.getStart(), end: tsImportClause.getEnd(), replacementText }];
4578  }
4579
4580  fixInteropEqualityOperator(
4581    tsBinaryExpr: ts.BinaryExpression,
4582    binaryOperator: ts.BinaryOperator
4583  ): Autofix[] | undefined {
4584    const text = this.replaceInteropEqualityOperator(tsBinaryExpr, binaryOperator);
4585    if (text) {
4586      return [{ start: tsBinaryExpr.getStart(), end: tsBinaryExpr.getEnd(), replacementText: text }];
4587    }
4588    return undefined;
4589  }
4590
4591  replaceInteropEqualityOperator(
4592    tsBinaryExpr: ts.BinaryExpression,
4593    binaryOperator: ts.BinaryOperator
4594  ): string | undefined {
4595    const info = this.getInteropEqualityReplacementInfo(binaryOperator);
4596    if (!info) {
4597      return undefined;
4598    }
4599
4600    const tsLhsExpr = tsBinaryExpr.left;
4601    const tsRhsExpr = tsBinaryExpr.right;
4602    const callExpression = ts.factory.createCallExpression(
4603      ts.factory.createPropertyAccessExpression(
4604        ts.factory.createIdentifier(tsLhsExpr.getText()),
4605        ts.factory.createIdentifier(info.functionName)
4606      ),
4607      undefined,
4608      [ts.factory.createIdentifier(tsRhsExpr.getText())]
4609    );
4610
4611    let text = this.printer.printNode(ts.EmitHint.Unspecified, callExpression, tsBinaryExpr.getSourceFile());
4612    if (info.isNegative) {
4613      text = '!' + text;
4614    }
4615    return text;
4616  }
4617
4618  getInteropEqualityReplacementInfo(
4619    binaryOperator: ts.BinaryOperator
4620  ): { functionName: string; isNegative: boolean } | undefined {
4621    void this;
4622    switch (binaryOperator) {
4623      case ts.SyntaxKind.EqualsEqualsToken:
4624        return { functionName: ARE_EQUAL, isNegative: false };
4625      case ts.SyntaxKind.ExclamationEqualsToken:
4626        return { functionName: ARE_EQUAL, isNegative: true };
4627      case ts.SyntaxKind.EqualsEqualsEqualsToken:
4628        return { functionName: ARE_STRICTLY_EQUAL, isNegative: false };
4629      case ts.SyntaxKind.ExclamationEqualsEqualsToken:
4630        return { functionName: ARE_STRICTLY_EQUAL, isNegative: true };
4631      default:
4632        return undefined;
4633    }
4634  }
4635
4636  fixArrayIndexExprType(argExpr: ts.Expression): Autofix[] | undefined {
4637    void this;
4638    if (ts.isAsExpression(argExpr)) {
4639      const innerExpr = argExpr.expression;
4640      return [
4641        {
4642          start: argExpr.getStart(),
4643          end: argExpr.getEnd(),
4644          replacementText: `${innerExpr ? innerExpr.getText() : ''} as int`
4645        }
4646      ];
4647    }
4648
4649    if (ts.isBinaryExpression(argExpr)) {
4650      return [{ start: argExpr.getStart(), end: argExpr.getEnd(), replacementText: `(${argExpr.getText()}) as int` }];
4651    }
4652
4653    return [{ start: argExpr.getStart(), end: argExpr.getEnd(), replacementText: `${argExpr.getText()} as int` }];
4654  }
4655
4656  fixNoTsLikeFunctionCall(callExpr: ts.CallExpression): Autofix[] {
4657    void this;
4658    const expr = callExpr.expression;
4659    const hasOptionalChain = !!callExpr.questionDotToken;
4660
4661    const replacementText = hasOptionalChain
4662        ? `${expr.getText()}${callExpr.questionDotToken.getText()}unsafeCall`
4663        : `${expr.getText()}.unsafeCall`;
4664
4665    return [{
4666        start: expr.getStart(),
4667        end: hasOptionalChain
4668            ? callExpr.questionDotToken.getEnd()
4669            : expr.getEnd(),
4670        replacementText
4671    }];
4672  }
4673
4674  private static createBuiltInTypeInitializer(type: ts.TypeReferenceNode): ts.Expression | undefined {
4675    const typeName = type.typeName.getText();
4676
4677    switch (typeName) {
4678      case 'Date':
4679        return ts.factory.createNewExpression(ts.factory.createIdentifier('Date'), undefined, []);
4680      case 'Map':
4681      case 'Set':
4682        return ts.factory.createNewExpression(ts.factory.createIdentifier(typeName), type.typeArguments, []);
4683      case 'Promise':
4684        return ts.factory.createIdentifier('undefined');
4685      default:
4686        return this.isNullableType(type) ? ts.factory.createIdentifier('undefined') : undefined;
4687    }
4688  }
4689
4690  private static isNullableType(type: ts.TypeNode): boolean {
4691    if (type.kind === ts.SyntaxKind.UndefinedKeyword) {
4692      return true;
4693    }
4694
4695    if (ts.isUnionTypeNode(type)) {
4696      return type.types.some((t) => {
4697        return t.kind === ts.SyntaxKind.UndefinedKeyword;
4698      });
4699    }
4700
4701    return false;
4702  }
4703
4704  private static createExactObjectInitializer(type: ts.TypeLiteralNode): ts.ObjectLiteralExpression {
4705    const properties = type.members.
4706      filter((member): member is ts.PropertySignature => {
4707        return ts.isPropertySignature(member);
4708      }).
4709      map((member) => {
4710        const initializer = Autofixer.createInitializerForPropertySignature(member);
4711        if (initializer) {
4712          return ts.factory.createPropertyAssignment(member.name, initializer);
4713        }
4714        return null;
4715      }).
4716      filter((property): property is ts.PropertyAssignment => {
4717        return property !== null;
4718      });
4719
4720    return ts.factory.createObjectLiteralExpression(properties, true);
4721  }
4722
4723  private static createInitializerForPropertySignature(member: ts.PropertySignature): ts.Expression | undefined {
4724    return member.type ? Autofixer.createTypeBasedInitializer(member.type) : undefined;
4725  }
4726
4727  private static createTypeBasedInitializer(type?: ts.TypeNode): ts.Expression | undefined {
4728    if (!type) {
4729      return undefined;
4730    }
4731
4732    switch (type.kind) {
4733      case ts.SyntaxKind.StringKeyword:
4734        return ts.factory.createStringLiteral('');
4735      case ts.SyntaxKind.TypeLiteral:
4736        return Autofixer.createExactObjectInitializer(type as ts.TypeLiteralNode);
4737      case ts.SyntaxKind.ArrayType:
4738        return ts.factory.createArrayLiteralExpression([], false);
4739      case ts.SyntaxKind.TypeReference:
4740        return Autofixer.createBuiltInTypeInitializer(type as ts.TypeReferenceNode);
4741      default:
4742        return this.isNullableType(type) ? ts.factory.createIdentifier('undefined') : undefined;
4743    }
4744  }
4745
4746  private static isUserDefinedClass(type: ts.TypeReferenceNode): boolean {
4747    const builtInTypes = new Set(['Date', 'Array', 'Map', 'Set', 'Promise', 'RegExp', 'Function']);
4748    return !builtInTypes.has(type.typeName.getText());
4749  }
4750
4751  fixLimitedVoidType(
4752    node: ts.VariableDeclaration | ts.ParameterDeclaration | ts.PropertyDeclaration
4753  ): Autofix[] | undefined {
4754    const srcFile = node.getSourceFile();
4755    const newType = Autofixer.createNewTypeFromVoid(node.type);
4756    const newInit = Autofixer.createNewInitializer(node.initializer, newType);
4757
4758    const newDecl = Autofixer.createNewDeclaration(node, newType, newInit);
4759    if (!newDecl) {
4760      return undefined;
4761    }
4762
4763    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, newDecl, srcFile);
4764    return [{ start: node.getStart(), end: node.getEnd(), replacementText }];
4765  }
4766
4767  private static createNewTypeFromVoid(type: ts.TypeNode | undefined): ts.TypeNode {
4768    const identUndefined = ts.factory.createIdentifier(UNDEFINED_NAME);
4769    if (type && ts.isUnionTypeNode(type)) {
4770      const updatedTypes = type.types.map((t) => {
4771        return t.kind === ts.SyntaxKind.VoidKeyword ? ts.factory.createTypeReferenceNode(UNDEFINED_NAME) : t;
4772      });
4773      return ts.factory.createUnionTypeNode(updatedTypes);
4774    }
4775    return ts.factory.createTypeReferenceNode(identUndefined);
4776  }
4777
4778  private static createNewInitializer(initializer: ts.Expression | undefined, newType: ts.TypeNode): ts.Expression {
4779    const identUndefined = ts.factory.createIdentifier(UNDEFINED_NAME);
4780    if (!initializer) {
4781      return identUndefined;
4782    }
4783
4784    const stmts: ts.Statement[] = [
4785      ts.factory.createExpressionStatement(initializer),
4786      ts.factory.createReturnStatement(identUndefined)
4787    ];
4788    const funcBody = ts.factory.createBlock(stmts);
4789    const arrowFunc = ts.factory.createArrowFunction(
4790      undefined,
4791      undefined,
4792      [],
4793      newType,
4794      ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
4795      funcBody
4796    );
4797    return ts.factory.createCallExpression(ts.factory.createParenthesizedExpression(arrowFunc), undefined, undefined);
4798  }
4799
4800  private static createNewDeclaration(
4801    node: ts.VariableDeclaration | ts.ParameterDeclaration | ts.PropertyDeclaration,
4802    newType: ts.TypeNode,
4803    newInit: ts.Expression
4804  ): ts.Node | undefined {
4805    if (ts.isVariableDeclaration(node)) {
4806      return ts.factory.createVariableDeclaration(node.name, node.exclamationToken, newType, newInit);
4807    }
4808
4809    if (ts.isParameter(node)) {
4810      return ts.factory.createParameterDeclaration(
4811        node.modifiers,
4812        node.dotDotDotToken,
4813        node.name,
4814        node.questionToken,
4815        newType,
4816        node.initializer ? newInit : undefined
4817      );
4818    }
4819
4820    if (ts.isPropertyDeclaration(node)) {
4821      const optionalToken = node.questionToken || node.exclamationToken;
4822      return ts.factory.createPropertyDeclaration(node.modifiers, node.name, optionalToken, newType, newInit);
4823    }
4824
4825    return undefined;
4826  }
4827
4828  /**
4829   * Fixes function declarations/expressions that return `void` as part of a union.
4830   * Replaces `void` with `undefined` in the return type,
4831   * replaces `return;` with `return undefined;`,
4832   * and adds `return undefined;` if the function has no returns.
4833   */
4834  fixLimitedVoidTypeFunction(fn: ts.FunctionLikeDeclaration): Autofix[] | undefined {
4835    const fixes: Autofix[] = [];
4836    const returnType = fn.type;
4837    if (!returnType || !ts.isUnionTypeNode(returnType) || !TsUtils.typeContainsVoid(returnType)) {
4838      return undefined;
4839    }
4840
4841    const updatedTypes = returnType.types.map((t) => {
4842      return t.kind === ts.SyntaxKind.VoidKeyword ? ts.factory.createTypeReferenceNode(UNDEFINED_NAME) : t;
4843    });
4844
4845    const newType = ts.factory.createUnionTypeNode(updatedTypes);
4846    fixes.push({
4847      start: returnType.getStart(),
4848      end: returnType.getEnd(),
4849      replacementText: this.printer.printNode(ts.EmitHint.Unspecified, newType, fn.getSourceFile())
4850    });
4851
4852    let hasReturn = false;
4853    function visit(node: ts.Node): void {
4854      if (ts.isReturnStatement(node)) {
4855        hasReturn = true;
4856        if (!node.expression) {
4857          fixes.push({
4858            start: node.getStart(),
4859            end: node.getEnd(),
4860            replacementText: 'return undefined;'
4861          });
4862        }
4863      }
4864      ts.forEachChild(node, visit);
4865    }
4866    if (fn.body) {
4867      visit(fn.body);
4868
4869      if (!hasReturn) {
4870        if (ts.isBlock(fn.body)) {
4871          const lastBrace = fn.body.getEnd() - 1;
4872          fixes.push({
4873            start: lastBrace,
4874            end: lastBrace,
4875            replacementText: '\nreturn undefined;\n'
4876          });
4877        }
4878      }
4879    }
4880
4881    return fixes;
4882  }
4883
4884  private fixGenericCallNoTypeArgsWithContextualType(node: ts.NewExpression): Autofix[] | undefined {
4885    const contextualType = this.typeChecker.getContextualType(node);
4886    if (!contextualType) {
4887      return undefined;
4888    }
4889
4890    const typeArgs = Autofixer.getTypeArgumentsFromType(contextualType);
4891    if (typeArgs.length === 0) {
4892      return undefined;
4893    }
4894    const reference = typeArgs.map((arg) => {
4895      return ts.factory.createTypeReferenceNode(this.typeChecker.typeToString(arg));
4896    });
4897    return this.generateGenericTypeArgumentsAutofix(node, reference);
4898  }
4899
4900  fixGenericCallNoTypeArgs(node: ts.NewExpression): Autofix[] | undefined {
4901    const typeNode = this.getTypeNodeForNewExpression(node);
4902    if (!typeNode) {
4903      return this.fixGenericCallNoTypeArgsWithContextualType(node);
4904    }
4905    if (ts.isUnionTypeNode(typeNode)) {
4906      return this.fixGenericCallNoTypeArgsForUnionType(node, typeNode);
4907    }
4908    if (ts.isArrayTypeNode(typeNode)) {
4909      return this.fixGenericCallNoTypeArgsForArrayType(node, typeNode);
4910    }
4911    if (!ts.isTypeReferenceNode(typeNode) || typeNode.typeName.getText() !== node.expression.getText()) {
4912      return undefined;
4913    }
4914
4915    const srcFile = node.getSourceFile();
4916    const typeArgsText = `<${typeNode.typeArguments?.
4917      map((arg) => {
4918        return this.printer.printNode(ts.EmitHint.Unspecified, arg, srcFile);
4919      }).
4920      join(', ')}>`;
4921
4922    // Insert the type arguments immediately after the constructor name
4923    const insertPos = node.expression.getEnd();
4924    return [{ start: insertPos, end: insertPos, replacementText: typeArgsText }];
4925  }
4926
4927  private fixGenericCallNoTypeArgsForArrayType(node: ts.NewExpression, arrayTypeNode: ts.ArrayTypeNode): Autofix[] | undefined {
4928    const elementTypeNode = arrayTypeNode.elementType;
4929    const srcFile = node.getSourceFile();
4930    const typeArgsText = `<${this.printer.printNode(ts.EmitHint.Unspecified, elementTypeNode, srcFile)}>`;
4931    const insertPos = node.expression.getEnd();
4932    return [{ start: insertPos, end: insertPos, replacementText: typeArgsText }];
4933  }
4934
4935  private fixGenericCallNoTypeArgsForUnionType(node: ts.NewExpression, unionType: ts.UnionTypeNode): Autofix[] | undefined {
4936    const matchingTypes = unionType.types.filter((type) => {
4937      return ts.isTypeReferenceNode(type) && type.typeName.getText() === node.expression.getText();
4938    }) as ts.TypeReferenceNode[];
4939
4940    if (matchingTypes.length === 1) {
4941      const matchingType = matchingTypes[0];
4942      if (matchingType.typeArguments) {
4943        const srcFile = node.getSourceFile();
4944        const typeArgsText = `<${matchingType.typeArguments.
4945          map((arg) => {
4946            return this.printer.printNode(ts.EmitHint.Unspecified, arg, srcFile);
4947          }).
4948          join(', ')}>`;
4949
4950        const insertPos = node.expression.getEnd();
4951        return [{ start: insertPos, end: insertPos, replacementText: typeArgsText }];
4952      }
4953    }
4954    return undefined;
4955  }
4956
4957  private generateGenericTypeArgumentsAutofix(
4958    node: ts.NewExpression,
4959    typeArgs: ts.TypeReferenceNode[]
4960  ): Autofix[] | undefined {
4961    const srcFile = node.getSourceFile();
4962    const identifier = node.expression;
4963    const args = node.arguments;
4964    const hasValidArgs = typeArgs.some((arg) => {
4965      return arg?.typeName && ts.isIdentifier(arg.typeName);
4966    });
4967    if (!hasValidArgs) {
4968      return undefined;
4969    }
4970    const hasAnyType = typeArgs.some((arg) => {
4971      return ts.isIdentifier(arg?.typeName) && arg.typeName.text === 'any';
4972    });
4973    if (hasAnyType) {
4974      return undefined;
4975    }
4976    const newExpression = ts.factory.createNewExpression(identifier, typeArgs, args);
4977    const text = this.printer.printNode(ts.EmitHint.Unspecified, newExpression, srcFile);
4978    return [{ start: node.getStart(), end: node.getEnd(), replacementText: text }];
4979  }
4980
4981  static getTypeArgumentsFromType(type: ts.Type): ts.Type[] {
4982    const typeReference = type as ts.TypeReference;
4983    if (typeReference.typeArguments) {
4984      return [...typeReference.typeArguments];
4985    }
4986    return [];
4987  }
4988
4989  private getTypeNodeForNewExpression(node: ts.NewExpression): ts.TypeNode | undefined {
4990    if (ts.isVariableDeclaration(node.parent) || ts.isPropertyDeclaration(node.parent)) {
4991      return node.parent.type;
4992    } else if (ts.isBinaryExpression(node.parent)) {
4993      return this.utils.getDeclarationTypeNode(node.parent.left);
4994    } else if (ts.isReturnStatement(node.parent) && ts.isBlock(node.parent.parent)) {
4995      const funcNode = node.parent.parent.parent;
4996      const isFunc = ts.isFunctionDeclaration(funcNode) || ts.isMethodDeclaration(funcNode);
4997      if (!isFunc || !funcNode.type) {
4998        return undefined;
4999      }
5000
5001      const isAsync = TsUtils.hasModifier(funcNode.modifiers, ts.SyntaxKind.AsyncKeyword);
5002      if (isAsync) {
5003        if (ts.isTypeReferenceNode(funcNode.type) && funcNode.type.typeName.getText() === 'Promise') {
5004          return funcNode.type?.typeArguments?.[0];
5005        }
5006      }
5007      return funcNode.type;
5008    }
5009    return undefined;
5010  }
5011
5012  private createJSInvokeCallExpression(
5013    ident: ts.Expression,
5014    method: string,
5015    args: ts.Expression[] | undefined
5016  ): ts.CallExpression | undefined {
5017    if (ts.isNewExpression(ident)) {
5018      const instantiatedClass = this.createJSInvokeCallExpression(
5019        ident.expression,
5020        INSTANTIATE,
5021        this.createArgs(ident.arguments)
5022      );
5023      if (!instantiatedClass) {
5024        return undefined;
5025      }
5026      return this.createJSInvokeCallExpression(instantiatedClass, method, args);
5027    }
5028    return ts.factory.createCallExpression(
5029      ts.factory.createPropertyAccessExpression(ident, ts.factory.createIdentifier(method)),
5030      undefined,
5031      args
5032    );
5033  }
5034
5035  fixAwaitJsCallExpression(ident: ts.Identifier, args: ts.NodeArray<ts.Expression> | undefined): Autofix[] | undefined {
5036    const newArgs = this.createArgs(args);
5037
5038    const newCallExpr = this.createJSInvokeCallExpression(ident, INVOKE, newArgs);
5039    if (!newCallExpr) {
5040      return undefined;
5041    }
5042
5043    const replacedNode = ts.factory.createCallExpression(
5044      ts.factory.createPropertyAccessExpression(newCallExpr, ts.factory.createIdentifier(TO_PROMISE)),
5045      undefined,
5046      undefined
5047    );
5048
5049    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, replacedNode, ident.getSourceFile());
5050    return [{ start: ident.parent.getStart(), end: ident.parent.getEnd(), replacementText }];
5051  }
5052
5053  fixAwaitJsMethodCallExpression(
5054    ident: ts.Identifier,
5055    args: ts.NodeArray<ts.Expression> | undefined
5056  ): Autofix[] | undefined {
5057    const propertyAccessExpr = ident.parent as ts.PropertyAccessExpression;
5058    const accessedProperty = propertyAccessExpr.expression;
5059    const newArgs = this.createArgs(args);
5060
5061    const newCallExpr = this.createJSInvokeCallExpression(accessedProperty, INVOKE_METHOD, [
5062      ts.factory.createStringLiteral(ident.text),
5063      ...newArgs || []
5064    ]);
5065
5066    if (!newCallExpr) {
5067      return undefined;
5068    }
5069
5070    const replacedNode = ts.factory.createCallExpression(
5071      ts.factory.createPropertyAccessExpression(newCallExpr, ts.factory.createIdentifier(TO_PROMISE)),
5072      undefined,
5073      undefined
5074    );
5075
5076    const replacementText = this.printer.printNode(ts.EmitHint.Unspecified, replacedNode, ident.getSourceFile());
5077    return [{ start: propertyAccessExpr.parent.getStart(), end: propertyAccessExpr.parent.getEnd(), replacementText }];
5078  }
5079
5080  fixAwaitJsPromise(ident: ts.Identifier): Autofix[] {
5081    void this;
5082    const replacementText = `${ident.text}.toPromise()`;
5083    return [{ start: ident.getStart(), end: ident.getEnd(), replacementText }];
5084  }
5085
5086  fixMissingAttribute(node: ts.PropertyAccessExpression): Autofix[] {
5087    const exprName = node.expression.getText();
5088    const propertyAccessExpr = ts.factory.createPropertyAccessExpression(
5089      ts.factory.createIdentifier(exprName),
5090      ts.factory.createIdentifier(METHOD_KEYS)
5091    );
5092    const replacement = this.printer.printNode(ts.EmitHint.Unspecified, propertyAccessExpr, node.getSourceFile());
5093    return [{ start: node.getStart(), end: node.getEnd(), replacementText: replacement }];
5094  }
5095
5096  fixBuilderDecorators(decorator: ts.Decorator): Autofix[] | undefined {
5097    const newDecorator = ts.factory.createDecorator(ts.factory.createIdentifier('Builder'));
5098    const text = this.printer.printNode(ts.EmitHint.Unspecified, newDecorator, decorator.getSourceFile());
5099    return [{ start: decorator.getStart(), end: decorator.getEnd(), replacementText: text }];
5100  }
5101
5102  fixCustomLayout(node: ts.StructDeclaration): Autofix[] {
5103    const startPos = Autofixer.getStartPositionWithoutDecorators(node);
5104    const decorator = ts.factory.createDecorator(ts.factory.createIdentifier(CustomDecoratorName.CustomLayout));
5105
5106    const text = this.getNewLine() + this.printer.printNode(ts.EmitHint.Unspecified, decorator, node.getSourceFile());
5107    return [{ start: startPos, end: startPos, replacementText: text }];
5108  }
5109
5110  private static getStartPositionWithoutDecorators(node: ts.StructDeclaration): number {
5111    const decorators = ts.getDecorators(node);
5112    if (!decorators || decorators.length === 0) {
5113      return node.getStart();
5114    }
5115
5116    return decorators[decorators.length - 1].getEnd();
5117  }
5118
5119  fixNumericLiteralIntToNumber(node: ts.NumericLiteral): Autofix[] | undefined {
5120    void this;
5121    let replacementText = node.getText();
5122    const parent = node.parent;
5123
5124    if (ts.isPrefixUnaryExpression(parent) && parent.operator === ts.SyntaxKind.MinusToken) {
5125      replacementText = `-${replacementText}.0`;
5126      return [
5127        {
5128          start: parent.getStart(),
5129          end: node.getEnd(),
5130          replacementText
5131        }
5132      ];
5133    }
5134
5135    return [
5136      {
5137        start: node.getStart(),
5138        end: node.getEnd(),
5139        replacementText: `${replacementText}.0`
5140      }
5141    ];
5142  }
5143
5144  fixPropDecorator(node: ts.Decorator, decoratorName: string): Autofix[] {
5145    const newDecorator = ts.factory.createDecorator(
5146      ts.factory.createIdentifier(decoratorName + NEW_PROP_DECORATOR_SUFFIX)
5147    );
5148
5149    const text = this.printer.printNode(ts.EmitHint.Unspecified, newDecorator, node.getSourceFile());
5150    return [{ start: node.getStart(), end: node.getEnd(), replacementText: text }];
5151  }
5152}
5153