• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023-2024 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 { FaultID } from '../Problems';
19import { scopeContainsThis } from '../utils/functions/ContainsThis';
20import { forEachNodeInSubtree } from '../utils/functions/ForEachNodeInSubtree';
21import { NameGenerator } from '../utils/functions/NameGenerator';
22import { isAssignmentOperator } from '../utils/functions/isAssignmentOperator';
23import { SymbolCache } from './SymbolCache';
24
25const GENERATED_OBJECT_LITERAL_INTERFACE_NAME = 'GeneratedObjectLiteralInterface_';
26const GENERATED_OBJECT_LITERAL_INTERFACE_TRESHOLD = 1000;
27
28const GENERATED_TYPE_LITERAL_INTERFACE_NAME = 'GeneratedTypeLiteralInterface_';
29const GENERATED_TYPE_LITERAL_INTERFACE_TRESHOLD = 1000;
30
31const GENERATED_DESTRUCT_OBJECT_NAME = 'GeneratedDestructObj_';
32const GENERATED_DESTRUCT_OBJECT_TRESHOLD = 1000;
33
34const GENERATED_DESTRUCT_ARRAY_NAME = 'GeneratedDestructArray_';
35const GENERATED_DESTRUCT_ARRAY_TRESHOLD = 1000;
36
37export interface Autofix {
38  replacementText: string;
39  start: number;
40  end: number;
41}
42
43export class Autofixer {
44  constructor(
45    private readonly typeChecker: ts.TypeChecker,
46    private readonly utils: TsUtils,
47    readonly sourceFile: ts.SourceFile,
48    readonly cancellationToken?: ts.CancellationToken
49  ) {
50    this.symbolCache = new SymbolCache(this.typeChecker, this.utils, sourceFile, cancellationToken);
51  }
52
53  /**
54   * Generates the text representation for destructuring elements in an object.
55   * @param variableDeclarationMap - Map of property names to variable names.
56   * @param newObjectName - Name of the new object to destructure.
57   * @param declarationFlags - Flags for the variable declaration.
58   * @param printer - TypeScript printer instance.
59   * @param sourceFile - Source file from which the nodes are taken.
60   * @returns The generated destructuring text.
61   */
62  private static genDestructElementTextForObjDecls(
63    variableDeclarationMap: Map<string, string>,
64    newObjectName: string,
65    declarationFlags: ts.NodeFlags,
66    printer: ts.Printer,
67    sourceFile: ts.SourceFile
68  ): string {
69    let destructElementText: string = '';
70
71    variableDeclarationMap.forEach((propertyName, variableName) => {
72      // Create a property access expression for the new object
73      const propertyAccessExpr = ts.factory.createPropertyAccessExpression(
74        ts.factory.createIdentifier(newObjectName),
75        ts.factory.createIdentifier(variableName)
76      );
77
78      // Create a variable declaration with the property access expression
79      const variableDecl = ts.factory.createVariableDeclaration(
80        ts.factory.createIdentifier(propertyName),
81        undefined,
82        undefined,
83        propertyAccessExpr
84      );
85
86      // Create a variable statement for the variable declaration
87      const variableStatement = ts.factory.createVariableStatement(
88        undefined,
89        ts.factory.createVariableDeclarationList([variableDecl], declarationFlags)
90      );
91
92      // Print the variable statement to text and append it
93      const text = printer.printNode(ts.EmitHint.Unspecified, variableStatement, sourceFile);
94      destructElementText += text;
95    });
96
97    return destructElementText;
98  }
99
100  /**
101   * Creates autofix suggestions for destructuring assignment.
102   * @param variableDeclaration - The variable or parameter declaration to fix.
103   * @param newObjectName - Name of the new object to use for destructuring.
104   * @param destructElementText - Generated text for destructuring elements.
105   * @returns Array of autofix suggestions or undefined.
106   */
107  private static genAutofixForObjDecls(
108    variableDeclaration: ts.VariableDeclaration | ts.ParameterDeclaration,
109    newObjectName: string | undefined,
110    destructElementText: string,
111    isIdentifier: boolean
112  ): Autofix[] | undefined {
113    let variableNameReplaceText: Autofix;
114    let destructElementReplaceText: Autofix;
115    let autofix: Autofix[] | undefined = [];
116
117    // Check if the initializer is a identifier
118    if (isIdentifier) {
119      destructElementReplaceText = {
120        replacementText: destructElementText,
121        start: variableDeclaration.parent.getStart(),
122        end: variableDeclaration.parent.parent.getEnd()
123      };
124      autofix = [destructElementReplaceText];
125    } else {
126      // Create autofix suggestions for both variable name and destructuring
127      variableNameReplaceText = {
128        replacementText: newObjectName as string,
129        start: variableDeclaration.name.getStart(),
130        end: variableDeclaration.name.getEnd()
131      };
132      destructElementReplaceText = {
133        replacementText: destructElementText,
134        start: variableDeclaration.parent.parent.getEnd(),
135        end: variableDeclaration.parent.parent.getEnd()
136      };
137      autofix = [variableNameReplaceText, destructElementReplaceText];
138    }
139
140    return autofix;
141  }
142
143  /**
144   * Checks if the given variable declaration passes boundary checks for object declarations.
145   *
146   * @param faultId - The fault ID indicating the type of check to perform (e.g., destructuring parameter).
147   * @param variableDeclaration - The variable or parameter declaration to check for boundary conditions.
148   * @returns A boolean indicating if the declaration passes the boundary checks.
149   */
150  private static passBoundaryCheckForObjDecls(
151    faultId: number,
152    variableDeclaration: ts.VariableDeclaration | ts.ParameterDeclaration
153  ): boolean {
154    // Check if the fault ID is for a destructuring parameter or if the declaration has a spread operator
155    if (
156      faultId === FaultID.DestructuringParameter ||
157      TsUtils.destructuringDeclarationHasSpreadOperator(variableDeclaration.name as ts.BindingPattern) ||
158      TsUtils.destructuringDeclarationHasDefaultValue(variableDeclaration.name as ts.BindingPattern)
159    ) {
160      return false;
161    }
162
163    // If the initializer is an object literal expression, check its properties
164    if (ts.isObjectLiteralExpression(variableDeclaration.initializer as ts.Node)) {
165      const len = (variableDeclaration.initializer as ts.ObjectLiteralExpression).properties.length;
166      if (len === 0) {
167        // Return false if there are no properties
168        return false;
169      }
170    }
171
172    // Return true if all checks are passed
173    return true;
174  }
175
176  /**
177   ** Fixes object binding pattern declarations by generating appropriate autofix suggestions.
178   * @param variableDeclaration - The variable or parameter declaration to fix.
179   * @param faultId - The fault ID indicating the type of check to perform.
180   * @returns Array of autofix suggestions or undefined.
181   */
182  fixObjectBindingPatternDeclarations(
183    variableDeclaration: ts.VariableDeclaration | ts.ParameterDeclaration,
184    faultId: number
185  ): Autofix[] | undefined {
186    if (!Autofixer.passBoundaryCheckForObjDecls(faultId, variableDeclaration)) {
187      return undefined;
188    }
189    // Map to hold variable names and their corresponding property names
190    const variableDeclarationMap: Map<string, string> = new Map();
191    // If the declaration is an object binding pattern, extract names
192    if (ts.isObjectBindingPattern(variableDeclaration.name)) {
193      variableDeclaration.name.elements.forEach((element) => {
194        if (!element.propertyName) {
195          variableDeclarationMap.set(element.name.getText(), element.name.getText());
196        } else {
197          variableDeclarationMap.set((element.propertyName as ts.Identifier).text, element.name.getText());
198        }
199      });
200    }
201    const sourceFile = variableDeclaration.getSourceFile();
202    let newObjectName: string | undefined;
203    const isIdentifier = ts.isIdentifier(variableDeclaration.initializer as ts.Node);
204    // If it is identifer, use its text as the new object name; otherwise, generate a unique name
205    if (isIdentifier) {
206      newObjectName = variableDeclaration.initializer?.getText();
207    } else {
208      newObjectName = TsUtils.generateUniqueName(this.destructObjNameGenerator, sourceFile);
209    }
210    if (!newObjectName) {
211      return undefined;
212    }
213    const declarationFlags = ts.getCombinedNodeFlags(variableDeclaration);
214    const destructElementText = Autofixer.genDestructElementTextForObjDecls(
215      variableDeclarationMap,
216      newObjectName,
217      declarationFlags,
218      this.printer,
219      sourceFile
220    );
221
222    // Generate and return autofix suggestions for the object declarations
223    return Autofixer.genAutofixForObjDecls(variableDeclaration, newObjectName, destructElementText, isIdentifier);
224  }
225
226  /**
227   * Generates the text representation for destructuring elements in an array.
228   * @param variableNames - Array of variable names corresponding to array elements.
229   * @param newArrayName - Name of the new array to destructure.
230   * @param declarationFlags - Flags for the variable declaration.
231   * @param printer - TypeScript printer instance.
232   * @param sourceFile - Source file from which the nodes are taken.
233   * @returns The generated destructuring text.
234   */
235  private static genDestructElementTextForArrayDecls(
236    variableNames: string[],
237    newArrayName: string,
238    declarationFlags: ts.NodeFlags,
239    printer: ts.Printer,
240    sourceFile: ts.SourceFile
241  ): string {
242    let destructElementText: string = '';
243    const length = variableNames.length;
244
245    // Iterate over the array of variable names
246    for (let i = 0; i < length; i++) {
247      const variableName = variableNames[i];
248
249      // Create an element access expression for the new array
250      const elementAccessExpr = ts.factory.createElementAccessExpression(
251        ts.factory.createIdentifier(newArrayName),
252        ts.factory.createNumericLiteral(i)
253      );
254
255      // Create a variable declaration with the element access expression
256      const variableDecl = ts.factory.createVariableDeclaration(
257        ts.factory.createIdentifier(variableName),
258        undefined,
259        undefined,
260        elementAccessExpr
261      );
262
263      // Create a variable statement for the variable declaration
264      const variableStatement = ts.factory.createVariableStatement(
265        undefined,
266        ts.factory.createVariableDeclarationList([variableDecl], declarationFlags)
267      );
268
269      // Print the variable statement to text and append it
270      const text = printer.printNode(ts.EmitHint.Unspecified, variableStatement, sourceFile);
271      destructElementText += text;
272    }
273
274    return destructElementText;
275  }
276
277  /**
278   * Creates autofix suggestions for array destructuring assignment.
279   * @param variableDeclaration - The variable or parameter declaration to fix.
280   * @param newArrayName - Name of the new array to use for destructuring.
281   * @param destructElementText - Generated text for destructuring elements.
282   * @returns Array of autofix suggestions.
283   */
284  private static genAutofixForArrayDecls(
285    variableDeclaration: ts.VariableDeclaration | ts.ParameterDeclaration,
286    newArrayName: string | undefined,
287    destructElementText: string,
288    isIdentifierOrElementAccess: boolean
289  ): Autofix[] {
290    let variableNameReplaceText: Autofix;
291    let destructElementReplaceText: Autofix;
292    let autofix: Autofix[] = [];
293
294    // Check if the initializer is an identifier or element access expression
295    if (isIdentifierOrElementAccess) {
296      destructElementReplaceText = {
297        replacementText: destructElementText,
298        start: variableDeclaration.parent.getStart(),
299        end: variableDeclaration.parent.parent.getEnd()
300      };
301      autofix = [destructElementReplaceText];
302    } else {
303      // Create autofix suggestions for both variable name and destructuring
304      variableNameReplaceText = {
305        replacementText: newArrayName as string,
306        start: variableDeclaration.name.getStart(),
307        end: variableDeclaration.name.getEnd()
308      };
309      destructElementReplaceText = {
310        replacementText: destructElementText,
311        start: variableDeclaration.parent.parent.getEnd(),
312        end: variableDeclaration.parent.parent.getEnd()
313      };
314      autofix = [variableNameReplaceText, destructElementReplaceText];
315    }
316
317    return autofix;
318  }
319
320  /**
321   * Checks if the given variable declaration passes boundary checks for array or tuple declarations.
322   *
323   * @param variableDeclaration - A variable declaration or parameter declaration to be checked.
324   * @param isArrayOrTuple - A boolean indicating whether the declaration is an array or tuple.
325   * @returns A boolean indicating if the declaration passes the boundary checks.
326   */
327  private static passBoundaryCheckForArrayDecls(
328    variableDeclaration: ts.VariableDeclaration | ts.ParameterDeclaration,
329    isArrayOrTuple: boolean
330  ): boolean {
331    // If it's not an array/tuple or the declaration has a spread operator in destructuring
332    if (
333      !isArrayOrTuple ||
334      TsUtils.destructuringDeclarationHasSpreadOperator(variableDeclaration.name as ts.BindingPattern) ||
335      TsUtils.destructuringDeclarationHasDefaultValue(variableDeclaration.name as ts.BindingPattern)
336    ) {
337      // Return false if it fails the boundary check
338      return false;
339    }
340
341    // Check if the array binding pattern has any empty elements
342    if (TsUtils.checkArrayBindingHasEmptyElement(variableDeclaration.name as ts.ArrayBindingPattern)) {
343      // Return false if it contains empty elements
344      return false;
345    }
346
347    // Check if the initializer has the same dimension as expected
348    if (!TsUtils.isSameDimension(variableDeclaration.initializer as ts.Node)) {
349      return false;
350    }
351
352    // Return true if all checks are passed
353    return true;
354  }
355
356  /**
357   * Fixes array binding pattern declarations by generating appropriate autofix suggestions.
358   *
359   * @param variableDeclaration - The variable or parameter declaration to fix.
360   * @param isArrayOrTuple - Flag indicating if the declaration is for an array or tuple.
361   * @returns Array of autofix suggestions or undefined.
362   */
363  fixArrayBindingPatternDeclarations(
364    variableDeclaration: ts.VariableDeclaration | ts.ParameterDeclaration,
365    isArrayOrTuple: boolean
366  ): Autofix[] | undefined {
367    if (!Autofixer.passBoundaryCheckForArrayDecls(variableDeclaration, isArrayOrTuple)) {
368      return undefined;
369    }
370    const variableNames: string[] = [];
371    // If the declaration is an array binding pattern, extract variable names
372    if (ts.isArrayBindingPattern(variableDeclaration.name)) {
373      variableDeclaration.name.elements.forEach((element) => {
374        variableNames.push(element.getText());
375      });
376    }
377
378    const sourceFile = variableDeclaration.getSourceFile();
379    let newArrayName: string | undefined = '';
380    // Check if the initializer is either an identifier or an element access expression
381    const isIdentifierOrElementAccess =
382      ts.isIdentifier(variableDeclaration.initializer as ts.Node) ||
383      ts.isElementAccessExpression(variableDeclaration.initializer as ts.Node);
384    // If it is, use its text as the new array name; otherwise, generate a unique name
385    if (isIdentifierOrElementAccess) {
386      newArrayName = variableDeclaration.initializer?.getText();
387    } else {
388      newArrayName = TsUtils.generateUniqueName(this.destructArrayNameGenerator, sourceFile);
389    }
390    if (!newArrayName) {
391      return undefined;
392    }
393
394    // Get the combined node flags for the variable declaration
395    const declarationFlags = ts.getCombinedNodeFlags(variableDeclaration);
396    // Generate the destructuring element text for the array declaration
397    const destructElementText = Autofixer.genDestructElementTextForArrayDecls(
398      variableNames,
399      newArrayName,
400      declarationFlags,
401      this.printer,
402      sourceFile
403    );
404
405    // Generate and return autofix suggestions for the array declarations
406    return Autofixer.genAutofixForArrayDecls(
407      variableDeclaration,
408      newArrayName,
409      destructElementText,
410      isIdentifierOrElementAccess
411    );
412  }
413
414  /**
415   * Generates the text representation for destructuring assignments in an array.
416   * @param variableNames - Array of variable names corresponding to array elements.
417   * @param newArrayName - Name of the new array to use for destructuring.
418   * @param printer - TypeScript printer instance.
419   * @param sourceFile - Source file from which the nodes are taken.
420   * @returns The generated destructuring assignment text.
421   */
422  private static genDestructElementTextForArrayAssignment(
423    variableNames: string[],
424    newArrayName: string | undefined,
425    printer: ts.Printer,
426    sourceFile: ts.SourceFile
427  ): string {
428    let destructElementText: string = '';
429    const length = variableNames.length;
430
431    // Iterate over the array of variable names
432    for (let i = 0; i < length; i++) {
433      const variableName = variableNames[i];
434
435      // Create an element access expression for the new array
436      const elementAccessExpr = ts.factory.createElementAccessExpression(
437        ts.factory.createIdentifier(newArrayName as string),
438        ts.factory.createNumericLiteral(i)
439      );
440
441      // Create a binary expression to assign the array element to the variable
442      const assignmentExpr = ts.factory.createBinaryExpression(
443        ts.factory.createIdentifier(variableName),
444        ts.factory.createToken(ts.SyntaxKind.EqualsToken),
445        elementAccessExpr
446      );
447
448      // Create an expression statement for the assignment expression
449      const expressionStatement = ts.factory.createExpressionStatement(assignmentExpr);
450
451      // Print the expression statement to text and append it
452      const text = printer.printNode(ts.EmitHint.Unspecified, expressionStatement, sourceFile);
453      destructElementText += text;
454    }
455
456    return destructElementText;
457  }
458
459  /**
460   * Creates autofix suggestions for array destructuring assignment.
461   * @param assignmentExpr - The binary expression for the assignment.
462   * @param newArrayName - Name of the new array to use for destructuring.
463   * @param destructElementText - Generated text for destructuring assignments.
464   * @returns Array of autofix suggestions.
465   */
466  private static genAutofixForArrayAssignment(
467    assignmentExpr: ts.BinaryExpression,
468    newArrayName: string | undefined,
469    destructElementText: string,
470    isIdentifierOrElementAccess: boolean
471  ): Autofix[] {
472    let arrayNameReplaceText: Autofix;
473    let destructElementReplaceText: Autofix;
474    let autofix: Autofix[] = [];
475
476    // Check if the right side of the assignment is an identifier or element access expression
477    if (isIdentifierOrElementAccess) {
478      destructElementReplaceText = {
479        replacementText: destructElementText,
480        start: assignmentExpr.parent.getStart(),
481        end: assignmentExpr.parent.getEnd()
482      };
483      autofix = [destructElementReplaceText];
484    } else {
485      // Create autofix suggestions for both array name and destructuring assignments
486      const keywordsLet = 'let ';
487      arrayNameReplaceText = {
488        replacementText: keywordsLet + newArrayName,
489        start: assignmentExpr.left.getStart(),
490        end: assignmentExpr.left.getEnd()
491      };
492      destructElementReplaceText = {
493        replacementText: destructElementText,
494        start: assignmentExpr.parent.getEnd(),
495        end: assignmentExpr.parent.getEnd()
496      };
497      autofix = [arrayNameReplaceText, destructElementReplaceText];
498    }
499
500    return autofix;
501  }
502
503  /**
504   * Checks if the given assignment expression passes boundary checks for array assignments.
505   *
506   * @param assignmentExpr - The assignment expression to check (a binary expression).
507   * @param isArrayOrTuple - A boolean indicating if the assignment is for an array or tuple.
508   * @returns A boolean indicating if the assignment passes the boundary checks.
509   */
510  private static passBoundaryCheckForArrayAssignment(
511    assignmentExpr: ts.BinaryExpression,
512    isArrayOrTuple: boolean
513  ): boolean {
514    // Return false if the assignment is not for an array or tuple
515    if (!isArrayOrTuple) {
516      return false;
517    }
518
519    // Check if the left side of the assignment is an array literal expression with a spread operator
520    if (TsUtils.destructuringAssignmentHasSpreadOperator(assignmentExpr.left as ts.ArrayLiteralExpression)) {
521      return false;
522    }
523
524    if (TsUtils.destructuringAssignmentHasDefaultValue(assignmentExpr.left as ts.ArrayLiteralExpression)) {
525      return false;
526    }
527
528    // Check if the left side of the assignment has an empty element
529    if (TsUtils.checkArrayLiteralHasEmptyElement(assignmentExpr.left as ts.ArrayLiteralExpression)) {
530      return false;
531    }
532
533    // Check if the right side of the assignment has the same dimension as the left side
534    if (!TsUtils.isSameDimension(assignmentExpr.right)) {
535      return false;
536    }
537
538    // Return true if all boundary checks are passed
539    return true;
540  }
541
542  /**
543   * Fixes array binding pattern assignments by generating appropriate autofix suggestions.
544   * @param assignmentExpr - The binary expression for the assignment.
545   * @param isArrayOrTuple - Flag indicating if the assignment is for an array or tuple.
546   * @returns Array of autofix suggestions or undefined.
547   */
548  fixArrayBindingPatternAssignment(
549    assignmentExpr: ts.BinaryExpression,
550    isArrayOrTuple: boolean
551  ): Autofix[] | undefined {
552    if (!Autofixer.passBoundaryCheckForArrayAssignment(assignmentExpr, isArrayOrTuple)) {
553      return undefined;
554    }
555    // Collect variable names from array literal expression
556    const variableNames: string[] = [];
557    if (ts.isArrayLiteralExpression(assignmentExpr.left)) {
558      assignmentExpr.left.elements.forEach((element) => {
559        variableNames.push(element.getText());
560      });
561    }
562
563    const sourceFile = assignmentExpr.getSourceFile();
564    let newArrayName: string | undefined = '';
565    const isIdentifierOrElementAccess =
566      ts.isIdentifier(assignmentExpr.right) || ts.isElementAccessExpression(assignmentExpr.right as ts.Node);
567    if (isIdentifierOrElementAccess) {
568      newArrayName = assignmentExpr.right.getText();
569    } else {
570      newArrayName = TsUtils.generateUniqueName(this.destructArrayNameGenerator, sourceFile);
571    }
572    if (!newArrayName) {
573      return undefined;
574    }
575
576    // Generate the text for destructuring assignments
577    const destructElementText = Autofixer.genDestructElementTextForArrayAssignment(
578      variableNames,
579      newArrayName,
580      this.printer,
581      sourceFile
582    );
583
584    return Autofixer.genAutofixForArrayAssignment(
585      assignmentExpr,
586      newArrayName,
587      destructElementText,
588      isIdentifierOrElementAccess
589    );
590  }
591
592  /**
593   * Creates a mapping of variable declarations and needParentheses for object properties based on the provided binary expression.
594   * @param binaryExpr - The binary expression containing the object literal.
595   * @returns An object containing the variable declaration map and needParentheses indicating if property initializers are object literals.
596   */
597  private static genTsVarDeclMapAndFlags(binaryExpr: ts.BinaryExpression): {
598    tsVarDeclMap: Map<string, string>;
599    needParentheses: boolean[];
600  } {
601    const tsVarDeclMap: Map<string, string> = new Map();
602    const needParentheses: boolean[] = [];
603
604    // Check if the left side of the binary expression is an object literal
605    if (ts.isObjectLiteralExpression(binaryExpr.left)) {
606      binaryExpr.left.properties.forEach((property) => {
607        // Handle property assignments with initializer
608        if (ts.isPropertyAssignment(property)) {
609          tsVarDeclMap.set(property.name?.getText(), property.initializer.getText());
610          needParentheses.push(ts.isObjectLiteralExpression(property.initializer));
611        } else if (ts.isShorthandPropertyAssignment(property)) {
612          tsVarDeclMap.set(property.name?.getText(), property.name.getText());
613          needParentheses.push(false);
614        }
615      });
616    }
617
618    return { tsVarDeclMap, needParentheses };
619  }
620
621  /**
622   * Generates the text for destructuring assignments based on the variable declaration map and needParentheses.
623   * @param tsVarDeclMap - Map of variable names to property names.
624   * @param needParentheses - Array of needParentheses indicating if property initializers are object literals.
625   * @param newObjName - The name of the new object to use for destructuring.
626   * @param binaryExpr - The binary expression representing the destructuring.
627   * @param printer - TypeScript printer instance for printing nodes.
628   * @returns The generated text for destructuring assignments.
629   */
630  private static genDestructElementTextForObjAssignment(
631    tsVarDeclMap: Map<string, string>,
632    needParentheses: boolean[],
633    newObjName: string,
634    binaryExpr: ts.BinaryExpression,
635    printer: ts.Printer
636  ): string {
637    let destructElementText: string = '';
638    let index: number = 0;
639
640    // Iterate through the variable declaration map to generate destructuring assignments
641    tsVarDeclMap.forEach((propertyName, variableName) => {
642      // Create property access expression for the new object
643      const propAccessExpr = ts.factory.createPropertyAccessExpression(
644        ts.factory.createIdentifier(newObjName),
645        ts.factory.createIdentifier(variableName)
646      );
647      // Create binary expression for assignment
648      const assignmentExpr = ts.factory.createBinaryExpression(
649        ts.factory.createIdentifier(propertyName),
650        ts.factory.createToken(ts.SyntaxKind.EqualsToken),
651        propAccessExpr
652      );
653      // Create statement for the assignment expression, with or without parentheses based on the flag
654      const statement = needParentheses[index] ?
655        ts.factory.createExpressionStatement(ts.factory.createParenthesizedExpression(assignmentExpr)) :
656        ts.factory.createExpressionStatement(assignmentExpr);
657
658      // Append the generated text for the destructuring assignment
659      destructElementText += printer.printNode(ts.EmitHint.Unspecified, statement, binaryExpr.getSourceFile());
660
661      index++;
662    });
663
664    return destructElementText;
665  }
666
667  /**
668   * Creates the replacement text for the variable declaration name.
669   * @param binaryExpr - The binary expression containing the object literal or call expression.
670   * @param newObjName - The new object name to be used in the replacement.
671   * @param printer - TypeScript printer instance for printing nodes.
672   * @returns The replacement text for the variable declaration name.
673   */
674  private static genDeclNameReplaceTextForObjAssignment(
675    binaryExpr: ts.BinaryExpression,
676    newObjName: string,
677    printer: ts.Printer
678  ): string {
679    let declNameReplaceText = '';
680
681    // create variableDeclList and get declNameReplaceText text
682    const variableDecl = ts.factory.createVariableDeclaration(
683      ts.factory.createIdentifier(newObjName),
684      undefined,
685      undefined,
686      binaryExpr.right
687    );
688    const variableDeclList = ts.factory.createVariableDeclarationList([variableDecl], ts.NodeFlags.Let);
689    declNameReplaceText = printer.printNode(ts.EmitHint.Unspecified, variableDeclList, binaryExpr.getSourceFile());
690
691    return declNameReplaceText;
692  }
693
694  /**
695   * Creates autofix suggestions for object literal destructuring assignments.
696   * @param binaryExpr - The binary expression containing the destructuring assignment.
697   * @param declNameReplaceText - Replacement text for the variable declaration name.
698   * @param destructElementText - Generated text for destructuring assignments.
699   * @returns Array of autofix suggestions or undefined if no fixes are needed.
700   */
701  private static createAutofixForObjAssignment(
702    binaryExpr: ts.BinaryExpression,
703    declNameReplaceText: string,
704    destructElementText: string,
705    isIdentifier: boolean
706  ): Autofix[] | undefined {
707    let declNameReplaceTextAutofix: Autofix;
708    let destructElementReplaceTextAutofix: Autofix;
709    let autofix: Autofix[] | undefined;
710
711    // Check if the right side of the binary expression is identifer
712    if (isIdentifier) {
713      destructElementReplaceTextAutofix = {
714        replacementText: destructElementText,
715        start: binaryExpr.parent.parent.getStart(),
716        end: binaryExpr.parent.parent.getEnd()
717      };
718      autofix = [destructElementReplaceTextAutofix];
719    } else {
720      declNameReplaceTextAutofix = {
721        replacementText: declNameReplaceText,
722        start: binaryExpr.parent.getStart(),
723        end: binaryExpr.parent.getEnd()
724      };
725      destructElementReplaceTextAutofix = {
726        replacementText: destructElementText,
727        start: binaryExpr.parent.parent.getEnd(),
728        end: binaryExpr.parent.parent.getEnd()
729      };
730      autofix = [declNameReplaceTextAutofix, destructElementReplaceTextAutofix];
731    }
732
733    return autofix;
734  }
735
736  /**
737   * Checks if the given assignment expression passes boundary checks for object assignments.
738   *
739   * @param binaryExpr - The binary expression representing the assignment to check.
740   * @returns A boolean indicating if the assignment passes the boundary checks.
741   */
742  private static passBoundaryCheckForObjAssignment(binaryExpr: ts.BinaryExpression): boolean {
743    // Check for spread operator in destructuring assignment on the left side
744    if (TsUtils.destructuringAssignmentHasSpreadOperator(binaryExpr.left as ts.ObjectLiteralExpression)) {
745      return false;
746    }
747
748    if (TsUtils.destructuringAssignmentHasDefaultValue(binaryExpr.left as ts.ObjectLiteralExpression)) {
749      return false;
750    }
751
752    // Check if the right side is an object literal expression with no properties
753    if (ts.isObjectLiteralExpression(binaryExpr.right) && binaryExpr.right.properties.length === 0) {
754      return false;
755    }
756
757    // Return true if all boundary checks are passed
758    return true;
759  }
760
761  /**
762   * Fixes object literal expression destructuring assignments by generating autofix suggestions.
763   * @param binaryExpr - The binary expression representing the destructuring assignment.
764   * @returns Array of autofix suggestions or undefined if no fixes are needed.
765   */
766  fixObjectLiteralExpressionDestructAssignment(binaryExpr: ts.BinaryExpression): Autofix[] | undefined {
767    if (!Autofixer.passBoundaryCheckForObjAssignment(binaryExpr)) {
768      return undefined;
769    }
770    // Create a mapping of variable declarations and needParentheses
771    const { tsVarDeclMap, needParentheses } = Autofixer.genTsVarDeclMapAndFlags(binaryExpr);
772
773    const sourceFile = binaryExpr.getSourceFile();
774    let newObjName: string | undefined = '';
775    // Generate a unique name if right expression is not identifer
776    const isIdentifier = ts.isIdentifier(binaryExpr.right);
777    if (isIdentifier) {
778      newObjName = binaryExpr.right?.getText();
779    } else {
780      newObjName = TsUtils.generateUniqueName(this.destructObjNameGenerator, sourceFile);
781    }
782    if (!newObjName) {
783      return undefined;
784    }
785    // Create the text for destructuring elements
786    const destructElementText = Autofixer.genDestructElementTextForObjAssignment(
787      tsVarDeclMap,
788      needParentheses,
789      newObjName,
790      binaryExpr,
791      this.printer
792    );
793
794    // Create the replacement text for the variable declaration name
795    const declNameReplaceText = Autofixer.genDeclNameReplaceTextForObjAssignment(binaryExpr, newObjName, this.printer);
796
797    // Generate autofix suggestions
798    return Autofixer.createAutofixForObjAssignment(binaryExpr, declNameReplaceText, destructElementText, isIdentifier);
799  }
800
801  fixLiteralAsPropertyNamePropertyAssignment(node: ts.PropertyAssignment): Autofix[] | undefined {
802    const contextualType = this.typeChecker.getContextualType(node.parent);
803    if (contextualType === undefined) {
804      return undefined;
805    }
806
807    const symbol = this.utils.getPropertySymbol(contextualType, node);
808    if (symbol === undefined) {
809      return undefined;
810    }
811
812    return this.renameSymbolAsIdentifier(symbol);
813  }
814
815  fixLiteralAsPropertyNamePropertyName(node: ts.PropertyName): Autofix[] | undefined {
816    const symbol = this.typeChecker.getSymbolAtLocation(node);
817    if (symbol === undefined) {
818      return undefined;
819    }
820
821    return this.renameSymbolAsIdentifier(symbol);
822  }
823
824  fixPropertyAccessByIndex(node: ts.ElementAccessExpression): Autofix[] | undefined {
825    const symbol = this.typeChecker.getSymbolAtLocation(node.argumentExpression);
826    if (symbol === undefined) {
827      return undefined;
828    }
829
830    return this.renameSymbolAsIdentifier(symbol);
831  }
832
833  private renameSymbolAsIdentifier(symbol: ts.Symbol): Autofix[] | undefined {
834    if (this.renameSymbolAsIdentifierCache.has(symbol)) {
835      return this.renameSymbolAsIdentifierCache.get(symbol);
836    }
837
838    if (!TsUtils.isPropertyOfInternalClassOrInterface(symbol)) {
839      this.renameSymbolAsIdentifierCache.set(symbol, undefined);
840      return undefined;
841    }
842
843    const newName = this.utils.findIdentifierNameForSymbol(symbol);
844    if (newName === undefined) {
845      this.renameSymbolAsIdentifierCache.set(symbol, undefined);
846      return undefined;
847    }
848
849    let result: Autofix[] | undefined = [];
850    this.symbolCache.getReferences(symbol).forEach((node) => {
851      if (result === undefined) {
852        return;
853      }
854
855      let autofix: Autofix[] | undefined;
856      if (ts.isPropertyDeclaration(node) || ts.isPropertyAssignment(node) || ts.isPropertySignature(node)) {
857        autofix = Autofixer.renamePropertyName(node.name, newName);
858      } else if (ts.isElementAccessExpression(node)) {
859        autofix = Autofixer.renameElementAccessExpression(node, newName);
860      }
861
862      if (autofix === undefined) {
863        result = undefined;
864        return;
865      }
866
867      result.push(...autofix);
868    });
869    if (!result?.length) {
870      result = undefined;
871    }
872
873    this.renameSymbolAsIdentifierCache.set(symbol, result);
874    return result;
875  }
876
877  private readonly renameSymbolAsIdentifierCache = new Map<ts.Symbol, Autofix[] | undefined>();
878
879  private static renamePropertyName(node: ts.PropertyName, newName: string): Autofix[] | undefined {
880    if (ts.isComputedPropertyName(node)) {
881      return undefined;
882    }
883
884    if (ts.isMemberName(node)) {
885      if (ts.idText(node) !== newName) {
886        return undefined;
887      }
888
889      return [];
890    }
891
892    return [{ replacementText: newName, start: node.getStart(), end: node.getEnd() }];
893  }
894
895  private static renameElementAccessExpression(
896    node: ts.ElementAccessExpression,
897    newName: string
898  ): Autofix[] | undefined {
899    const argExprKind = node.argumentExpression.kind;
900    if (argExprKind !== ts.SyntaxKind.NumericLiteral && argExprKind !== ts.SyntaxKind.StringLiteral) {
901      return undefined;
902    }
903
904    return [
905      {
906        replacementText: node.expression.getText() + '.' + newName,
907        start: node.getStart(),
908        end: node.getEnd()
909      }
910    ];
911  }
912
913  fixFunctionExpression(
914    funcExpr: ts.FunctionExpression,
915    // eslint-disable-next-line default-param-last
916    retType: ts.TypeNode | undefined = funcExpr.type,
917    modifiers: readonly ts.Modifier[] | undefined,
918    isGenerator: boolean,
919    hasUnfixableReturnType: boolean
920  ): Autofix[] | undefined {
921    const hasThisKeyword = scopeContainsThis(funcExpr.body);
922    const isCalledRecursively = this.utils.isFunctionCalledRecursively(funcExpr);
923    if (isGenerator || hasThisKeyword || isCalledRecursively || hasUnfixableReturnType) {
924      return undefined;
925    }
926
927    let arrowFunc: ts.Expression = ts.factory.createArrowFunction(
928      modifiers,
929      funcExpr.typeParameters,
930      funcExpr.parameters,
931      retType,
932      ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
933      funcExpr.body
934    );
935    if (Autofixer.needsParentheses(funcExpr)) {
936      arrowFunc = ts.factory.createParenthesizedExpression(arrowFunc);
937    }
938    const text = this.printer.printNode(ts.EmitHint.Unspecified, arrowFunc, funcExpr.getSourceFile());
939    return [{ start: funcExpr.getStart(), end: funcExpr.getEnd(), replacementText: text }];
940  }
941
942  private static isNodeInWhileOrIf(node: ts.Node): boolean {
943    return (
944      node.kind === ts.SyntaxKind.WhileStatement ||
945      node.kind === ts.SyntaxKind.DoStatement ||
946      node.kind === ts.SyntaxKind.IfStatement
947    );
948  }
949
950  private static isNodeInForLoop(node: ts.Node): boolean {
951    return (
952      node.kind === ts.SyntaxKind.ForInStatement ||
953      node.kind === ts.SyntaxKind.ForOfStatement ||
954      node.kind === ts.SyntaxKind.ForStatement
955    );
956  }
957
958  private static parentInFor(node: ts.Node): ts.Node | undefined {
959    let parentNode = node.parent;
960    while (parentNode) {
961      if (Autofixer.isNodeInForLoop(parentNode)) {
962        return parentNode;
963      }
964      parentNode = parentNode.parent;
965    }
966    return undefined;
967  }
968
969  private static parentInCaseOrWhile(varDeclList: ts.VariableDeclarationList): boolean {
970    let parentNode: ts.Node = varDeclList.parent;
971    while (parentNode) {
972      if (parentNode.kind === ts.SyntaxKind.CaseClause || Autofixer.isNodeInWhileOrIf(parentNode)) {
973        return false;
974      }
975      parentNode = parentNode.parent;
976    }
977    return true;
978  }
979
980  private static isFunctionLikeDeclarationKind(node: ts.Node): boolean {
981    switch (node.kind) {
982      case ts.SyntaxKind.FunctionDeclaration:
983      case ts.SyntaxKind.MethodDeclaration:
984      case ts.SyntaxKind.Constructor:
985      case ts.SyntaxKind.GetAccessor:
986      case ts.SyntaxKind.SetAccessor:
987      case ts.SyntaxKind.FunctionExpression:
988      case ts.SyntaxKind.ArrowFunction:
989        return true;
990      default:
991        return false;
992    }
993  }
994
995  private static findVarScope(node: ts.Node): ts.Node {
996    while (node !== undefined) {
997      if (node.kind === ts.SyntaxKind.Block || node.kind === ts.SyntaxKind.SourceFile) {
998        break;
999      }
1000      // eslint-disable-next-line no-param-reassign
1001      node = node.parent;
1002    }
1003    return node;
1004  }
1005
1006  private static varHasScope(node: ts.Node, scope: ts.Node): boolean {
1007    while (node !== undefined) {
1008      if (node === scope) {
1009        return true;
1010      }
1011      // eslint-disable-next-line no-param-reassign
1012      node = node.parent;
1013    }
1014    return false;
1015  }
1016
1017  private static varInFunctionForScope(node: ts.Node, scope: ts.Node): boolean {
1018    while (node !== undefined) {
1019      if (Autofixer.isFunctionLikeDeclarationKind(node)) {
1020        break;
1021      }
1022      // eslint-disable-next-line no-param-reassign
1023      node = node.parent;
1024    }
1025    // node now Function like declaration
1026
1027    // node need to check that function like declaration is in scope
1028    if (Autofixer.varHasScope(node, scope)) {
1029      // var use is in function scope, which is in for scope
1030      return true;
1031    }
1032    return false;
1033  }
1034
1035  private static selfDeclared(decl: ts.Node, ident: ts.Node): boolean {
1036    // Do not check the same node
1037    if (ident === decl) {
1038      return false;
1039    }
1040
1041    while (ident !== undefined) {
1042      if (ident.kind === ts.SyntaxKind.VariableDeclaration) {
1043        const declName = (ident as ts.VariableDeclaration).name;
1044        if (declName === decl) {
1045          return true;
1046        }
1047      }
1048      // eslint-disable-next-line no-param-reassign
1049      ident = ident.parent;
1050    }
1051    return false;
1052  }
1053
1054  private static analizeTDZ(decl: ts.VariableDeclaration, identifiers: ts.Node[]): boolean {
1055    for (const ident of identifiers) {
1056      if (Autofixer.selfDeclared(decl.name, ident)) {
1057        return false;
1058      }
1059      if (ident.pos < decl.pos) {
1060        return false;
1061      }
1062    }
1063    return true;
1064  }
1065
1066  private static analizeScope(decl: ts.VariableDeclaration, identifiers: ts.Node[]): boolean {
1067    const scope = Autofixer.findVarScope(decl);
1068    if (scope === undefined) {
1069      return false;
1070    } else if (scope.kind === ts.SyntaxKind.Block) {
1071      for (const ident of identifiers) {
1072        if (!Autofixer.varHasScope(ident, scope)) {
1073          return false;
1074        }
1075      }
1076    } else if (scope.kind === ts.SyntaxKind.SourceFile) {
1077      // Do nothing
1078    } else {
1079      // Unreachable, but check it
1080      return false;
1081    }
1082    return true;
1083  }
1084
1085  private static analizeFor(decl: ts.VariableDeclaration, identifiers: ts.Node[]): boolean {
1086    const forNode = Autofixer.parentInFor(decl);
1087    if (forNode) {
1088      // analize that var is initialized
1089      if (forNode.kind === ts.SyntaxKind.ForInStatement || forNode.kind === ts.SyntaxKind.ForOfStatement) {
1090        const typedForNode = forNode as ts.ForInOrOfStatement;
1091        const forVarDeclarations = (typedForNode.initializer as ts.VariableDeclarationList).declarations;
1092        if (forVarDeclarations.length !== 1) {
1093          return false;
1094        }
1095        const forVarDecl = forVarDeclarations[0];
1096
1097        // our goal to skip declarations in for of/in initializer
1098        if (forVarDecl !== decl && decl.initializer === undefined) {
1099          return false;
1100        }
1101      } else if (decl.initializer === undefined) {
1102        return false;
1103      }
1104
1105      // analize that var uses are only in function block
1106      for (const ident of identifiers) {
1107        if (ident !== decl && !Autofixer.varHasScope(ident, forNode)) {
1108          return false;
1109        }
1110      }
1111
1112      // analize that var is not in function
1113      for (const ident of identifiers) {
1114        if (ident !== decl && Autofixer.varInFunctionForScope(ident, forNode)) {
1115          return false;
1116        }
1117      }
1118    }
1119    return true;
1120  }
1121
1122  private checkVarDeclarations(varDeclList: ts.VariableDeclarationList): boolean {
1123    for (const decl of varDeclList.declarations) {
1124      const symbol = this.typeChecker.getSymbolAtLocation(decl.name);
1125      if (!symbol) {
1126        return false;
1127      }
1128
1129      const identifiers = this.symbolCache.getReferences(symbol);
1130
1131      const declLength = symbol.declarations?.length;
1132      if (!declLength || declLength >= 2) {
1133        return false;
1134      }
1135
1136      // Check for var use in tdz oe self declaration
1137      if (!Autofixer.analizeTDZ(decl, identifiers)) {
1138        return false;
1139      }
1140
1141      // Has use outside scope of declaration?
1142      if (!Autofixer.analizeScope(decl, identifiers)) {
1143        return false;
1144      }
1145
1146      // For analisys
1147      if (!Autofixer.analizeFor(decl, identifiers)) {
1148        return false;
1149      }
1150
1151      if (symbol.getName() === 'let') {
1152        return false;
1153      }
1154    }
1155    return true;
1156  }
1157
1158  private canAutofixNoVar(varDeclList: ts.VariableDeclarationList): boolean {
1159    if (!Autofixer.parentInCaseOrWhile(varDeclList)) {
1160      return false;
1161    }
1162
1163    if (!this.checkVarDeclarations(varDeclList)) {
1164      return false;
1165    }
1166
1167    return true;
1168  }
1169
1170  fixVarDeclaration(node: ts.VariableDeclarationList): Autofix[] | undefined {
1171    const newNode = ts.factory.createVariableDeclarationList(node.declarations, ts.NodeFlags.Let);
1172    const text = this.printer.printNode(ts.EmitHint.Unspecified, newNode, node.getSourceFile());
1173    return this.canAutofixNoVar(node) ?
1174      [{ start: node.getStart(), end: node.getEnd(), replacementText: text }] :
1175      undefined;
1176  }
1177
1178  private getFixReturnTypeArrowFunction(funcLikeDecl: ts.FunctionLikeDeclaration, typeNode: ts.TypeNode): string {
1179    if (!funcLikeDecl.body) {
1180      return '';
1181    }
1182    const node = ts.factory.createArrowFunction(
1183      undefined,
1184      funcLikeDecl.typeParameters,
1185      funcLikeDecl.parameters,
1186      typeNode,
1187      ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
1188      funcLikeDecl.body
1189    );
1190    return this.printer.printNode(ts.EmitHint.Unspecified, node, funcLikeDecl.getSourceFile());
1191  }
1192
1193  fixMissingReturnType(funcLikeDecl: ts.FunctionLikeDeclaration, typeNode: ts.TypeNode): Autofix[] {
1194    if (ts.isArrowFunction(funcLikeDecl)) {
1195      const text = this.getFixReturnTypeArrowFunction(funcLikeDecl, typeNode);
1196      const startPos = funcLikeDecl.getStart();
1197      const endPos = funcLikeDecl.getEnd();
1198      return [{ start: startPos, end: endPos, replacementText: text }];
1199    }
1200    const text = ': ' + this.printer.printNode(ts.EmitHint.Unspecified, typeNode, funcLikeDecl.getSourceFile());
1201    const pos = Autofixer.getReturnTypePosition(funcLikeDecl);
1202    return [{ start: pos, end: pos, replacementText: text }];
1203  }
1204
1205  dropTypeOnVarDecl(varDecl: ts.VariableDeclaration): Autofix[] {
1206    const newVarDecl = ts.factory.createVariableDeclaration(varDecl.name, undefined, undefined, undefined);
1207    const text = this.printer.printNode(ts.EmitHint.Unspecified, newVarDecl, varDecl.getSourceFile());
1208    return [{ start: varDecl.getStart(), end: varDecl.getEnd(), replacementText: text }];
1209  }
1210
1211  fixTypeAssertion(typeAssertion: ts.TypeAssertion): Autofix[] {
1212    const asExpr = ts.factory.createAsExpression(typeAssertion.expression, typeAssertion.type);
1213    const text = this.nonCommentPrinter.printNode(ts.EmitHint.Unspecified, asExpr, typeAssertion.getSourceFile());
1214    return [{ start: typeAssertion.getStart(), end: typeAssertion.getEnd(), replacementText: text }];
1215  }
1216
1217  fixCommaOperator(tsNode: ts.Node): Autofix[] {
1218    const tsExprNode = tsNode as ts.BinaryExpression;
1219    const text = this.recursiveCommaOperator(tsExprNode);
1220    return [{ start: tsExprNode.parent.getFullStart(), end: tsExprNode.parent.getEnd(), replacementText: text }];
1221  }
1222
1223  private recursiveCommaOperator(tsExprNode: ts.BinaryExpression): string {
1224    let text = '';
1225    if (tsExprNode.operatorToken.kind !== ts.SyntaxKind.CommaToken) {
1226      return tsExprNode.getFullText() + ';';
1227    }
1228
1229    if (tsExprNode.left.kind === ts.SyntaxKind.BinaryExpression) {
1230      text += this.recursiveCommaOperator(tsExprNode.left as ts.BinaryExpression);
1231      text += '\n' + tsExprNode.right.getFullText() + ';';
1232    } else {
1233      const leftText = tsExprNode.left.getFullText();
1234      const rightText = tsExprNode.right.getFullText();
1235      text = leftText + ';\n' + rightText + ';';
1236    }
1237
1238    return text;
1239  }
1240
1241  private getEnumMembers(node: ts.Node, enumDeclsInFile: ts.Declaration[], result: Autofix[] | undefined): void {
1242    if (result === undefined || !ts.isEnumDeclaration(node)) {
1243      return;
1244    }
1245
1246    if (result.length) {
1247      result.push({ start: node.getStart(), end: node.getEnd(), replacementText: '' });
1248      return;
1249    }
1250
1251    const members: ts.EnumMember[] = [];
1252    for (const decl of enumDeclsInFile) {
1253      for (const member of (decl as ts.EnumDeclaration).members) {
1254        if (
1255          member.initializer &&
1256          member.initializer.kind !== ts.SyntaxKind.NumericLiteral &&
1257          member.initializer.kind !== ts.SyntaxKind.StringLiteral
1258        ) {
1259          result = undefined;
1260          return;
1261        }
1262      }
1263      members.push(...(decl as ts.EnumDeclaration).members);
1264    }
1265
1266    const fullEnum = ts.factory.createEnumDeclaration(node.modifiers, node.name, members);
1267    const fullText = this.printer.printNode(ts.EmitHint.Unspecified, fullEnum, node.getSourceFile());
1268    result.push({ start: node.getStart(), end: node.getEnd(), replacementText: fullText });
1269  }
1270
1271  fixEnumMerging(enumSymbol: ts.Symbol, enumDeclsInFile: ts.Declaration[]): Autofix[] | undefined {
1272    if (this.enumMergingCache.has(enumSymbol)) {
1273      return this.enumMergingCache.get(enumSymbol);
1274    }
1275
1276    if (enumDeclsInFile.length <= 1) {
1277      this.enumMergingCache.set(enumSymbol, undefined);
1278      return undefined;
1279    }
1280
1281    let result: Autofix[] | undefined = [];
1282    this.symbolCache.getReferences(enumSymbol).forEach((node) => {
1283      this.getEnumMembers(node, enumDeclsInFile, result);
1284    });
1285    if (!result?.length) {
1286      result = undefined;
1287    }
1288
1289    this.enumMergingCache.set(enumSymbol, result);
1290    return result;
1291  }
1292
1293  private readonly enumMergingCache = new Map<ts.Symbol, Autofix[] | undefined>();
1294
1295  private readonly printer: ts.Printer = ts.createPrinter({
1296    omitTrailingSemicolon: false,
1297    removeComments: false,
1298    newLine: ts.NewLineKind.LineFeed
1299  });
1300
1301  private readonly nonCommentPrinter: ts.Printer = ts.createPrinter({
1302    omitTrailingSemicolon: false,
1303    removeComments: true,
1304    newLine: ts.NewLineKind.LineFeed
1305  });
1306
1307  private static getReturnTypePosition(funcLikeDecl: ts.FunctionLikeDeclaration): number {
1308    if (funcLikeDecl.body) {
1309
1310      /*
1311       * Find position of the first node or token that follows parameters.
1312       * After that, iterate over child nodes in reverse order, until found
1313       * first closing parenthesis.
1314       */
1315      const postParametersPosition = ts.isArrowFunction(funcLikeDecl) ?
1316        funcLikeDecl.equalsGreaterThanToken.getStart() :
1317        funcLikeDecl.body.getStart();
1318
1319      const children = funcLikeDecl.getChildren();
1320      for (let i = children.length - 1; i >= 0; i--) {
1321        const child = children[i];
1322        if (child.kind === ts.SyntaxKind.CloseParenToken && child.getEnd() <= postParametersPosition) {
1323          return child.getEnd();
1324        }
1325      }
1326    }
1327
1328    // Shouldn't get here.
1329    return -1;
1330  }
1331
1332  private static needsParentheses(node: ts.FunctionExpression): boolean {
1333    const parent = node.parent;
1334    return (
1335      ts.isPrefixUnaryExpression(parent) ||
1336      ts.isPostfixUnaryExpression(parent) ||
1337      ts.isPropertyAccessExpression(parent) ||
1338      ts.isElementAccessExpression(parent) ||
1339      ts.isTypeOfExpression(parent) ||
1340      ts.isVoidExpression(parent) ||
1341      ts.isAwaitExpression(parent) ||
1342      ts.isCallExpression(parent) && node === parent.expression ||
1343      ts.isBinaryExpression(parent) && !isAssignmentOperator(parent.operatorToken)
1344    );
1345  }
1346
1347  fixCtorParameterProperties(
1348    ctorDecl: ts.ConstructorDeclaration,
1349    paramTypes: ts.TypeNode[] | undefined
1350  ): Autofix[] | undefined {
1351    if (paramTypes === undefined) {
1352      return undefined;
1353    }
1354
1355    const fieldInitStmts: ts.Statement[] = [];
1356    const newFieldPos = ctorDecl.getStart();
1357    const autofixes: Autofix[] = [{ start: newFieldPos, end: newFieldPos, replacementText: '' }];
1358
1359    for (let i = 0; i < ctorDecl.parameters.length; i++) {
1360      this.fixCtorParameterPropertiesProcessParam(
1361        ctorDecl.parameters[i],
1362        paramTypes[i],
1363        ctorDecl.getSourceFile(),
1364        fieldInitStmts,
1365        autofixes
1366      );
1367    }
1368
1369    // Note: Bodyless ctors can't have parameter properties.
1370    if (ctorDecl.body) {
1371      const newBody = ts.factory.createBlock(fieldInitStmts.concat(ctorDecl.body.statements), true);
1372      const newBodyText = this.printer.printNode(ts.EmitHint.Unspecified, newBody, ctorDecl.getSourceFile());
1373      autofixes.push({ start: ctorDecl.body.getStart(), end: ctorDecl.body.getEnd(), replacementText: newBodyText });
1374    }
1375
1376    return autofixes;
1377  }
1378
1379  private fixCtorParameterPropertiesProcessParam(
1380    param: ts.ParameterDeclaration,
1381    paramType: ts.TypeNode,
1382    sourceFile: ts.SourceFile,
1383    fieldInitStmts: ts.Statement[],
1384    autofixes: Autofix[]
1385  ): void {
1386    // Parameter property can not be a destructuring parameter.
1387    if (!ts.isIdentifier(param.name)) {
1388      return;
1389    }
1390
1391    if (this.utils.hasAccessModifier(param)) {
1392      const propIdent = ts.factory.createIdentifier(param.name.text);
1393
1394      const newFieldNode = ts.factory.createPropertyDeclaration(
1395        ts.getModifiers(param),
1396        propIdent,
1397        undefined,
1398        paramType,
1399        undefined
1400      );
1401      const newFieldText = this.printer.printNode(ts.EmitHint.Unspecified, newFieldNode, sourceFile) + '\n';
1402      autofixes[0].replacementText += newFieldText;
1403
1404      const newParamDecl = ts.factory.createParameterDeclaration(
1405        undefined,
1406        undefined,
1407        param.name,
1408        param.questionToken,
1409        param.type,
1410        param.initializer
1411      );
1412      const newParamText = this.printer.printNode(ts.EmitHint.Unspecified, newParamDecl, sourceFile);
1413      autofixes.push({ start: param.getStart(), end: param.getEnd(), replacementText: newParamText });
1414
1415      fieldInitStmts.push(
1416        ts.factory.createExpressionStatement(
1417          ts.factory.createAssignment(
1418            ts.factory.createPropertyAccessExpression(ts.factory.createThis(), propIdent),
1419            propIdent
1420          )
1421        )
1422      );
1423    }
1424  }
1425
1426  fixPrivateIdentifier(node: ts.PrivateIdentifier): Autofix[] | undefined {
1427    const classMember = this.typeChecker.getSymbolAtLocation(node);
1428    if (!classMember || (classMember.getFlags() & ts.SymbolFlags.ClassMember) === 0 || !classMember.valueDeclaration) {
1429      return undefined;
1430    }
1431
1432    if (this.privateIdentifierCache.has(classMember)) {
1433      return this.privateIdentifierCache.get(classMember);
1434    }
1435
1436    const memberDecl = classMember.valueDeclaration as ts.ClassElement;
1437    const parentDecl = memberDecl.parent;
1438    if (!ts.isClassLike(parentDecl) || this.utils.classMemberHasDuplicateName(memberDecl, parentDecl, true)) {
1439      this.privateIdentifierCache.set(classMember, undefined);
1440      return undefined;
1441    }
1442
1443    let result: Autofix[] | undefined = [];
1444    this.symbolCache.getReferences(classMember).forEach((ident) => {
1445      if (ts.isPrivateIdentifier(ident)) {
1446        result!.push(this.fixSinglePrivateIdentifier(ident));
1447      }
1448    });
1449    if (!result.length) {
1450      result = undefined;
1451    }
1452
1453    this.privateIdentifierCache.set(classMember, result);
1454    return result;
1455  }
1456
1457  private isFunctionDeclarationFirst(tsFunctionDeclaration: ts.FunctionDeclaration): boolean {
1458    if (tsFunctionDeclaration.name === undefined) {
1459      return false;
1460    }
1461
1462    const symbol = this.typeChecker.getSymbolAtLocation(tsFunctionDeclaration.name);
1463    if (symbol === undefined) {
1464      return false;
1465    }
1466
1467    let minPos = tsFunctionDeclaration.pos;
1468    this.symbolCache.getReferences(symbol).forEach((ident) => {
1469      if (ident.pos < minPos) {
1470        minPos = ident.pos;
1471      }
1472    });
1473
1474    return minPos >= tsFunctionDeclaration.pos;
1475  }
1476
1477  fixNestedFunction(tsFunctionDeclaration: ts.FunctionDeclaration): Autofix[] | undefined {
1478    const isGenerator = tsFunctionDeclaration.asteriskToken !== undefined;
1479    const hasThisKeyword =
1480      tsFunctionDeclaration.body === undefined ? false : scopeContainsThis(tsFunctionDeclaration.body);
1481    const canBeFixed = !isGenerator && !hasThisKeyword;
1482    if (!canBeFixed) {
1483      return undefined;
1484    }
1485
1486    const name = tsFunctionDeclaration.name?.escapedText;
1487    const type = tsFunctionDeclaration.type;
1488    const body = tsFunctionDeclaration.body;
1489    if (!name || !type || !body) {
1490      return undefined;
1491    }
1492
1493    // Check only illegal decorators, cause all decorators for function declaration are illegal
1494    if (ts.getIllegalDecorators(tsFunctionDeclaration)) {
1495      return undefined;
1496    }
1497
1498    if (!this.isFunctionDeclarationFirst(tsFunctionDeclaration)) {
1499      return undefined;
1500    }
1501
1502    const typeParameters = tsFunctionDeclaration.typeParameters;
1503    const parameters = tsFunctionDeclaration.parameters;
1504    const modifiers = ts.getModifiers(tsFunctionDeclaration);
1505
1506    const token = ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken);
1507    const typeDecl = ts.factory.createFunctionTypeNode(typeParameters, parameters, type);
1508    const arrowFunc = ts.factory.createArrowFunction(modifiers, typeParameters, parameters, type, token, body);
1509
1510    const declaration: ts.VariableDeclaration = ts.factory.createVariableDeclaration(
1511      name,
1512      undefined,
1513      typeDecl,
1514      arrowFunc
1515    );
1516    const list: ts.VariableDeclarationList = ts.factory.createVariableDeclarationList([declaration], ts.NodeFlags.Let);
1517
1518    const statement = ts.factory.createVariableStatement(modifiers, list);
1519    const text = this.printer.printNode(ts.EmitHint.Unspecified, statement, tsFunctionDeclaration.getSourceFile());
1520    return [{ start: tsFunctionDeclaration.getStart(), end: tsFunctionDeclaration.getEnd(), replacementText: text }];
1521  }
1522
1523  fixMultipleStaticBlocks(nodes: ts.Node[]): Autofix[] | undefined {
1524    const autofix: Autofix[] | undefined = [];
1525    let body = (nodes[0] as ts.ClassStaticBlockDeclaration).body;
1526    let bodyStatements: ts.Statement[] = [];
1527    bodyStatements = bodyStatements.concat(body.statements);
1528    for (let i = 1; i < nodes.length; i++) {
1529      bodyStatements = bodyStatements.concat((nodes[i] as ts.ClassStaticBlockDeclaration).body.statements);
1530      autofix[i] = { start: nodes[i].getStart(), end: nodes[i].getEnd(), replacementText: '' };
1531    }
1532    body = ts.factory.createBlock(bodyStatements, true);
1533    // static blocks shouldn't have modifiers
1534    const statickBlock = ts.factory.createClassStaticBlockDeclaration(body);
1535    const text = this.printer.printNode(ts.EmitHint.Unspecified, statickBlock, nodes[0].getSourceFile());
1536    autofix[0] = { start: nodes[0].getStart(), end: nodes[0].getEnd(), replacementText: text };
1537    return autofix;
1538  }
1539
1540  private readonly privateIdentifierCache = new Map<ts.Symbol, Autofix[] | undefined>();
1541
1542  private fixSinglePrivateIdentifier(ident: ts.PrivateIdentifier): Autofix {
1543    if (
1544      ts.isPropertyDeclaration(ident.parent) ||
1545      ts.isMethodDeclaration(ident.parent) ||
1546      ts.isGetAccessorDeclaration(ident.parent) ||
1547      ts.isSetAccessorDeclaration(ident.parent)
1548    ) {
1549      // Note: 'private' modifier should always be first.
1550      const mods = ts.getModifiers(ident.parent);
1551      const newMods: ts.Modifier[] = [ts.factory.createModifier(ts.SyntaxKind.PrivateKeyword)];
1552      if (mods) {
1553        for (const mod of mods) {
1554          newMods.push(ts.factory.createModifier(mod.kind));
1555        }
1556      }
1557
1558      const newName = ident.text.slice(1, ident.text.length);
1559      const newDecl = Autofixer.replacePrivateIdentInDeclarationName(newMods, newName, ident.parent);
1560      const text = this.printer.printNode(ts.EmitHint.Unspecified, newDecl, ident.getSourceFile());
1561      return { start: ident.parent.getStart(), end: ident.parent.getEnd(), replacementText: text };
1562    }
1563
1564    return {
1565      start: ident.getStart(),
1566      end: ident.getEnd(),
1567      replacementText: ident.text.slice(1, ident.text.length)
1568    };
1569  }
1570
1571  private static replacePrivateIdentInDeclarationName(
1572    mods: ts.Modifier[],
1573    name: string,
1574    oldDecl: ts.PropertyDeclaration | ts.MethodDeclaration | ts.GetAccessorDeclaration | ts.SetAccessorDeclaration
1575  ): ts.Declaration {
1576    if (ts.isPropertyDeclaration(oldDecl)) {
1577      return ts.factory.createPropertyDeclaration(
1578        mods,
1579        ts.factory.createIdentifier(name),
1580        oldDecl.questionToken ?? oldDecl.exclamationToken,
1581        oldDecl.type,
1582        oldDecl.initializer
1583      );
1584    } else if (ts.isMethodDeclaration(oldDecl)) {
1585      return ts.factory.createMethodDeclaration(
1586        mods,
1587        oldDecl.asteriskToken,
1588        ts.factory.createIdentifier(name),
1589        oldDecl.questionToken,
1590        oldDecl.typeParameters,
1591        oldDecl.parameters,
1592        oldDecl.type,
1593        oldDecl.body
1594      );
1595    } else if (ts.isGetAccessorDeclaration(oldDecl)) {
1596      return ts.factory.createGetAccessorDeclaration(
1597        mods,
1598        ts.factory.createIdentifier(name),
1599        oldDecl.parameters,
1600        oldDecl.type,
1601        oldDecl.body
1602      );
1603    }
1604    return ts.factory.createSetAccessorDeclaration(
1605      mods,
1606      ts.factory.createIdentifier(name),
1607      oldDecl.parameters,
1608      oldDecl.body
1609    );
1610  }
1611
1612  fixRecordObjectLiteral(objectLiteralExpr: ts.ObjectLiteralExpression): Autofix[] | undefined {
1613    const autofix: Autofix[] = [];
1614
1615    for (const prop of objectLiteralExpr.properties) {
1616      if (!prop.name) {
1617        return undefined;
1618      }
1619      if (this.utils.isValidRecordObjectLiteralKey(prop.name)) {
1620        // Skip property with a valid property key.
1621        continue;
1622      }
1623      if (!ts.isIdentifier(prop.name)) {
1624        // Can only fix identifier name.
1625        return undefined;
1626      }
1627
1628      const stringLiteralName = ts.factory.createStringLiteralFromNode(prop.name, true);
1629      const text = this.printer.printNode(ts.EmitHint.Unspecified, stringLiteralName, prop.name.getSourceFile());
1630      autofix.push({ start: prop.name.getStart(), end: prop.name.getEnd(), replacementText: text });
1631    }
1632
1633    return autofix;
1634  }
1635
1636  fixUntypedObjectLiteral(
1637    objectLiteralExpr: ts.ObjectLiteralExpression,
1638    objectLiteralType: ts.Type | undefined
1639  ): Autofix[] | undefined {
1640    if (objectLiteralType) {
1641
1642      /*
1643       * Special case for object literal of Record type: fix object's property names
1644       * by replacing identifiers with string literals.
1645       */
1646      if (this.utils.isStdRecordType(this.utils.getNonNullableType(objectLiteralType))) {
1647        return this.fixRecordObjectLiteral(objectLiteralExpr);
1648      }
1649
1650      // Can't fix when object literal has a contextual type.
1651      return undefined;
1652    }
1653
1654    const enclosingStmt = TsUtils.getEnclosingTopLevelStatement(objectLiteralExpr);
1655    if (!enclosingStmt) {
1656      return undefined;
1657    }
1658
1659    const newInterfaceProps = this.getInterfacePropertiesFromObjectLiteral(objectLiteralExpr, enclosingStmt);
1660    if (!newInterfaceProps) {
1661      return undefined;
1662    }
1663
1664    const srcFile = objectLiteralExpr.getSourceFile();
1665    const newInterfaceName = TsUtils.generateUniqueName(this.objectLiteralInterfaceNameGenerator, srcFile);
1666    if (!newInterfaceName) {
1667      return undefined;
1668    }
1669
1670    return [
1671      this.createNewInterface(srcFile, newInterfaceName, newInterfaceProps, enclosingStmt.getStart()),
1672      this.fixObjectLiteralExpression(srcFile, newInterfaceName, objectLiteralExpr)
1673    ];
1674  }
1675
1676  private getInterfacePropertiesFromObjectLiteral(
1677    objectLiteralExpr: ts.ObjectLiteralExpression,
1678    enclosingStmt: ts.Node
1679  ): ts.PropertySignature[] | undefined {
1680    const interfaceProps: ts.PropertySignature[] = [];
1681    for (const prop of objectLiteralExpr.properties) {
1682      const interfaceProp = this.getInterfacePropertyFromObjectLiteralElement(prop, enclosingStmt);
1683      if (!interfaceProp) {
1684        return undefined;
1685      }
1686      interfaceProps.push(interfaceProp);
1687    }
1688    return interfaceProps;
1689  }
1690
1691  private getInterfacePropertyFromObjectLiteralElement(
1692    prop: ts.ObjectLiteralElementLike,
1693    enclosingStmt: ts.Node
1694  ): ts.PropertySignature | undefined {
1695    // Can't fix if property is not a key-value pair, or the property name is a computed value.
1696    if (!ts.isPropertyAssignment(prop) || ts.isComputedPropertyName(prop.name)) {
1697      return undefined;
1698    }
1699
1700    const propType = this.typeChecker.getTypeAtLocation(prop);
1701
1702    // Can't capture generic type parameters of enclosing declarations.
1703    if (this.utils.hasGenericTypeParameter(propType)) {
1704      return undefined;
1705    }
1706
1707    if (Autofixer.propertyTypeIsCapturedFromEnclosingLocalScope(propType, enclosingStmt)) {
1708      return undefined;
1709    }
1710
1711    const propTypeNode = this.typeChecker.typeToTypeNode(propType, undefined, ts.NodeBuilderFlags.None);
1712    if (!propTypeNode || !this.utils.isSupportedType(propTypeNode)) {
1713      return undefined;
1714    }
1715
1716    const newProp: ts.PropertySignature = ts.factory.createPropertySignature(
1717      undefined,
1718      prop.name,
1719      undefined,
1720      propTypeNode
1721    );
1722    return newProp;
1723  }
1724
1725  private static propertyTypeIsCapturedFromEnclosingLocalScope(type: ts.Type, enclosingStmt: ts.Node): boolean {
1726    const sym = type.getSymbol();
1727    let symNode: ts.Node | undefined = TsUtils.getDeclaration(sym);
1728
1729    while (symNode) {
1730      if (symNode === enclosingStmt) {
1731        return true;
1732      }
1733      symNode = symNode.parent;
1734    }
1735
1736    return false;
1737  }
1738
1739  private createNewInterface(
1740    srcFile: ts.SourceFile,
1741    interfaceName: string,
1742    members: ts.TypeElement[],
1743    pos: number
1744  ): Autofix {
1745    const newInterfaceDecl = ts.factory.createInterfaceDeclaration(
1746      undefined,
1747      interfaceName,
1748      undefined,
1749      undefined,
1750      members
1751    );
1752    const text = this.printer.printNode(ts.EmitHint.Unspecified, newInterfaceDecl, srcFile) + '\n';
1753    return { start: pos, end: pos, replacementText: text };
1754  }
1755
1756  private fixObjectLiteralExpression(
1757    srcFile: ts.SourceFile,
1758    newInterfaceName: string,
1759    objectLiteralExpr: ts.ObjectLiteralExpression
1760  ): Autofix {
1761
1762    /*
1763     * If object literal is initializing a variable or property,
1764     * then simply add new 'contextual' type to the declaration.
1765     * Otherwise, cast object literal to newly created interface type.
1766     */
1767    if (
1768      (ts.isVariableDeclaration(objectLiteralExpr.parent) ||
1769        ts.isPropertyDeclaration(objectLiteralExpr.parent) ||
1770        ts.isParameter(objectLiteralExpr.parent)) &&
1771      !objectLiteralExpr.parent.type
1772    ) {
1773      const text = ': ' + newInterfaceName;
1774      const pos = Autofixer.getDeclarationTypePositionForObjectLiteral(objectLiteralExpr.parent);
1775      return { start: pos, end: pos, replacementText: text };
1776    }
1777
1778    const newTypeRef = ts.factory.createTypeReferenceNode(newInterfaceName);
1779    let newExpr: ts.Expression = ts.factory.createAsExpression(
1780      ts.factory.createObjectLiteralExpression(objectLiteralExpr.properties),
1781      newTypeRef
1782    );
1783    if (!ts.isParenthesizedExpression(objectLiteralExpr.parent)) {
1784      newExpr = ts.factory.createParenthesizedExpression(newExpr);
1785    }
1786    const text = this.printer.printNode(ts.EmitHint.Unspecified, newExpr, srcFile);
1787    return { start: objectLiteralExpr.getStart(), end: objectLiteralExpr.getEnd(), replacementText: text };
1788  }
1789
1790  private static getDeclarationTypePositionForObjectLiteral(
1791    decl: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration
1792  ): number {
1793    if (ts.isPropertyDeclaration(decl)) {
1794      return (decl.questionToken || decl.exclamationToken || decl.name).getEnd();
1795    } else if (ts.isParameter(decl)) {
1796      return (decl.questionToken || decl.name).getEnd();
1797    }
1798    return (decl.exclamationToken || decl.name).getEnd();
1799  }
1800
1801  private readonly objectLiteralInterfaceNameGenerator = new NameGenerator(
1802    GENERATED_OBJECT_LITERAL_INTERFACE_NAME,
1803    GENERATED_OBJECT_LITERAL_INTERFACE_TRESHOLD
1804  );
1805
1806  /*
1807   * In case of type alias initialized with type literal, replace
1808   * entire type alias with identical interface declaration.
1809   */
1810  private proceedTypeAliasDeclaration(typeLiteral: ts.TypeLiteralNode): Autofix[] | undefined {
1811    if (ts.isTypeAliasDeclaration(typeLiteral.parent)) {
1812      const typeAlias = typeLiteral.parent;
1813      const newInterfaceDecl = ts.factory.createInterfaceDeclaration(
1814        typeAlias.modifiers,
1815        typeAlias.name,
1816        typeAlias.typeParameters,
1817        undefined,
1818        typeLiteral.members
1819      );
1820      const text = this.printer.printNode(ts.EmitHint.Unspecified, newInterfaceDecl, typeLiteral.getSourceFile());
1821      return [{ start: typeAlias.getStart(), end: typeAlias.getEnd(), replacementText: text }];
1822    }
1823    return undefined;
1824  }
1825
1826  fixTypeliteral(typeLiteral: ts.TypeLiteralNode): Autofix[] | undefined {
1827    const typeAliasAutofix = this.proceedTypeAliasDeclaration(typeLiteral);
1828    if (typeAliasAutofix) {
1829      return typeAliasAutofix;
1830    }
1831
1832    /*
1833     * Create new interface declaration with members of type literal
1834     * and put the interface name in place of the type literal.
1835     */
1836    const srcFile = typeLiteral.getSourceFile();
1837    const enclosingStmt = TsUtils.getEnclosingTopLevelStatement(typeLiteral);
1838    if (!enclosingStmt) {
1839      return undefined;
1840    }
1841
1842    if (this.typeLiteralCapturesTypeFromEnclosingLocalScope(typeLiteral, enclosingStmt)) {
1843      return undefined;
1844    }
1845
1846    const newInterfaceName = TsUtils.generateUniqueName(this.typeLiteralInterfaceNameGenerator, srcFile);
1847    if (!newInterfaceName) {
1848      return undefined;
1849    }
1850    const newInterfacePos = enclosingStmt.getStart();
1851    const newInterfaceDecl = ts.factory.createInterfaceDeclaration(
1852      undefined,
1853      newInterfaceName,
1854      undefined,
1855      undefined,
1856      typeLiteral.members
1857    );
1858    const interfaceText = this.printer.printNode(ts.EmitHint.Unspecified, newInterfaceDecl, srcFile) + '\n';
1859
1860    return [
1861      { start: newInterfacePos, end: newInterfacePos, replacementText: interfaceText },
1862      { start: typeLiteral.getStart(), end: typeLiteral.getEnd(), replacementText: newInterfaceName }
1863    ];
1864  }
1865
1866  typeLiteralCapturesTypeFromEnclosingLocalScope(typeLiteral: ts.TypeLiteralNode, enclosingStmt: ts.Node): boolean {
1867    let found = false;
1868
1869    const callback = (node: ts.Node): void => {
1870      if (!ts.isIdentifier(node)) {
1871        return;
1872      }
1873      const sym = this.typeChecker.getSymbolAtLocation(node);
1874      let symNode: ts.Node | undefined = TsUtils.getDeclaration(sym);
1875      while (symNode) {
1876        if (symNode === typeLiteral) {
1877          return;
1878        }
1879        if (symNode === enclosingStmt) {
1880          found = true;
1881          return;
1882        }
1883        symNode = symNode.parent;
1884      }
1885    };
1886
1887    const stopCondition = (node: ts.Node): boolean => {
1888      void node;
1889      return found;
1890    };
1891
1892    forEachNodeInSubtree(typeLiteral, callback, stopCondition);
1893    return found;
1894  }
1895
1896  // eslint-disable-next-line class-methods-use-this
1897  removeDecorator(decorator: ts.Decorator): Autofix[] {
1898    return [{ start: decorator.getStart(), end: decorator.getEnd(), replacementText: '' }];
1899  }
1900
1901  private readonly typeLiteralInterfaceNameGenerator = new NameGenerator(
1902    GENERATED_TYPE_LITERAL_INTERFACE_NAME,
1903    GENERATED_TYPE_LITERAL_INTERFACE_TRESHOLD
1904  );
1905
1906  private readonly destructObjNameGenerator = new NameGenerator(
1907    GENERATED_DESTRUCT_OBJECT_NAME,
1908    GENERATED_DESTRUCT_OBJECT_TRESHOLD
1909  );
1910
1911  private readonly destructArrayNameGenerator = new NameGenerator(
1912    GENERATED_DESTRUCT_ARRAY_NAME,
1913    GENERATED_DESTRUCT_ARRAY_TRESHOLD
1914  );
1915
1916  private readonly symbolCache: SymbolCache;
1917}
1918