namespace ts . refactor { const refactorName = "Move to a new file" ; const description = getLocaleSpecificMessage ( Diagnostics . Move_to_a_new_file ) ; const moveToNewFileAction = { name : refactorName , description , kind : "refactor.move.newFile" , } ; registerRefactor ( refactorName , { kinds : [ moveToNewFileAction . kind ] , getAvailableActions : function getRefactorActionsToMoveToNewFile ( context ) : readonly ApplicableRefactorInfo [ ] { const statements = getStatementsToMove ( context ) ; if ( context . preferences . allowTextChangesInNewFiles && statements ) { return [ { name : refactorName , description , actions : [ moveToNewFileAction ] } ] ; } if ( context . preferences . provideRefactorNotApplicableReason ) { return [ { name : refactorName , description , actions : [ { ... moveToNewFileAction , notApplicableReason : getLocaleSpecificMessage ( Diagnostics . Selection_is_not_a_valid_statement_or_statements ) } ] } ] ; } return emptyArray ; } , getEditsForAction : function getRefactorEditsToMoveToNewFile ( context , actionName ) : RefactorEditInfo { Debug . assert ( actionName === refactorName , "Wrong refactor invoked" ) ; const statements = Debug . checkDefined ( getStatementsToMove ( context ) ) ; const edits = textChanges . ChangeTracker . with ( context , t => doChange ( context . file , context . program , statements , t , context . host , context . preferences ) ) ; return { edits , renameFilename : undefined , renameLocation : undefined } ; } } ) ; interface RangeToMove { readonly toMove : readonly Statement [ ] ; readonly afterLast : Statement | undefined ; } function getRangeToMove ( context ) { const { file } = context ; const range = createTextRangeFromSpan ( getRefactorContextSpan ( context ) ) ; const { statements } = file ; const startNodeIndex = findIndex ( statements , s => s . end > range . pos ) ; if ( startNodeIndex === - 1 ) return undefined ; const startStatement = statements [ startNodeIndex ] ; if ( isNamedDeclaration ( startStatement ) && startStatement . name && rangeContainsRange ( startStatement . name , range ) ) { return { toMove : [ statements [ startNodeIndex ] ] , afterLast : statements [ startNodeIndex + 1 ] } ; } if ( range . pos > startStatement . getStart ( file ) ) return undefined ; const afterEndNodeIndex = findIndex ( statements , s => s . end > range . end , startNodeIndex ) ; if ( afterEndNodeIndex !== - 1 && ( afterEndNodeIndex === 0 || statements [ afterEndNodeIndex ] . getStart ( file ) < range . end ) ) return undefined ; return { toMove : statements . slice ( startNodeIndex , afterEndNodeIndex === - 1 ? statements . length : afterEndNodeIndex ) , afterLast : afterEndNodeIndex === - 1 ? undefined : statements [ afterEndNodeIndex ] , } ; } function doChange ( oldFile , program , toMove , changes , host , preferences ) { const checker = program . getTypeChecker ( ) ; const usage = getUsageInfo ( oldFile , toMove . all , checker ) ; const currentDirectory = getDirectoryPath ( oldFile . fileName ) ; const extension = extensionFromPath ( oldFile . fileName ) ; const newModuleName = makeUniqueModuleName ( getNewModuleName ( usage . movedSymbols ) , extension , currentDirectory , host ) ; const newFileNameWithExtension = newModuleName + extension ; changes . createNewFile ( oldFile , combinePaths ( currentDirectory , newFileNameWithExtension ) , getNewStatementsAndRemoveFromOldFile ( oldFile , usage , changes , toMove , program , newModuleName , preferences ) ) ; addNewFileToTsconfig ( program , changes , oldFile . fileName , newFileNameWithExtension , hostGetCanonicalFileName ( host ) ) ; } interface StatementRange { readonly first : Statement ; readonly afterLast : Statement | undefined ; } interface ToMove { readonly all : readonly Statement [ ] ; readonly ranges : readonly StatementRange [ ] ; } function getStatementsToMove ( context ) { const rangeToMove = getRangeToMove ( context ) ; if ( rangeToMove === undefined ) return undefined ; const all = [ ] ; const ranges = [ ] ; const { toMove , afterLast } = rangeToMove ; getRangesWhere ( toMove , isAllowedStatementToMove , ( start , afterEndIndex ) => { for ( let i = start ; i < afterEndIndex ; i ++ ) all . push ( toMove [ i ] ) ; ranges . push ( { first : toMove [ start ] , afterLast } ) ; } ) ; return all . length === 0 ? undefined : { all , ranges } ; } function isAllowedStatementToMove ( statement ) { return ! isPureImport ( statement ) && ! isPrologueDirective ( statement ) ; ; } function isPureImport ( node ) { switch ( node . kind ) { case SyntaxKind . ImportDeclaration : return true ; case SyntaxKind . ImportEqualsDeclaration : return ! hasSyntacticModifier ( node , ModifierFlags . Export ) ; case SyntaxKind . VariableStatement : return ( node as VariableStatement ) . declarationList . declarations . every ( d => ! ! d . initializer && isRequireCall ( d . initializer , true ) ) ; default : return false ; } } function addNewFileToTsconfig ( program , changes , oldFileName , newFileNameWithExtension , getCanonicalFileName ) { const cfg = program . getCompilerOptions ( ) . configFile ; if ( ! cfg ) return ; const newFileAbsolutePath = normalizePath ( combinePaths ( oldFileName , ".." , newFileNameWithExtension ) ) ; const newFilePath = getRelativePathFromFile ( cfg . fileName , newFileAbsolutePath , getCanonicalFileName ) ; const cfgObject = cfg . statements [ 0 ] && tryCast ( cfg . statements [ 0 ] . expression , isObjectLiteralExpression ) ; const filesProp = cfgObject && find ( cfgObject . properties , ( prop ) : prop is PropertyAssignment => isPropertyAssignment ( prop ) && isStringLiteral ( prop . name ) && prop . name . text === "files" ) ; if ( filesProp && isArrayLiteralExpression ( filesProp . initializer ) ) { changes . insertNodeInListAfter ( cfg , last ( filesProp . initializer . elements ) , factory . createStringLiteral ( newFilePath ) , filesProp . initializer . elements ) ; } } function getNewStatementsAndRemoveFromOldFile ( oldFile , usage , changes , toMove , program , newModuleName , preferences ) { const checker = program . getTypeChecker ( ) ; const prologueDirectives = takeWhile ( oldFile . statements , isPrologueDirective ) ; if ( ! oldFile . externalModuleIndicator && ! oldFile . commonJsModuleIndicator ) { deleteMovedStatements ( oldFile , toMove . ranges , changes ) ; return [ ... prologueDirectives , ... toMove . all ] ; } const useEsModuleSyntax = ! ! oldFile . externalModuleIndicator ; const quotePreference = getQuotePreference ( oldFile , preferences ) ; const importsFromNewFile = createOldFileImportsFromNewFile ( usage . oldFileImportsFromNewFile , newModuleName , useEsModuleSyntax , quotePreference ) ; if ( importsFromNewFile ) { insertImports ( changes , oldFile , importsFromNewFile , true ) ; } deleteUnusedOldImports ( oldFile , toMove . all , changes , usage . unusedImportsFromOldFile , checker ) ; deleteMovedStatements ( oldFile , toMove . ranges , changes ) ; updateImportsInOtherFiles ( changes , program , oldFile , usage . movedSymbols , newModuleName ) ; const imports = getNewFileImportsAndAddExportInOldFile ( oldFile , usage . oldImportsNeededByNewFile , usage . newFileImportsFromOldFile , changes , checker , useEsModuleSyntax , quotePreference ) ; const body = addExports ( oldFile , toMove . all , usage . oldFileImportsFromNewFile , useEsModuleSyntax ) ; if ( imports . length && body . length ) { return [ ... prologueDirectives , ... imports , SyntaxKind . NewLineTrivia as const , ... body ] ; } return [ ... prologueDirectives , ... imports , ... body , ] ; } function deleteMovedStatements ( sourceFile , moved , changes ) { for ( const { first , afterLast } of moved ) { changes . deleteNodeRangeExcludingEnd ( sourceFile , first , afterLast ) ; } } function deleteUnusedOldImports ( oldFile , toMove , changes , toDelete , checker ) { for ( const statement of oldFile . statements ) { if ( contains ( toMove , statement ) ) continue ; forEachImportInStatement ( statement , i => deleteUnusedImports ( oldFile , i , changes , name => toDelete . has ( checker . getSymbolAtLocation ( name ) ! ) ) ) ; } } function updateImportsInOtherFiles ( changes , program , oldFile , movedSymbols , newModuleName ) { const checker = program . getTypeChecker ( ) ; for ( const sourceFile of program . getSourceFiles ( ) ) { if ( sourceFile === oldFile ) continue ; for ( const statement of sourceFile . statements ) { forEachImportInStatement ( statement , importNode => { if ( checker . getSymbolAtLocation ( moduleSpecifierFromImport ( importNode ) ) !== oldFile . symbol ) return ; const shouldMove = ( name ) : boolean => { const symbol = isBindingElement ( name . parent ) ? getPropertySymbolFromBindingElement ( checker , name . parent as ObjectBindingElementWithoutPropertyName ) : skipAlias ( checker . getSymbolAtLocation ( name ) ! , checker ) ; return ! ! symbol && movedSymbols . has ( symbol ) ; } ; deleteUnusedImports ( sourceFile , importNode , changes , shouldMove ) ; const newModuleSpecifier = combinePaths ( getDirectoryPath ( moduleSpecifierFromImport ( importNode ) . text ) , newModuleName ) ; const newImportDeclaration = filterImport ( importNode , factory . createStringLiteral ( newModuleSpecifier ) , shouldMove ) ; if ( newImportDeclaration ) changes . insertNodeAfter ( sourceFile , statement , newImportDeclaration ) ; const ns = getNamespaceLikeImport ( importNode ) ; if ( ns ) updateNamespaceLikeImport ( changes , sourceFile , checker , movedSymbols , newModuleName , newModuleSpecifier , ns , importNode ) ; } ) ; } } } function getNamespaceLikeImport ( node ) { switch ( node . kind ) { case SyntaxKind . ImportDeclaration : return node . importClause && node . importClause . namedBindings && node . importClause . namedBindings . kind === SyntaxKind . NamespaceImport ? node . importClause . namedBindings . name : undefined ; case SyntaxKind . ImportEqualsDeclaration : return node . name ; case SyntaxKind . VariableDeclaration : return tryCast ( node . name , isIdentifier ) ; default : return Debug . assertNever ( node , `Unexpected node kind ${ ( node as SupportedImport ) . kind }` ) ; } } function updateNamespaceLikeImport ( changes , sourceFile , checker , movedSymbols , newModuleName , newModuleSpecifier , oldImportId , oldImportNode ) { const preferredNewNamespaceName = codefix . moduleSpecifierToValidIdentifier ( newModuleName , ScriptTarget . ESNext ) ; let needUniqueName = false ; const toChange = [ ] ; FindAllReferences . Core . eachSymbolReferenceInFile ( oldImportId , checker , sourceFile , ref => { if ( ! isPropertyAccessExpression ( ref . parent ) ) return ; needUniqueName = needUniqueName || ! ! checker . resolveName ( preferredNewNamespaceName , ref , SymbolFlags . All , true ) ; if ( movedSymbols . has ( checker . getSymbolAtLocation ( ref . parent . name ) ! ) ) { toChange . push ( ref ) ; } } ) ; if ( toChange . length ) { const newNamespaceName = needUniqueName ? getUniqueName ( preferredNewNamespaceName , sourceFile ) : preferredNewNamespaceName ; for ( const ref of toChange ) { changes . replaceNode ( sourceFile , ref , factory . createIdentifier ( newNamespaceName ) ) ; } changes . insertNodeAfter ( sourceFile , oldImportNode , updateNamespaceLikeImportNode ( oldImportNode , newModuleName , newModuleSpecifier ) ) ; } } function updateNamespaceLikeImportNode ( node , newNamespaceName , newModuleSpecifier ) { const newNamespaceId = factory . createIdentifier ( newNamespaceName ) ; const newModuleString = factory . createStringLiteral ( newModuleSpecifier ) ; switch ( node . kind ) { case SyntaxKind . ImportDeclaration : return factory . createImportDeclaration ( undefined , undefined , factory . createImportClause ( false , undefined , factory . createNamespaceImport ( newNamespaceId ) ) , newModuleString , undefined ) ; case SyntaxKind . ImportEqualsDeclaration : return factory . createImportEqualsDeclaration ( undefined , undefined , false , newNamespaceId , factory . createExternalModuleReference ( newModuleString ) ) ; case SyntaxKind . VariableDeclaration : return factory . createVariableDeclaration ( newNamespaceId , undefined , undefined , createRequireCall ( newModuleString ) ) ; default : return Debug . assertNever ( node , `Unexpected node kind ${ ( node as SupportedImport ) . kind }` ) ; } } function moduleSpecifierFromImport ( i ) { return ( i . kind === SyntaxKind . ImportDeclaration ? i . moduleSpecifier : i . kind === SyntaxKind . ImportEqualsDeclaration ? i . moduleReference . expression : i . initializer . arguments [ 0 ] ) ; } function forEachImportInStatement ( statement , cb ) { if ( isImportDeclaration ( statement ) ) { if ( isStringLiteral ( statement . moduleSpecifier ) ) cb ( statement as SupportedImport ) ; } else if ( isImportEqualsDeclaration ( statement ) ) { if ( isExternalModuleReference ( statement . moduleReference ) && isStringLiteralLike ( statement . moduleReference . expression ) ) { cb ( statement as SupportedImport ) ; } } else if ( isVariableStatement ( statement ) ) { for ( const decl of statement . declarationList . declarations ) { if ( decl . initializer && isRequireCall ( decl . initializer , true ) ) { cb ( decl as SupportedImport ) ; } } } } type SupportedImport = ImportDeclaration & { moduleSpecifier : StringLiteralLike ; } | ImportEqualsDeclaration & { moduleReference : ExternalModuleReference & { expression : StringLiteralLike ; } ; } | VariableDeclaration & { initializer : RequireOrImportCall ; } ; type SupportedImportStatement = ImportDeclaration | ImportEqualsDeclaration | VariableStatement ; function createOldFileImportsFromNewFile ( newFileNeedExport , newFileNameWithExtension , useEs6Imports , quotePreference ) { let defaultImport ; const imports = [ ] ; newFileNeedExport . forEach ( symbol => { if ( symbol . escapedName === InternalSymbolName . Default ) { defaultImport = factory . createIdentifier ( symbolNameNoDefault ( symbol ) ! ) ; } else { imports . push ( symbol . name ) ; } } ) ; return makeImportOrRequire ( defaultImport , imports , newFileNameWithExtension , useEs6Imports , quotePreference ) ; } function makeImportOrRequire ( defaultImport , imports , path , useEs6Imports , quotePreference ) { path = ensurePathIsNonModuleName ( path ) ; if ( useEs6Imports ) { const specifiers = imports . map ( i => factory . createImportSpecifier ( false , undefined , factory . createIdentifier ( i ) ) ) ; return makeImportIfNecessary ( defaultImport , specifiers , path , quotePreference ) ; } else { Debug . assert ( ! defaultImport , "No default import should exist" ) ; const bindingElements = imports . map ( i => factory . createBindingElement ( undefined , undefined , i ) ) ; return bindingElements . length ? makeVariableStatement ( factory . createObjectBindingPattern ( bindingElements ) , undefined , createRequireCall ( factory . createStringLiteral ( path ) ) ) as RequireVariableStatement : undefined ; } } function makeVariableStatement ( name , type , initializer , flags = NodeFlags . Const ) { return factory . createVariableStatement ( undefined , factory . createVariableDeclarationList ( [ factory . createVariableDeclaration ( name , undefined , type , initializer ) ] , flags ) ) ; } function createRequireCall ( moduleSpecifier ) { return factory . createCallExpression ( factory . createIdentifier ( "require" ) , undefined , [ moduleSpecifier ] ) ; } function addExports ( sourceFile , toMove , needExport , useEs6Exports ) { return flatMap ( toMove , statement => { if ( isTopLevelDeclarationStatement ( statement ) && ! isExported ( sourceFile , statement , useEs6Exports ) && forEachTopLevelDeclaration ( statement , d => needExport . has ( Debug . checkDefined ( d . symbol ) ) ) ) { const exports = addExport ( statement , useEs6Exports ) ; if ( exports ) return exports ; } return statement ; } ) ; } function deleteUnusedImports ( sourceFile , importDecl , changes , isUnused ) { switch ( importDecl . kind ) { case SyntaxKind . ImportDeclaration : deleteUnusedImportsInDeclaration ( sourceFile , importDecl , changes , isUnused ) ; break ; case SyntaxKind . ImportEqualsDeclaration : if ( isUnused ( importDecl . name ) ) { changes . delete ( sourceFile , importDecl ) ; } break ; case SyntaxKind . VariableDeclaration : deleteUnusedImportsInVariableDeclaration ( sourceFile , importDecl , changes , isUnused ) ; break ; default : Debug . assertNever ( importDecl , `Unexpected import decl kind ${ ( importDecl as SupportedImport ) . kind }` ) ; } } function deleteUnusedImportsInDeclaration ( sourceFile , importDecl , changes , isUnused ) { if ( ! importDecl . importClause ) return ; const { name , namedBindings } = importDecl . importClause ; const defaultUnused = ! name || isUnused ( name ) ; const namedBindingsUnused = ! namedBindings || ( namedBindings . kind === SyntaxKind . NamespaceImport ? isUnused ( namedBindings . name ) : namedBindings . elements . length !== 0 && namedBindings . elements . every ( e => isUnused ( e . name ) ) ) ; if ( defaultUnused && namedBindingsUnused ) { changes . delete ( sourceFile , importDecl ) ; } else { if ( name && defaultUnused ) { changes . delete ( sourceFile , name ) ; } if ( namedBindings ) { if ( namedBindingsUnused ) { changes . replaceNode ( sourceFile , importDecl . importClause , factory . updateImportClause ( importDecl . importClause , importDecl . importClause . isTypeOnly , name , undefined ) ) ; } else if ( namedBindings . kind === SyntaxKind . NamedImports ) { for ( const element of namedBindings . elements ) { if ( isUnused ( element . name ) ) changes . delete ( sourceFile , element ) ; } } } } } function deleteUnusedImportsInVariableDeclaration ( sourceFile , varDecl , changes , isUnused ) { const { name } = varDecl ; switch ( name . kind ) { case SyntaxKind . Identifier : if ( isUnused ( name ) ) { changes . delete ( sourceFile , name ) ; } break ; case SyntaxKind . ArrayBindingPattern : break ; case SyntaxKind . ObjectBindingPattern : if ( name . elements . every ( e => isIdentifier ( e . name ) && isUnused ( e . name ) ) ) { changes . delete ( sourceFile , isVariableDeclarationList ( varDecl . parent ) && varDecl . parent . declarations . length === 1 ? varDecl . parent . parent : varDecl ) ; } else { for ( const element of name . elements ) { if ( isIdentifier ( element . name ) && isUnused ( element . name ) ) { changes . delete ( sourceFile , element . name ) ; } } } break ; } } function getNewFileImportsAndAddExportInOldFile ( oldFile , importsToCopy , newFileImportsFromOldFile , changes , checker , useEsModuleSyntax , quotePreference ) { const copiedOldImports = [ ] ; for ( const oldStatement of oldFile . statements ) { forEachImportInStatement ( oldStatement , i => { append ( copiedOldImports , filterImport ( i , moduleSpecifierFromImport ( i ) , name => importsToCopy . has ( checker . getSymbolAtLocation ( name ) ! ) ) ) ; } ) ; } let oldFileDefault ; const oldFileNamedImports = [ ] ; const markSeenTop = nodeSeenTracker ( ) ; newFileImportsFromOldFile . forEach ( symbol => { if ( ! symbol . declarations ) { return ; } for ( const decl of symbol . declarations ) { if ( ! isTopLevelDeclaration ( decl ) ) continue ; const name = nameOfTopLevelDeclaration ( decl ) ; if ( ! name ) continue ; const top = getTopLevelDeclarationStatement ( decl ) ; if ( markSeenTop ( top ) ) { addExportToChanges ( oldFile , top , name , changes , useEsModuleSyntax ) ; } if ( hasSyntacticModifier ( decl , ModifierFlags . Default ) ) { oldFileDefault = name ; } else { oldFileNamedImports . push ( name . text ) ; } } } ) ; append ( copiedOldImports , makeImportOrRequire ( oldFileDefault , oldFileNamedImports , removeFileExtension ( getBaseFileName ( oldFile . fileName ) ) , useEsModuleSyntax , quotePreference ) ) ; return copiedOldImports ; } function makeUniqueModuleName ( moduleName , extension , inDirectory , host ) { let newModuleName = moduleName ; for ( let i = 1 ; ; i ++ ) { const name = combinePaths ( inDirectory , newModuleName + extension ) ; if ( ! host . fileExists ! ( name ) ) return newModuleName ; newModuleName = `${ moduleName }.${ i }` ; } } function getNewModuleName ( movedSymbols ) { return movedSymbols . forEachEntry ( symbolNameNoDefault ) || "newFile" ; } interface UsageInfo { readonly movedSymbols : ReadonlySymbolSet ; readonly newFileImportsFromOldFile : ReadonlySymbolSet ; readonly oldFileImportsFromNewFile : ReadonlySymbolSet ; readonly oldImportsNeededByNewFile : ReadonlySymbolSet ; readonly unusedImportsFromOldFile : ReadonlySymbolSet ; } function getUsageInfo ( oldFile , toMove , checker ) { const movedSymbols = new SymbolSet ( ) ; const oldImportsNeededByNewFile = new SymbolSet ( ) ; const newFileImportsFromOldFile = new SymbolSet ( ) ; const containsJsx = find ( toMove , statement => ! ! ( statement . transformFlags & TransformFlags . ContainsJsx ) ) ; const jsxNamespaceSymbol = getJsxNamespaceSymbol ( containsJsx ) ; if ( jsxNamespaceSymbol ) { oldImportsNeededByNewFile . add ( jsxNamespaceSymbol ) ; } for ( const statement of toMove ) { forEachTopLevelDeclaration ( statement , decl => { movedSymbols . add ( Debug . checkDefined ( isExpressionStatement ( decl ) ? checker . getSymbolAtLocation ( decl . expression . left ) : decl . symbol , "Need a symbol here" ) ) ; } ) ; } for ( const statement of toMove ) { forEachReference ( statement , checker , symbol => { if ( ! symbol . declarations ) return ; for ( const decl of symbol . declarations ) { if ( isInImport ( decl ) ) { oldImportsNeededByNewFile . add ( symbol ) ; } else if ( isTopLevelDeclaration ( decl ) && sourceFileOfTopLevelDeclaration ( decl ) === oldFile && ! movedSymbols . has ( symbol ) ) { newFileImportsFromOldFile . add ( symbol ) ; } } } ) ; } const unusedImportsFromOldFile = oldImportsNeededByNewFile . clone ( ) ; const oldFileImportsFromNewFile = new SymbolSet ( ) ; for ( const statement of oldFile . statements ) { if ( contains ( toMove , statement ) ) continue ; if ( jsxNamespaceSymbol && ! ! ( statement . transformFlags & TransformFlags . ContainsJsx ) ) { unusedImportsFromOldFile . delete ( jsxNamespaceSymbol ) ; } forEachReference ( statement , checker , symbol => { if ( movedSymbols . has ( symbol ) ) oldFileImportsFromNewFile . add ( symbol ) ; unusedImportsFromOldFile . delete ( symbol ) ; } ) ; } return { movedSymbols , newFileImportsFromOldFile , oldFileImportsFromNewFile , oldImportsNeededByNewFile , unusedImportsFromOldFile } ; function getJsxNamespaceSymbol ( containsJsx ) { if ( containsJsx === undefined ) { return undefined ; } const jsxNamespace = checker . getJsxNamespace ( containsJsx ) ; const jsxNamespaceSymbol = checker . resolveName ( jsxNamespace , containsJsx , SymbolFlags . Namespace , true ) ; return ! ! jsxNamespaceSymbol && some ( jsxNamespaceSymbol . declarations , isInImport ) ? jsxNamespaceSymbol : undefined ; } } function isInImport ( decl ) { switch ( decl . kind ) { case SyntaxKind . ImportEqualsDeclaration : case SyntaxKind . ImportSpecifier : case SyntaxKind . ImportClause : case SyntaxKind . NamespaceImport : return true ; case SyntaxKind . VariableDeclaration : return isVariableDeclarationInImport ( decl as VariableDeclaration ) ; case SyntaxKind . BindingElement : return isVariableDeclaration ( decl . parent . parent ) && isVariableDeclarationInImport ( decl . parent . parent ) ; default : return false ; } } function isVariableDeclarationInImport ( decl ) { return isSourceFile ( decl . parent . parent . parent ) && ! ! decl . initializer && isRequireCall ( decl . initializer , true ) ; } function filterImport ( i , moduleSpecifier , keep ) { switch ( i . kind ) { case SyntaxKind . ImportDeclaration : { const clause = i . importClause ; if ( ! clause ) return undefined ; const defaultImport = clause . name && keep ( clause . name ) ? clause . name : undefined ; const namedBindings = clause . namedBindings && filterNamedBindings ( clause . namedBindings , keep ) ; return defaultImport || namedBindings ? factory . createImportDeclaration ( undefined , undefined , factory . createImportClause ( false , defaultImport , namedBindings ) , moduleSpecifier , undefined ) : undefined ; } case SyntaxKind . ImportEqualsDeclaration : return keep ( i . name ) ? i : undefined ; case SyntaxKind . VariableDeclaration : { const name = filterBindingName ( i . name , keep ) ; return name ? makeVariableStatement ( name , i . type , createRequireCall ( moduleSpecifier ) , i . parent . flags ) : undefined ; } default : return Debug . assertNever ( i , `Unexpected import kind ${ ( i as SupportedImport ) . kind }` ) ; } } function filterNamedBindings ( namedBindings , keep ) { if ( namedBindings . kind === SyntaxKind . NamespaceImport ) { return keep ( namedBindings . name ) ? namedBindings : undefined ; } else { const newElements = namedBindings . elements . filter ( e => keep ( e . name ) ) ; return newElements . length ? factory . createNamedImports ( newElements ) : undefined ; } } function filterBindingName ( name , keep ) { switch ( name . kind ) { case SyntaxKind . Identifier : return keep ( name ) ? name : undefined ; case SyntaxKind . ArrayBindingPattern : return name ; case SyntaxKind . ObjectBindingPattern : { const newElements = name . elements . filter ( prop => prop . propertyName || ! isIdentifier ( prop . name ) || keep ( prop . name ) ) ; return newElements . length ? factory . createObjectBindingPattern ( newElements ) : undefined ; } } } function forEachReference ( node , checker , onReference ) { node . forEachChild ( function cb ( node ) { if ( isIdentifier ( node ) && ! isDeclarationName ( node ) ) { const sym = checker . getSymbolAtLocation ( node ) ; if ( sym ) onReference ( sym ) ; } else { node . forEachChild ( cb ) ; } } ) ; } interface ReadonlySymbolSet { has ( symbol ) : boolean ; forEach ( cb ) : void ; forEachEntry < T > ( cb ) : T | undefined ; } class SymbolSet implements ReadonlySymbolSet { private map = new Map < string , Symbol > ( ) ; add ( symbol ) { this . map . set ( String ( getSymbolId ( symbol ) ) , symbol ) ; } has ( symbol ) { return this . map . has ( String ( getSymbolId ( symbol ) ) ) ; } delete ( symbol ) { this . map . delete ( String ( getSymbolId ( symbol ) ) ) ; } forEach ( cb ) { this . map . forEach ( cb ) ; } forEachEntry < T > ( cb ) { return forEachEntry ( this . map , cb ) ; } clone ( ) { const clone = new SymbolSet ( ) ; copyEntries ( this . map , clone . map ) ; return clone ; } } type TopLevelExpressionStatement = ExpressionStatement & { expression : BinaryExpression & { left : PropertyAccessExpression ; } ; } ; type NonVariableTopLevelDeclaration = FunctionDeclaration | ClassDeclaration | EnumDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ModuleDeclaration | TopLevelExpressionStatement | ImportEqualsDeclaration ; type TopLevelDeclarationStatement = NonVariableTopLevelDeclaration | VariableStatement ; interface TopLevelVariableDeclaration extends VariableDeclaration { parent : VariableDeclarationList & { parent : VariableStatement ; } ; } type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration | BindingElement ; function isTopLevelDeclaration ( node ) { return isNonVariableTopLevelDeclaration ( node ) && isSourceFile ( node . parent ) || isVariableDeclaration ( node ) && isSourceFile ( node . parent . parent . parent ) ; } function sourceFileOfTopLevelDeclaration ( node ) { return isVariableDeclaration ( node ) ? node . parent . parent . parent : node . parent ; } function isTopLevelDeclarationStatement ( node ) { Debug . assert ( isSourceFile ( node . parent ) , "Node parent should be a SourceFile" ) ; return isNonVariableTopLevelDeclaration ( node ) || isVariableStatement ( node ) ; } function isNonVariableTopLevelDeclaration ( node ) { switch ( node . kind ) { case SyntaxKind . FunctionDeclaration : case SyntaxKind . ClassDeclaration : case SyntaxKind . ModuleDeclaration : case SyntaxKind . EnumDeclaration : case SyntaxKind . TypeAliasDeclaration : case SyntaxKind . InterfaceDeclaration : case SyntaxKind . ImportEqualsDeclaration : return true ; default : return false ; } } function forEachTopLevelDeclaration < T > ( statement , cb ) { switch ( statement . kind ) { case SyntaxKind . FunctionDeclaration : case SyntaxKind . ClassDeclaration : case SyntaxKind . ModuleDeclaration : case SyntaxKind . EnumDeclaration : case SyntaxKind . TypeAliasDeclaration : case SyntaxKind . InterfaceDeclaration : case SyntaxKind . ImportEqualsDeclaration : return cb ( statement as FunctionDeclaration | ClassDeclaration | EnumDeclaration | ModuleDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ImportEqualsDeclaration ) ; case SyntaxKind . VariableStatement : return firstDefined ( ( statement as VariableStatement ) . declarationList . declarations , decl => forEachTopLevelDeclarationInBindingName ( decl . name , cb ) ) ; case SyntaxKind . ExpressionStatement : { const { expression } = statement as ExpressionStatement ; return isBinaryExpression ( expression ) && getAssignmentDeclarationKind ( expression ) === AssignmentDeclarationKind . ExportsProperty ? cb ( statement as TopLevelExpressionStatement ) : undefined ; } } } function forEachTopLevelDeclarationInBindingName < T > ( name , cb ) { switch ( name . kind ) { case SyntaxKind . Identifier : return cb ( cast ( name . parent , ( x ) : x is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration ( x ) || isBindingElement ( x ) ) ) ; case SyntaxKind . ArrayBindingPattern : case SyntaxKind . ObjectBindingPattern : return firstDefined ( name . elements , em => isOmittedExpression ( em ) ? undefined : forEachTopLevelDeclarationInBindingName ( em . name , cb ) ) ; default : return Debug . assertNever ( name , `Unexpected name kind ${ ( name as BindingName ) . kind }` ) ; } } function nameOfTopLevelDeclaration ( d ) { return isExpressionStatement ( d ) ? tryCast ( d . expression . left . name , isIdentifier ) : tryCast ( d . name , isIdentifier ) ; } function getTopLevelDeclarationStatement ( d ) { switch ( d . kind ) { case SyntaxKind . VariableDeclaration : return d . parent . parent ; case SyntaxKind . BindingElement : return getTopLevelDeclarationStatement ( cast ( d . parent . parent , ( p ) : p is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration ( p ) || isBindingElement ( p ) ) ) ; default : return d ; } } function addExportToChanges ( sourceFile , decl , name , changes , useEs6Exports ) { if ( isExported ( sourceFile , decl , useEs6Exports , name ) ) return ; if ( useEs6Exports ) { if ( ! isExpressionStatement ( decl ) ) changes . insertExportModifier ( sourceFile , decl ) ; } else { const names = getNamesToExportInCommonJS ( decl ) ; if ( names . length !== 0 ) changes . insertNodesAfter ( sourceFile , decl , names . map ( createExportAssignment ) ) ; } } function isExported ( sourceFile , decl , useEs6Exports , name ? ) { if ( useEs6Exports ) { return ! isExpressionStatement ( decl ) && hasSyntacticModifier ( decl , ModifierFlags . Export ) || ! ! ( name && sourceFile . symbol . exports ?. has ( name . escapedText ) ) ; } return getNamesToExportInCommonJS ( decl ) . some ( name => sourceFile . symbol . exports ! . has ( escapeLeadingUnderscores ( name ) ) ) ; } function addExport ( decl , useEs6Exports ) { return useEs6Exports ? [ addEs6Export ( decl ) ] : addCommonjsExport ( decl ) ; } function addEs6Export ( d ) { const modifiers = concatenate ( [ factory . createModifier ( SyntaxKind . ExportKeyword ) ] , d . modifiers ) ; switch ( d . kind ) { case SyntaxKind . FunctionDeclaration : return factory . updateFunctionDeclaration ( d , d . decorators , modifiers , d . asteriskToken , d . name , d . typeParameters , d . parameters , d . type , d . body ) ; case SyntaxKind . ClassDeclaration : return factory . updateClassDeclaration ( d , d . decorators , modifiers , d . name , d . typeParameters , d . heritageClauses , d . members ) ; case SyntaxKind . VariableStatement : return factory . updateVariableStatement ( d , modifiers , d . declarationList ) ; case SyntaxKind . ModuleDeclaration : return factory . updateModuleDeclaration ( d , d . decorators , modifiers , d . name , d . body ) ; case SyntaxKind . EnumDeclaration : return factory . updateEnumDeclaration ( d , d . decorators , modifiers , d . name , d . members ) ; case SyntaxKind . TypeAliasDeclaration : return factory . updateTypeAliasDeclaration ( d , d . decorators , modifiers , d . name , d . typeParameters , d . type ) ; case SyntaxKind . InterfaceDeclaration : return factory . updateInterfaceDeclaration ( d , d . decorators , modifiers , d . name , d . typeParameters , d . heritageClauses , d . members ) ; case SyntaxKind . ImportEqualsDeclaration : return factory . updateImportEqualsDeclaration ( d , d . decorators , modifiers , d . isTypeOnly , d . name , d . moduleReference ) ; case SyntaxKind . ExpressionStatement : return Debug . fail ( ) ; default : return Debug . assertNever ( d , `Unexpected declaration kind ${ ( d as DeclarationStatement ) . kind }` ) ; } } function addCommonjsExport ( decl ) { return [ decl , ... getNamesToExportInCommonJS ( decl ) . map ( createExportAssignment ) ] ; } function getNamesToExportInCommonJS ( decl ) { switch ( decl . kind ) { case SyntaxKind . FunctionDeclaration : case SyntaxKind . ClassDeclaration : return [ decl . name ! . text ] ; case SyntaxKind . VariableStatement : return mapDefined ( decl . declarationList . declarations , d => isIdentifier ( d . name ) ? d . name . text : undefined ) ; case SyntaxKind . ModuleDeclaration : case SyntaxKind . EnumDeclaration : case SyntaxKind . TypeAliasDeclaration : case SyntaxKind . InterfaceDeclaration : case SyntaxKind . ImportEqualsDeclaration : return emptyArray ; case SyntaxKind . ExpressionStatement : return Debug . fail ( "Can't export an ExpressionStatement" ) ; default : return Debug . assertNever ( decl , `Unexpected decl kind ${ ( decl as TopLevelDeclarationStatement ) . kind }` ) ; } } /** Creates `exports.x = x;` */ function createExportAssignment ( name ) { return factory . createExpressionStatement ( factory . createBinaryExpression ( factory . createPropertyAccessExpression ( factory . createIdentifier ( "exports" ) , factory . createIdentifier ( name ) ) , SyntaxKind . EqualsToken , factory . createIdentifier ( name ) ) ) ; } }