namespace ts . OrganizeImports { /**
     * Organize imports by:
     *   1) Removing unused imports
     *   2) Coalescing imports from the same module
     *   3) Sorting imports
     */ export function organizeImports ( sourceFile , formatContext , host , program , preferences , skipDestructiveCodeActions ? ) { const changeTracker = textChanges . ChangeTracker . fromContext ( { host , formatContext , preferences } ) ; const coalesceAndOrganizeImports = ( importGroup ) => stableSort ( coalesceImports ( removeUnusedImports ( importGroup , sourceFile , program , skipDestructiveCodeActions ) ) , ( s1 , s2 ) => compareImportsOrRequireStatements ( s1 , s2 ) ) ; const topLevelImportDecls = sourceFile . statements . filter ( isImportDeclaration ) ; organizeImportsWorker ( topLevelImportDecls , coalesceAndOrganizeImports ) ; const topLevelExportDecls = sourceFile . statements . filter ( isExportDeclaration ) ; organizeImportsWorker ( topLevelExportDecls , coalesceExports ) ; for ( const ambientModule of sourceFile . statements . filter ( isAmbientModule ) ) { if ( ! ambientModule . body ) continue ; const ambientModuleImportDecls = ambientModule . body . statements . filter ( isImportDeclaration ) ; organizeImportsWorker ( ambientModuleImportDecls , coalesceAndOrganizeImports ) ; const ambientModuleExportDecls = ambientModule . body . statements . filter ( isExportDeclaration ) ; organizeImportsWorker ( ambientModuleExportDecls , coalesceExports ) ; } return changeTracker . getChanges ( ) ; function organizeImportsWorker < T extends ImportDeclaration | ExportDeclaration > ( oldImportDecls , coalesce ) { if ( length ( oldImportDecls ) === 0 ) { return ; } suppressLeadingTrivia ( oldImportDecls [ 0 ] ) ; const oldImportGroups = group ( oldImportDecls , importDecl => getExternalModuleName ( importDecl . moduleSpecifier ! ) ! ) ; const sortedImportGroups = stableSort ( oldImportGroups , ( group1 , group2 ) => compareModuleSpecifiers ( group1 [ 0 ] . moduleSpecifier , group2 [ 0 ] . moduleSpecifier ) ) ; const newImportDecls = flatMap ( sortedImportGroups , importGroup => getExternalModuleName ( importGroup [ 0 ] . moduleSpecifier ! ) ? coalesce ( importGroup ) : importGroup ) ; if ( newImportDecls . length === 0 ) { changeTracker . deleteNodes ( sourceFile , oldImportDecls , { trailingTriviaOption : textChanges . TrailingTriviaOption . Include , } , true ) ; } else { const replaceOptions = { leadingTriviaOption : textChanges . LeadingTriviaOption . Exclude , trailingTriviaOption : textChanges . TrailingTriviaOption . Include , suffix : getNewLineOrDefaultFromHost ( host , formatContext . options ) , } ; changeTracker . replaceNodeWithNodes ( sourceFile , oldImportDecls [ 0 ] , newImportDecls , replaceOptions ) ; const hasTrailingComment = changeTracker . nodeHasTrailingComment ( sourceFile , oldImportDecls [ 0 ] , replaceOptions ) ; changeTracker . deleteNodes ( sourceFile , oldImportDecls . slice ( 1 ) , { trailingTriviaOption : textChanges . TrailingTriviaOption . Include , } , hasTrailingComment ) ; } } } function removeUnusedImports ( oldImports , sourceFile , program , skipDestructiveCodeActions ) { if ( skipDestructiveCodeActions ) { return oldImports ; } const typeChecker = program . getTypeChecker ( ) ; const jsxNamespace = typeChecker . getJsxNamespace ( sourceFile ) ; const jsxFragmentFactory = typeChecker . getJsxFragmentFactory ( sourceFile ) ; const jsxElementsPresent = ! ! ( sourceFile . transformFlags & TransformFlags . ContainsJsx ) ; const usedImports = [ ] ; for ( const importDecl of oldImports ) { const { importClause , moduleSpecifier } = importDecl ; if ( ! importClause ) { usedImports . push ( importDecl ) ; continue ; } let { name , namedBindings } = importClause ; if ( name && ! isDeclarationUsed ( name ) ) { name = undefined ; } if ( namedBindings ) { if ( isNamespaceImport ( namedBindings ) ) { if ( ! isDeclarationUsed ( namedBindings . name ) ) { namedBindings = undefined ; } } else { const newElements = namedBindings . elements . filter ( e => isDeclarationUsed ( e . name ) ) ; if ( newElements . length < namedBindings . elements . length ) { namedBindings = newElements . length ? factory . updateNamedImports ( namedBindings , newElements ) : undefined ; } } } if ( name || namedBindings ) { usedImports . push ( updateImportDeclarationAndClause ( importDecl , name , namedBindings ) ) ; } else if ( hasModuleDeclarationMatchingSpecifier ( sourceFile , moduleSpecifier ) ) { if ( sourceFile . isDeclarationFile ) { usedImports . push ( factory . createImportDeclaration ( importDecl . decorators , importDecl . modifiers , undefined , moduleSpecifier , undefined ) ) ; } else { usedImports . push ( importDecl ) ; } } } return usedImports ; function isDeclarationUsed ( identifier ) { return jsxElementsPresent && ( identifier . text === jsxNamespace || jsxFragmentFactory && identifier . text === jsxFragmentFactory ) || FindAllReferences . Core . isSymbolReferencedInFile ( identifier , typeChecker , sourceFile ) ; } } function hasModuleDeclarationMatchingSpecifier ( sourceFile , moduleSpecifier ) { const moduleSpecifierText = isStringLiteral ( moduleSpecifier ) && moduleSpecifier . text ; return isString ( moduleSpecifierText ) && some ( sourceFile . moduleAugmentations , moduleName => isStringLiteral ( moduleName ) && moduleName . text === moduleSpecifierText ) ; } function getExternalModuleName ( specifier ) { return specifier !== undefined && isStringLiteralLike ( specifier ) ? specifier . text : undefined ; } param importGroup export function coalesceImports ( importGroup ) { if ( importGroup . length === 0 ) { return importGroup ; } const { importWithoutClause , typeOnlyImports , regularImports } = getCategorizedImports ( importGroup ) ; const coalescedImports = [ ] ; if ( importWithoutClause ) { coalescedImports . push ( importWithoutClause ) ; } for ( const group of [ regularImports , typeOnlyImports ] ) { const isTypeOnly = group === typeOnlyImports ; const { defaultImports , namespaceImports , namedImports } = group ; if ( ! isTypeOnly && defaultImports . length === 1 && namespaceImports . length === 1 && namedImports . length === 0 ) { const defaultImport = defaultImports [ 0 ] ; coalescedImports . push ( updateImportDeclarationAndClause ( defaultImport , defaultImport . importClause ! . name , namespaceImports [ 0 ] . importClause ! . namedBindings ) ) ; continue ; } const sortedNamespaceImports = stableSort ( namespaceImports , ( i1 , i2 ) => compareIdentifiers ( ( i1 . importClause ! . namedBindings as NamespaceImport ) . name , ( i2 . importClause ! . namedBindings as NamespaceImport ) . name ) ) ; for ( const namespaceImport of sortedNamespaceImports ) { coalescedImports . push ( updateImportDeclarationAndClause ( namespaceImport , undefined , namespaceImport . importClause ! . namedBindings ) ) ; } if ( defaultImports . length === 0 && namedImports . length === 0 ) { continue ; } let newDefaultImport ; const newImportSpecifiers = [ ] ; if ( defaultImports . length === 1 ) { newDefaultImport = defaultImports [ 0 ] . importClause ! . name ; } else { for ( const defaultImport of defaultImports ) { newImportSpecifiers . push ( factory . createImportSpecifier ( false , factory . createIdentifier ( "default" ) , defaultImport . importClause ! . name ! ) ) ; } } newImportSpecifiers . push ( ... flatMap ( namedImports , i => ( i . importClause ! . namedBindings as NamedImports ) . elements ) ) ; const sortedImportSpecifiers = sortSpecifiers ( newImportSpecifiers ) ; const importDecl = defaultImports . length > 0 ? defaultImports [ 0 ] : namedImports [ 0 ] ; const newNamedImports = sortedImportSpecifiers . length === 0 ? newDefaultImport ? undefined : factory . createNamedImports ( emptyArray ) : namedImports . length === 0 ? factory . createNamedImports ( sortedImportSpecifiers ) : factory . updateNamedImports ( namedImports [ 0 ] . importClause ! . namedBindings as NamedImports , sortedImportSpecifiers ) ; if ( isTypeOnly && newDefaultImport && newNamedImports ) { coalescedImports . push ( updateImportDeclarationAndClause ( importDecl , newDefaultImport , undefined ) ) ; coalescedImports . push ( updateImportDeclarationAndClause ( namedImports [ 0 ] ?? importDecl , undefined , newNamedImports ) ) ; } else { coalescedImports . push ( updateImportDeclarationAndClause ( importDecl , newDefaultImport , newNamedImports ) ) ; } } return coalescedImports ; } interface ImportGroup { defaultImports : ImportDeclaration [ ] ; namespaceImports : ImportDeclaration [ ] ; namedImports : ImportDeclaration [ ] ; } function getCategorizedImports ( importGroup ) { let importWithoutClause ; const typeOnlyImports = { defaultImports : [ ] , namespaceImports : [ ] , namedImports : [ ] } ; const regularImports = { defaultImports : [ ] , namespaceImports : [ ] , namedImports : [ ] } ; for ( const importDeclaration of importGroup ) { if ( importDeclaration . importClause === undefined ) { importWithoutClause = importWithoutClause || importDeclaration ; continue ; } const group = importDeclaration . importClause . isTypeOnly ? typeOnlyImports : regularImports ; const { name , namedBindings } = importDeclaration . importClause ; if ( name ) { group . defaultImports . push ( importDeclaration ) ; } if ( namedBindings ) { if ( isNamespaceImport ( namedBindings ) ) { group . namespaceImports . push ( importDeclaration ) ; } else { group . namedImports . push ( importDeclaration ) ; } } } return { importWithoutClause , typeOnlyImports , regularImports , } ; } param exportGroup export function coalesceExports ( exportGroup ) { if ( exportGroup . length === 0 ) { return exportGroup ; } const { exportWithoutClause , namedExports , typeOnlyExports } = getCategorizedExports ( exportGroup ) ; const coalescedExports = [ ] ; if ( exportWithoutClause ) { coalescedExports . push ( exportWithoutClause ) ; } for ( const exportGroup of [ namedExports , typeOnlyExports ] ) { if ( exportGroup . length === 0 ) { continue ; } const newExportSpecifiers = [ ] ; newExportSpecifiers . push ( ... flatMap ( exportGroup , i => i . exportClause && isNamedExports ( i . exportClause ) ? i . exportClause . elements : emptyArray ) ) ; const sortedExportSpecifiers = sortSpecifiers ( newExportSpecifiers ) ; const exportDecl = exportGroup [ 0 ] ; coalescedExports . push ( factory . updateExportDeclaration ( exportDecl , exportDecl . decorators , exportDecl . modifiers , exportDecl . isTypeOnly , exportDecl . exportClause && ( isNamedExports ( exportDecl . exportClause ) ? factory . updateNamedExports ( exportDecl . exportClause , sortedExportSpecifiers ) : factory . updateNamespaceExport ( exportDecl . exportClause , exportDecl . exportClause . name ) ) , exportDecl . moduleSpecifier , exportDecl . assertClause ) ) ; } return coalescedExports ; function getCategorizedExports ( exportGroup ) { let exportWithoutClause ; const namedExports = [ ] ; const typeOnlyExports = [ ] ; for ( const exportDeclaration of exportGroup ) { if ( exportDeclaration . exportClause === undefined ) { exportWithoutClause = exportWithoutClause || exportDeclaration ; } else if ( exportDeclaration . isTypeOnly ) { typeOnlyExports . push ( exportDeclaration ) ; } else { namedExports . push ( exportDeclaration ) ; } } return { exportWithoutClause , namedExports , typeOnlyExports , } ; } } function updateImportDeclarationAndClause ( importDeclaration , name , namedBindings ) { return factory . updateImportDeclaration ( importDeclaration , importDeclaration . decorators , importDeclaration . modifiers , factory . updateImportClause ( importDeclaration . importClause ! , importDeclaration . importClause ! . isTypeOnly , name , namedBindings ) , importDeclaration . moduleSpecifier , importDeclaration . assertClause ) ; } function sortSpecifiers < T extends ImportOrExportSpecifier > ( specifiers ) { return stableSort ( specifiers , compareImportOrExportSpecifiers ) ; } export function compareImportOrExportSpecifiers < T extends ImportOrExportSpecifier > ( s1 , s2 ) { return compareBooleans ( s1 . isTypeOnly , s2 . isTypeOnly ) || compareIdentifiers ( s1 . propertyName || s1 . name , s2 . propertyName || s2 . name ) || compareIdentifiers ( s1 . name , s2 . name ) ; } export function compareModuleSpecifiers ( m1 , m2 ) { const name1 = m1 === undefined ? undefined : getExternalModuleName ( m1 ) ; const name2 = m2 === undefined ? undefined : getExternalModuleName ( m2 ) ; return compareBooleans ( name1 === undefined , name2 === undefined ) || compareBooleans ( isExternalModuleNameRelative ( name1 ! ) , isExternalModuleNameRelative ( name2 ! ) ) || compareStringsCaseInsensitive ( name1 ! , name2 ! ) ; } function compareIdentifiers ( s1 , s2 ) { return compareStringsCaseInsensitive ( s1 . text , s2 . text ) ; } function getModuleSpecifierExpression ( declaration ) { switch ( declaration . kind ) { case SyntaxKind . ImportEqualsDeclaration : return tryCast ( declaration . moduleReference , isExternalModuleReference ) ?. expression ; case SyntaxKind . ImportDeclaration : return declaration . moduleSpecifier ; case SyntaxKind . VariableStatement : return declaration . declarationList . declarations [ 0 ] . initializer . arguments [ 0 ] ; } } export function importsAreSorted ( imports ) { return arrayIsSorted ( imports , compareImportsOrRequireStatements ) ; } export function importSpecifiersAreSorted ( imports ) { return arrayIsSorted ( imports , compareImportOrExportSpecifiers ) ; } export function getImportDeclarationInsertionIndex ( sortedImports , newImport ) { const index = binarySearch ( sortedImports , newImport , identity , compareImportsOrRequireStatements ) ; return index < 0 ? ~ index : index ; } export function getImportSpecifierInsertionIndex ( sortedImports , newImport ) { const index = binarySearch ( sortedImports , newImport , identity , compareImportOrExportSpecifiers ) ; return index < 0 ? ~ index : index ; } export function compareImportsOrRequireStatements ( s1 , s2 ) { return compareModuleSpecifiers ( getModuleSpecifierExpression ( s1 ) , getModuleSpecifierExpression ( s2 ) ) || compareImportKind ( s1 , s2 ) ; } function compareImportKind ( s1 , s2 ) { return compareValues ( getImportKindOrder ( s1 ) , getImportKindOrder ( s2 ) ) ; } function getImportKindOrder ( s1 ) { switch ( s1 . kind ) { case SyntaxKind . ImportDeclaration : if ( ! s1 . importClause ) return 0 ; if ( s1 . importClause . isTypeOnly ) return 1 ; if ( s1 . importClause . namedBindings ?. kind === SyntaxKind . NamespaceImport ) return 2 ; if ( s1 . importClause . name ) return 3 ; return 4 ; case SyntaxKind . ImportEqualsDeclaration : return 5 ; case SyntaxKind . VariableStatement : return 6 ; } } }