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