1import { 2 AnyImportOrRequireStatement, arrayIsSorted, binarySearch, compareBooleans, compareStringsCaseInsensitive, 3 compareValues, Comparison, createScanner, emptyArray, ExportDeclaration, ExportSpecifier, Expression, factory, 4 FileTextChanges, 5 FindAllReferences, flatMap, formatting, getNewLineOrDefaultFromHost, group, Identifier, identity, ImportDeclaration, 6 ImportOrExportSpecifier, ImportSpecifier, isAmbientModule, isExportDeclaration, isExternalModuleNameRelative, 7 isExternalModuleReference, isImportDeclaration, isNamedExports, isNamedImports, isNamespaceImport, isString, 8 isStringLiteral, isStringLiteralLike, jsxModeNeedsExplicitImport, LanguageServiceHost, length, map, 9 NamedImportBindings, NamedImports, NamespaceImport, OrganizeImportsMode, Program, Scanner, some, 10 SortedReadonlyArray, SourceFile, stableSort, suppressLeadingTrivia, SyntaxKind, textChanges, TransformFlags, 11 tryCast, UserPreferences, 12} from "./_namespaces/ts"; 13 14/** 15 * Organize imports by: 16 * 1) Removing unused imports 17 * 2) Coalescing imports from the same module 18 * 3) Sorting imports 19 * 20 * @internal 21 */ 22export function organizeImports( 23 sourceFile: SourceFile, 24 formatContext: formatting.FormatContext, 25 host: LanguageServiceHost, 26 program: Program, 27 preferences: UserPreferences, 28 mode: OrganizeImportsMode, 29): FileTextChanges[] { 30 const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext, preferences }); 31 const shouldSort = mode === OrganizeImportsMode.SortAndCombine || mode === OrganizeImportsMode.All; 32 const shouldCombine = shouldSort; // These are currently inseparable, but I draw a distinction for clarity and in case we add modes in the future. 33 const shouldRemove = mode === OrganizeImportsMode.RemoveUnused || mode === OrganizeImportsMode.All; 34 const maybeRemove = shouldRemove ? removeUnusedImports : identity; 35 const maybeCoalesce = shouldCombine ? coalesceImports : identity; 36 const processImportsOfSameModuleSpecifier = (importGroup: readonly ImportDeclaration[]) => { 37 const processedDeclarations = maybeCoalesce(maybeRemove(importGroup, sourceFile, program)); 38 return shouldSort 39 ? stableSort(processedDeclarations, (s1, s2) => compareImportsOrRequireStatements(s1, s2)) 40 : processedDeclarations; 41 }; 42 43 // All of the old ImportDeclarations in the file, in syntactic order. 44 const topLevelImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration)); 45 topLevelImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier)); 46 47 // Exports are always used 48 if (mode !== OrganizeImportsMode.RemoveUnused) { 49 // All of the old ExportDeclarations in the file, in syntactic order. 50 const topLevelExportDecls = sourceFile.statements.filter(isExportDeclaration); 51 organizeImportsWorker(topLevelExportDecls, coalesceExports); 52 } 53 54 for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) { 55 if (!ambientModule.body) continue; 56 57 const ambientModuleImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, ambientModule.body.statements.filter(isImportDeclaration)); 58 ambientModuleImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier)); 59 60 // Exports are always used 61 if (mode !== OrganizeImportsMode.RemoveUnused) { 62 const ambientModuleExportDecls = ambientModule.body.statements.filter(isExportDeclaration); 63 organizeImportsWorker(ambientModuleExportDecls, coalesceExports); 64 } 65 } 66 67 return changeTracker.getChanges(); 68 69 function organizeImportsWorker<T extends ImportDeclaration | ExportDeclaration>( 70 oldImportDecls: readonly T[], 71 coalesce: (group: readonly T[]) => readonly T[], 72 ) { 73 if (length(oldImportDecls) === 0) { 74 return; 75 } 76 77 // Special case: normally, we'd expect leading and trailing trivia to follow each import 78 // around as it's sorted. However, we do not want this to happen for leading trivia 79 // on the first import because it is probably the header comment for the file. 80 // Consider: we could do a more careful check that this trivia is actually a header, 81 // but the consequences of being wrong are very minor. 82 suppressLeadingTrivia(oldImportDecls[0]); 83 84 const oldImportGroups = shouldCombine 85 ? group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier!)!) 86 : [oldImportDecls]; 87 const sortedImportGroups = shouldSort 88 ? stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiers(group1[0].moduleSpecifier, group2[0].moduleSpecifier)) 89 : oldImportGroups; 90 const newImportDecls = flatMap(sortedImportGroups, importGroup => 91 getExternalModuleName(importGroup[0].moduleSpecifier!) 92 ? coalesce(importGroup) 93 : importGroup); 94 95 // Delete all nodes if there are no imports. 96 if (newImportDecls.length === 0) { 97 // Consider the first node to have trailingTrivia as we want to exclude the 98 // "header" comment. 99 changeTracker.deleteNodes(sourceFile, oldImportDecls, { 100 trailingTriviaOption: textChanges.TrailingTriviaOption.Include, 101 }, /*hasTrailingComment*/ true); 102 } 103 else { 104 // Note: Delete the surrounding trivia because it will have been retained in newImportDecls. 105 const replaceOptions = { 106 leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, // Leave header comment in place 107 trailingTriviaOption: textChanges.TrailingTriviaOption.Include, 108 suffix: getNewLineOrDefaultFromHost(host, formatContext.options), 109 }; 110 changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, replaceOptions); 111 const hasTrailingComment = changeTracker.nodeHasTrailingComment(sourceFile, oldImportDecls[0], replaceOptions); 112 changeTracker.deleteNodes(sourceFile, oldImportDecls.slice(1), { 113 trailingTriviaOption: textChanges.TrailingTriviaOption.Include, 114 }, hasTrailingComment); 115 } 116 } 117} 118 119function groupImportsByNewlineContiguous(sourceFile: SourceFile, importDecls: ImportDeclaration[]): ImportDeclaration[][] { 120 const scanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/ false, sourceFile.languageVariant); 121 const groupImports: ImportDeclaration[][] = []; 122 let groupIndex = 0; 123 for (const topLevelImportDecl of importDecls) { 124 if (isNewGroup(sourceFile, topLevelImportDecl, scanner)) { 125 groupIndex++; 126 } 127 128 if (!groupImports[groupIndex]) { 129 groupImports[groupIndex] = []; 130 } 131 132 groupImports[groupIndex].push(topLevelImportDecl); 133 } 134 135 return groupImports; 136} 137 138// a new group is created if an import includes at least two new line 139// new line from multi-line comment doesn't count 140function isNewGroup(sourceFile: SourceFile, topLevelImportDecl: ImportDeclaration, scanner: Scanner) { 141 const startPos = topLevelImportDecl.getFullStart(); 142 const endPos = topLevelImportDecl.getStart(); 143 scanner.setText(sourceFile.text, startPos, endPos - startPos); 144 145 let numberOfNewLines = 0; 146 while (scanner.getTokenPos() < endPos) { 147 const tokenKind = scanner.scan(); 148 149 if (tokenKind === SyntaxKind.NewLineTrivia) { 150 numberOfNewLines++; 151 152 if (numberOfNewLines >= 2) { 153 return true; 154 } 155 } 156 } 157 158 return false; 159} 160 161function removeUnusedImports(oldImports: readonly ImportDeclaration[], sourceFile: SourceFile, program: Program) { 162 const typeChecker = program.getTypeChecker(); 163 const compilerOptions = program.getCompilerOptions(); 164 const jsxNamespace = typeChecker.getJsxNamespace(sourceFile); 165 const jsxFragmentFactory = typeChecker.getJsxFragmentFactory(sourceFile); 166 const jsxElementsPresent = !!(sourceFile.transformFlags & TransformFlags.ContainsJsx); 167 168 const usedImports: ImportDeclaration[] = []; 169 170 for (const importDecl of oldImports) { 171 const { importClause, moduleSpecifier } = importDecl; 172 173 if (!importClause) { 174 // Imports without import clauses are assumed to be included for their side effects and are not removed. 175 usedImports.push(importDecl); 176 continue; 177 } 178 179 let { name, namedBindings } = importClause; 180 181 // Default import 182 if (name && !isDeclarationUsed(name)) { 183 name = undefined; 184 } 185 186 if (namedBindings) { 187 if (isNamespaceImport(namedBindings)) { 188 // Namespace import 189 if (!isDeclarationUsed(namedBindings.name)) { 190 namedBindings = undefined; 191 } 192 } 193 else { 194 // List of named imports 195 const newElements = namedBindings.elements.filter(e => isDeclarationUsed(e.name)); 196 if (newElements.length < namedBindings.elements.length) { 197 namedBindings = newElements.length 198 ? factory.updateNamedImports(namedBindings, newElements) 199 : undefined; 200 } 201 } 202 } 203 204 if (name || namedBindings) { 205 usedImports.push(updateImportDeclarationAndClause(importDecl, name, namedBindings)); 206 } 207 // If a module is imported to be augmented, it’s used 208 else if (hasModuleDeclarationMatchingSpecifier(sourceFile, moduleSpecifier)) { 209 // If we’re in a declaration file, it’s safe to remove the import clause from it 210 if (sourceFile.isDeclarationFile) { 211 usedImports.push(factory.createImportDeclaration( 212 importDecl.modifiers, 213 /*importClause*/ undefined, 214 moduleSpecifier, 215 /*assertClause*/ undefined)); 216 } 217 // If we’re not in a declaration file, we can’t remove the import clause even though 218 // the imported symbols are unused, because removing them makes it look like the import 219 // declaration has side effects, which will cause it to be preserved in the JS emit. 220 else { 221 usedImports.push(importDecl); 222 } 223 } 224 } 225 226 return usedImports; 227 228 function isDeclarationUsed(identifier: Identifier) { 229 // The JSX factory symbol is always used if JSX elements are present - even if they are not allowed. 230 return jsxElementsPresent && (identifier.text === jsxNamespace || jsxFragmentFactory && identifier.text === jsxFragmentFactory) && jsxModeNeedsExplicitImport(compilerOptions.jsx) || 231 FindAllReferences.Core.isSymbolReferencedInFile(identifier, typeChecker, sourceFile); 232 } 233} 234 235function hasModuleDeclarationMatchingSpecifier(sourceFile: SourceFile, moduleSpecifier: Expression) { 236 const moduleSpecifierText = isStringLiteral(moduleSpecifier) && moduleSpecifier.text; 237 return isString(moduleSpecifierText) && some(sourceFile.moduleAugmentations, moduleName => 238 isStringLiteral(moduleName) 239 && moduleName.text === moduleSpecifierText); 240} 241 242function getExternalModuleName(specifier: Expression) { 243 return specifier !== undefined && isStringLiteralLike(specifier) 244 ? specifier.text 245 : undefined; 246} 247 248// Internal for testing 249/** 250 * @param importGroup a list of ImportDeclarations, all with the same module name. 251 * 252 * @internal 253 */ 254export function coalesceImports(importGroup: readonly ImportDeclaration[]) { 255 if (importGroup.length === 0) { 256 return importGroup; 257 } 258 259 const { importWithoutClause, typeOnlyImports, regularImports } = getCategorizedImports(importGroup); 260 261 const coalescedImports: ImportDeclaration[] = []; 262 263 if (importWithoutClause) { 264 coalescedImports.push(importWithoutClause); 265 } 266 267 for (const group of [regularImports, typeOnlyImports]) { 268 const isTypeOnly = group === typeOnlyImports; 269 const { defaultImports, namespaceImports, namedImports } = group; 270 // Normally, we don't combine default and namespace imports, but it would be silly to 271 // produce two import declarations in this special case. 272 if (!isTypeOnly && defaultImports.length === 1 && namespaceImports.length === 1 && namedImports.length === 0) { 273 // Add the namespace import to the existing default ImportDeclaration. 274 const defaultImport = defaultImports[0]; 275 coalescedImports.push( 276 updateImportDeclarationAndClause(defaultImport, defaultImport.importClause!.name, namespaceImports[0].importClause!.namedBindings)); // TODO: GH#18217 277 278 continue; 279 } 280 281 const sortedNamespaceImports = stableSort(namespaceImports, (i1, i2) => 282 compareIdentifiers((i1.importClause!.namedBindings as NamespaceImport).name, (i2.importClause!.namedBindings as NamespaceImport).name)); // TODO: GH#18217 283 284 for (const namespaceImport of sortedNamespaceImports) { 285 // Drop the name, if any 286 coalescedImports.push( 287 updateImportDeclarationAndClause(namespaceImport, /*name*/ undefined, namespaceImport.importClause!.namedBindings)); // TODO: GH#18217 288 } 289 290 if (defaultImports.length === 0 && namedImports.length === 0) { 291 continue; 292 } 293 294 let newDefaultImport: Identifier | undefined; 295 const newImportSpecifiers: ImportSpecifier[] = []; 296 if (defaultImports.length === 1) { 297 newDefaultImport = defaultImports[0].importClause!.name; 298 } 299 else { 300 for (const defaultImport of defaultImports) { 301 newImportSpecifiers.push( 302 factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("default"), defaultImport.importClause!.name!)); // TODO: GH#18217 303 } 304 } 305 306 newImportSpecifiers.push(...getNewImportSpecifiers(namedImports)); 307 308 const sortedImportSpecifiers = sortSpecifiers(newImportSpecifiers); 309 const importDecl = defaultImports.length > 0 310 ? defaultImports[0] 311 : namedImports[0]; 312 313 const newNamedImports = sortedImportSpecifiers.length === 0 314 ? newDefaultImport 315 ? undefined 316 : factory.createNamedImports(emptyArray) 317 : namedImports.length === 0 318 ? factory.createNamedImports(sortedImportSpecifiers) 319 : factory.updateNamedImports(namedImports[0].importClause!.namedBindings as NamedImports, sortedImportSpecifiers); // TODO: GH#18217 320 321 // Type-only imports are not allowed to mix default, namespace, and named imports in any combination. 322 // We could rewrite a default import as a named import (`import { default as name }`), but we currently 323 // choose not to as a stylistic preference. 324 if (isTypeOnly && newDefaultImport && newNamedImports) { 325 coalescedImports.push( 326 updateImportDeclarationAndClause(importDecl, newDefaultImport, /*namedBindings*/ undefined)); 327 coalescedImports.push( 328 updateImportDeclarationAndClause(namedImports[0] ?? importDecl, /*name*/ undefined, newNamedImports)); 329 } 330 else { 331 coalescedImports.push( 332 updateImportDeclarationAndClause(importDecl, newDefaultImport, newNamedImports)); 333 } 334 } 335 336 return coalescedImports; 337 338} 339 340interface ImportGroup { 341 defaultImports: ImportDeclaration[]; 342 namespaceImports: ImportDeclaration[]; 343 namedImports: ImportDeclaration[]; 344} 345 346/* 347 * Returns entire import declarations because they may already have been rewritten and 348 * may lack parent pointers. The desired parts can easily be recovered based on the 349 * categorization. 350 * 351 * NB: There may be overlap between `defaultImports` and `namespaceImports`/`namedImports`. 352 */ 353function getCategorizedImports(importGroup: readonly ImportDeclaration[]) { 354 let importWithoutClause: ImportDeclaration | undefined; 355 const typeOnlyImports: ImportGroup = { defaultImports: [], namespaceImports: [], namedImports: [] }; 356 const regularImports: ImportGroup = { defaultImports: [], namespaceImports: [], namedImports: [] }; 357 358 for (const importDeclaration of importGroup) { 359 if (importDeclaration.importClause === undefined) { 360 // Only the first such import is interesting - the others are redundant. 361 // Note: Unfortunately, we will lose trivia that was on this node. 362 importWithoutClause = importWithoutClause || importDeclaration; 363 continue; 364 } 365 366 const group = importDeclaration.importClause.isTypeOnly ? typeOnlyImports : regularImports; 367 const { name, namedBindings } = importDeclaration.importClause; 368 369 if (name) { 370 group.defaultImports.push(importDeclaration); 371 } 372 373 if (namedBindings) { 374 if (isNamespaceImport(namedBindings)) { 375 group.namespaceImports.push(importDeclaration); 376 } 377 else { 378 group.namedImports.push(importDeclaration); 379 } 380 } 381 } 382 383 return { 384 importWithoutClause, 385 typeOnlyImports, 386 regularImports, 387 }; 388} 389 390// Internal for testing 391/** 392 * @param exportGroup a list of ExportDeclarations, all with the same module name. 393 * 394 * @internal 395 */ 396export function coalesceExports(exportGroup: readonly ExportDeclaration[]) { 397 if (exportGroup.length === 0) { 398 return exportGroup; 399 } 400 401 const { exportWithoutClause, namedExports, typeOnlyExports } = getCategorizedExports(exportGroup); 402 403 const coalescedExports: ExportDeclaration[] = []; 404 405 if (exportWithoutClause) { 406 coalescedExports.push(exportWithoutClause); 407 } 408 409 for (const exportGroup of [namedExports, typeOnlyExports]) { 410 if (exportGroup.length === 0) { 411 continue; 412 } 413 const newExportSpecifiers: ExportSpecifier[] = []; 414 newExportSpecifiers.push(...flatMap(exportGroup, i => i.exportClause && isNamedExports(i.exportClause) ? i.exportClause.elements : emptyArray)); 415 416 const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers); 417 418 const exportDecl = exportGroup[0]; 419 coalescedExports.push( 420 factory.updateExportDeclaration( 421 exportDecl, 422 exportDecl.modifiers, 423 exportDecl.isTypeOnly, 424 exportDecl.exportClause && ( 425 isNamedExports(exportDecl.exportClause) ? 426 factory.updateNamedExports(exportDecl.exportClause, sortedExportSpecifiers) : 427 factory.updateNamespaceExport(exportDecl.exportClause, exportDecl.exportClause.name) 428 ), 429 exportDecl.moduleSpecifier, 430 exportDecl.assertClause)); 431 } 432 433 return coalescedExports; 434 435 /* 436 * Returns entire export declarations because they may already have been rewritten and 437 * may lack parent pointers. The desired parts can easily be recovered based on the 438 * categorization. 439 */ 440 function getCategorizedExports(exportGroup: readonly ExportDeclaration[]) { 441 let exportWithoutClause: ExportDeclaration | undefined; 442 const namedExports: ExportDeclaration[] = []; 443 const typeOnlyExports: ExportDeclaration[] = []; 444 445 for (const exportDeclaration of exportGroup) { 446 if (exportDeclaration.exportClause === undefined) { 447 // Only the first such export is interesting - the others are redundant. 448 // Note: Unfortunately, we will lose trivia that was on this node. 449 exportWithoutClause = exportWithoutClause || exportDeclaration; 450 } 451 else if (exportDeclaration.isTypeOnly) { 452 typeOnlyExports.push(exportDeclaration); 453 } 454 else { 455 namedExports.push(exportDeclaration); 456 } 457 } 458 459 return { 460 exportWithoutClause, 461 namedExports, 462 typeOnlyExports, 463 }; 464 } 465} 466 467function updateImportDeclarationAndClause( 468 importDeclaration: ImportDeclaration, 469 name: Identifier | undefined, 470 namedBindings: NamedImportBindings | undefined) { 471 472 return factory.updateImportDeclaration( 473 importDeclaration, 474 importDeclaration.modifiers, 475 factory.updateImportClause(importDeclaration.importClause!, importDeclaration.importClause!.isTypeOnly, name, namedBindings), // TODO: GH#18217 476 importDeclaration.moduleSpecifier, 477 importDeclaration.assertClause); 478} 479 480function sortSpecifiers<T extends ImportOrExportSpecifier>(specifiers: readonly T[]) { 481 return stableSort(specifiers, compareImportOrExportSpecifiers); 482} 483 484/** @internal */ 485export function compareImportOrExportSpecifiers<T extends ImportOrExportSpecifier>(s1: T, s2: T): Comparison { 486 return compareBooleans(s1.isTypeOnly, s2.isTypeOnly) 487 || compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name) 488 || compareIdentifiers(s1.name, s2.name); 489} 490 491/** 492 * Exported for testing 493 * 494 * @internal 495 */ 496export function compareModuleSpecifiers(m1: Expression | undefined, m2: Expression | undefined) { 497 const name1 = m1 === undefined ? undefined : getExternalModuleName(m1); 498 const name2 = m2 === undefined ? undefined : getExternalModuleName(m2); 499 return compareBooleans(name1 === undefined, name2 === undefined) || 500 compareBooleans(isExternalModuleNameRelative(name1!), isExternalModuleNameRelative(name2!)) || 501 compareStringsCaseInsensitive(name1!, name2!); 502} 503 504function compareIdentifiers(s1: Identifier, s2: Identifier) { 505 return compareStringsCaseInsensitive(s1.text, s2.text); 506} 507 508function getModuleSpecifierExpression(declaration: AnyImportOrRequireStatement): Expression | undefined { 509 switch (declaration.kind) { 510 case SyntaxKind.ImportEqualsDeclaration: 511 return tryCast(declaration.moduleReference, isExternalModuleReference)?.expression; 512 case SyntaxKind.ImportDeclaration: 513 return declaration.moduleSpecifier; 514 case SyntaxKind.VariableStatement: 515 return declaration.declarationList.declarations[0].initializer.arguments[0]; 516 } 517} 518 519/** @internal */ 520export function importsAreSorted(imports: readonly AnyImportOrRequireStatement[]): imports is SortedReadonlyArray<AnyImportOrRequireStatement> { 521 return arrayIsSorted(imports, compareImportsOrRequireStatements); 522} 523 524/** @internal */ 525export function importSpecifiersAreSorted(imports: readonly ImportSpecifier[]): imports is SortedReadonlyArray<ImportSpecifier> { 526 return arrayIsSorted(imports, compareImportOrExportSpecifiers); 527} 528 529/** @internal */ 530export function getImportDeclarationInsertionIndex(sortedImports: SortedReadonlyArray<AnyImportOrRequireStatement>, newImport: AnyImportOrRequireStatement) { 531 const index = binarySearch(sortedImports, newImport, identity, compareImportsOrRequireStatements); 532 return index < 0 ? ~index : index; 533} 534 535/** @internal */ 536export function getImportSpecifierInsertionIndex(sortedImports: SortedReadonlyArray<ImportSpecifier>, newImport: ImportSpecifier) { 537 const index = binarySearch(sortedImports, newImport, identity, compareImportOrExportSpecifiers); 538 return index < 0 ? ~index : index; 539} 540 541/** @internal */ 542export function compareImportsOrRequireStatements(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) { 543 return compareModuleSpecifiers(getModuleSpecifierExpression(s1), getModuleSpecifierExpression(s2)) || compareImportKind(s1, s2); 544} 545 546function compareImportKind(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) { 547 return compareValues(getImportKindOrder(s1), getImportKindOrder(s2)); 548} 549 550// 1. Side-effect imports 551// 2. Type-only imports 552// 3. Namespace imports 553// 4. Default imports 554// 5. Named imports 555// 6. ImportEqualsDeclarations 556// 7. Require variable statements 557function getImportKindOrder(s1: AnyImportOrRequireStatement) { 558 switch (s1.kind) { 559 case SyntaxKind.ImportDeclaration: 560 if (!s1.importClause) return 0; 561 if (s1.importClause.isTypeOnly) return 1; 562 if (s1.importClause.namedBindings?.kind === SyntaxKind.NamespaceImport) return 2; 563 if (s1.importClause.name) return 3; 564 return 4; 565 case SyntaxKind.ImportEqualsDeclaration: 566 return 5; 567 case SyntaxKind.VariableStatement: 568 return 6; 569 } 570} 571 572function getNewImportSpecifiers(namedImports: ImportDeclaration[]) { 573 return flatMap(namedImports, namedImport => 574 map(tryGetNamedBindingElements(namedImport), importSpecifier => 575 importSpecifier.name && importSpecifier.propertyName && importSpecifier.name.escapedText === importSpecifier.propertyName.escapedText 576 ? factory.updateImportSpecifier(importSpecifier, importSpecifier.isTypeOnly, /*propertyName*/ undefined, importSpecifier.name) 577 : importSpecifier 578 ) 579 ); 580} 581 582function tryGetNamedBindingElements(namedImport: ImportDeclaration) { 583 return namedImport.importClause?.namedBindings && isNamedImports(namedImport.importClause.namedBindings) 584 ? namedImport.importClause.namedBindings.elements 585 : undefined; 586} 587