namespace ts . refactor { const refactorName = "Convert export" ; const defaultToNamedAction = { name : "Convert default export to named export" , description : Diagnostics . Convert_default_export_to_named_export . message , kind : "refactor.rewrite.export.named" } ; const namedToDefaultAction = { name : "Convert named export to default export" , description : Diagnostics . Convert_named_export_to_default_export . message , kind : "refactor.rewrite.export.default" } ; registerRefactor ( refactorName , { kinds : [ defaultToNamedAction . kind , namedToDefaultAction . kind ] , getAvailableActions : function getRefactorActionsToConvertBetweenNamedAndDefaultExports ( context ) : readonly ApplicableRefactorInfo [ ] { const info = getInfo ( context , context . triggerReason === "invoked" ) ; if ( ! info ) return emptyArray ; if ( ! isRefactorErrorInfo ( info ) ) { const action = info . wasDefault ? defaultToNamedAction : namedToDefaultAction ; return [ { name : refactorName , description : action . description , actions : [ action ] } ] ; } if ( context . preferences . provideRefactorNotApplicableReason ) { return [ { name : refactorName , description : Diagnostics . Convert_default_export_to_named_export . message , actions : [ { ... defaultToNamedAction , notApplicableReason : info . error } , { ... namedToDefaultAction , notApplicableReason : info . error } , ] } ] ; } return emptyArray ; } , getEditsForAction : function getRefactorEditsToConvertBetweenNamedAndDefaultExports ( context , actionName ) : RefactorEditInfo { Debug . assert ( actionName === defaultToNamedAction . name || actionName === namedToDefaultAction . name , "Unexpected action name" ) ; const info = getInfo ( context ) ; Debug . assert ( info && ! isRefactorErrorInfo ( info ) , "Expected applicable refactor info" ) ; const edits = textChanges . ChangeTracker . with ( context , t => doChange ( context . file , context . program , info , t , context . cancellationToken ) ) ; return { edits , renameFilename : undefined , renameLocation : undefined } ; } , } ) ; type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement | ExportAssignment ; interface ExportInfo { readonly exportNode : ExportToConvert ; readonly exportName : Identifier ; readonly wasDefault : boolean ; readonly exportingModuleSymbol : Symbol ; } ; function getInfo ( context , considerPartialSpans = true ) { const { file , program } = context ; const span = getRefactorContextSpan ( context ) ; const token = getTokenAtPosition ( file , span . start ) ; const exportNode = ! ! ( token . parent && getSyntacticModifierFlags ( token . parent ) & ModifierFlags . Export ) && considerPartialSpans ? token . parent : getParentNodeInSpan ( token , file , span ) ; if ( ! exportNode || ( ! isSourceFile ( exportNode . parent ) && ! ( isModuleBlock ( exportNode . parent ) && isAmbientModule ( exportNode . parent . parent ) ) ) ) { return { error : getLocaleSpecificMessage ( Diagnostics . Could_not_find_export_statement ) } ; } const exportingModuleSymbol = isSourceFile ( exportNode . parent ) ? exportNode . parent . symbol : exportNode . parent . parent . symbol ; const flags = getSyntacticModifierFlags ( exportNode ) || ( ( isExportAssignment ( exportNode ) && ! exportNode . isExportEquals ) ? ModifierFlags . ExportDefault : ModifierFlags . None ) ; const wasDefault = ! ! ( flags & ModifierFlags . Default ) ; if ( ! ( flags & ModifierFlags . Export ) || ! wasDefault && exportingModuleSymbol . exports ! . has ( InternalSymbolName . Default ) ) { return { error : getLocaleSpecificMessage ( Diagnostics . This_file_already_has_a_default_export ) } ; } const checker = program . getTypeChecker ( ) ; const noSymbolError = ( id ) => ( isIdentifier ( id ) && checker . getSymbolAtLocation ( id ) ) ? undefined : { error : getLocaleSpecificMessage ( Diagnostics . Can_only_convert_named_export ) } ; switch ( exportNode . kind ) { case SyntaxKind . FunctionDeclaration : case SyntaxKind . ClassDeclaration : case SyntaxKind . InterfaceDeclaration : case SyntaxKind . EnumDeclaration : case SyntaxKind . TypeAliasDeclaration : case SyntaxKind . ModuleDeclaration : { const node = exportNode as FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | TypeAliasDeclaration | NamespaceDeclaration ; if ( ! node . name ) return undefined ; return noSymbolError ( node . name ) || { exportNode : node , exportName : node . name , wasDefault , exportingModuleSymbol } ; } case SyntaxKind . VariableStatement : { const vs = exportNode as VariableStatement ; if ( ! ( vs . declarationList . flags & NodeFlags . Const ) || vs . declarationList . declarations . length !== 1 ) { return undefined ; } const decl = first ( vs . declarationList . declarations ) ; if ( ! decl . initializer ) return undefined ; Debug . assert ( ! wasDefault , "Can't have a default flag here" ) ; return noSymbolError ( decl . name ) || { exportNode : vs , exportName : decl . name as Identifier , wasDefault , exportingModuleSymbol } ; } case SyntaxKind . ExportAssignment : { const node = exportNode as ExportAssignment ; if ( node . isExportEquals ) return undefined ; return noSymbolError ( node . expression ) || { exportNode : node , exportName : node . expression as Identifier , wasDefault , exportingModuleSymbol } ; } default : return undefined ; } } function doChange ( exportingSourceFile , program , info , changes , cancellationToken ) { changeExport ( exportingSourceFile , info , changes , program . getTypeChecker ( ) ) ; changeImports ( program , info , changes , cancellationToken ) ; } function changeExport ( exportingSourceFile , { wasDefault , exportNode , exportName } , changes , checker ) { if ( wasDefault ) { if ( isExportAssignment ( exportNode ) && ! exportNode . isExportEquals ) { const exp = exportNode . expression as Identifier ; const spec = makeExportSpecifier ( exp . text , exp . text ) ; changes . replaceNode ( exportingSourceFile , exportNode , factory . createExportDeclaration ( undefined , undefined , false , factory . createNamedExports ( [ spec ] ) ) ) ; } else { changes . delete ( exportingSourceFile , Debug . checkDefined ( findModifier ( exportNode , SyntaxKind . DefaultKeyword ) , "Should find a default keyword in modifier list" ) ) ; } } else { const exportKeyword = Debug . checkDefined ( findModifier ( exportNode , SyntaxKind . ExportKeyword ) , "Should find an export keyword in modifier list" ) ; switch ( exportNode . kind ) { case SyntaxKind . FunctionDeclaration : case SyntaxKind . ClassDeclaration : case SyntaxKind . InterfaceDeclaration : changes . insertNodeAfter ( exportingSourceFile , exportKeyword , factory . createToken ( SyntaxKind . DefaultKeyword ) ) ; break ; case SyntaxKind . VariableStatement : const decl = first ( exportNode . declarationList . declarations ) ; if ( ! FindAllReferences . Core . isSymbolReferencedInFile ( exportName , checker , exportingSourceFile ) && ! decl . type ) { changes . replaceNode ( exportingSourceFile , exportNode , factory . createExportDefault ( Debug . checkDefined ( decl . initializer , "Initializer was previously known to be present" ) ) ) ; break ; } case SyntaxKind . EnumDeclaration : case SyntaxKind . TypeAliasDeclaration : case SyntaxKind . ModuleDeclaration : changes . deleteModifier ( exportingSourceFile , exportKeyword ) ; changes . insertNodeAfter ( exportingSourceFile , exportNode , factory . createExportDefault ( factory . createIdentifier ( exportName . text ) ) ) ; break ; default : Debug . fail ( `Unexpected exportNode kind ${ ( exportNode as ExportToConvert ) . kind }` ) ; } } } function changeImports ( program , { wasDefault , exportName , exportingModuleSymbol } , changes , cancellationToken ) { const checker = program . getTypeChecker ( ) ; const exportSymbol = Debug . checkDefined ( checker . getSymbolAtLocation ( exportName ) , "Export name should resolve to a symbol" ) ; FindAllReferences . Core . eachExportReference ( program . getSourceFiles ( ) , checker , cancellationToken , exportSymbol , exportingModuleSymbol , exportName . text , wasDefault , ref => { const importingSourceFile = ref . getSourceFile ( ) ; if ( wasDefault ) { changeDefaultToNamedImport ( importingSourceFile , ref , changes , exportName . text ) ; } else { changeNamedToDefaultImport ( importingSourceFile , ref , changes ) ; } } ) ; } function changeDefaultToNamedImport ( importingSourceFile , ref , changes , exportName ) { const { parent } = ref ; switch ( parent . kind ) { case SyntaxKind . PropertyAccessExpression : changes . replaceNode ( importingSourceFile , ref , factory . createIdentifier ( exportName ) ) ; break ; case SyntaxKind . ImportSpecifier : case SyntaxKind . ExportSpecifier : { const spec = parent as ImportSpecifier | ExportSpecifier ; changes . replaceNode ( importingSourceFile , spec , makeImportSpecifier ( exportName , spec . name . text ) ) ; break ; } case SyntaxKind . ImportClause : { const clause = parent as ImportClause ; Debug . assert ( clause . name === ref , "Import clause name should match provided ref" ) ; const spec = makeImportSpecifier ( exportName , ref . text ) ; const { namedBindings } = clause ; if ( ! namedBindings ) { changes . replaceNode ( importingSourceFile , ref , factory . createNamedImports ( [ spec ] ) ) ; } else if ( namedBindings . kind === SyntaxKind . NamespaceImport ) { changes . deleteRange ( importingSourceFile , { pos : ref . getStart ( importingSourceFile ) , end : namedBindings . getStart ( importingSourceFile ) } ) ; const quotePreference = isStringLiteral ( clause . parent . moduleSpecifier ) ? quotePreferenceFromString ( clause . parent . moduleSpecifier , importingSourceFile ) : QuotePreference . Double ; const newImport = makeImport ( undefined , [ makeImportSpecifier ( exportName , ref . text ) ] , clause . parent . moduleSpecifier , quotePreference ) ; changes . insertNodeAfter ( importingSourceFile , clause . parent , newImport ) ; } else { changes . delete ( importingSourceFile , ref ) ; changes . insertNodeAtEndOfList ( importingSourceFile , namedBindings . elements , spec ) ; } break ; } default : Debug . failBadSyntaxKind ( parent ) ; } } function changeNamedToDefaultImport ( importingSourceFile , ref , changes ) { const parent = ref . parent as PropertyAccessExpression | ImportSpecifier | ExportSpecifier ; switch ( parent . kind ) { case SyntaxKind . PropertyAccessExpression : changes . replaceNode ( importingSourceFile , ref , factory . createIdentifier ( "default" ) ) ; break ; case SyntaxKind . ImportSpecifier : { const defaultImport = factory . createIdentifier ( parent . name . text ) ; if ( parent . parent . elements . length === 1 ) { changes . replaceNode ( importingSourceFile , parent . parent , defaultImport ) ; } else { changes . delete ( importingSourceFile , parent ) ; changes . insertNodeBefore ( importingSourceFile , parent . parent , defaultImport ) ; } break ; } case SyntaxKind . ExportSpecifier : { changes . replaceNode ( importingSourceFile , parent , makeExportSpecifier ( "default" , parent . name . text ) ) ; break ; } default : Debug . assertNever ( parent , `Unexpected parent kind ${ ( parent as Node ) . kind }` ) ; } } function makeImportSpecifier ( propertyName , name ) { return factory . createImportSpecifier ( false , propertyName === name ? undefined : factory . createIdentifier ( propertyName ) , factory . createIdentifier ( name ) ) ; } function makeExportSpecifier ( propertyName , name ) { return factory . createExportSpecifier ( false , propertyName === name ? undefined : factory . createIdentifier ( propertyName ) , factory . createIdentifier ( name ) ) ; } }