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