namespace ts . codefix { registerCodeFix ( { errorCodes : [ Diagnostics . File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module . code ] , getCodeActions ( context ) { const { sourceFile , program , preferences } = context ; const changes = textChanges . ChangeTracker . with ( context , changes => { const moduleExportsChangedToDefault = convertFileToEsModule ( sourceFile , program . getTypeChecker ( ) , changes , getEmitScriptTarget ( program . getCompilerOptions ( ) ) , getQuotePreference ( sourceFile , preferences ) ) ; if ( moduleExportsChangedToDefault ) { for ( const importingFile of program . getSourceFiles ( ) ) { fixImportOfModuleExports ( importingFile , sourceFile , changes , getQuotePreference ( importingFile , preferences ) ) ; } } } ) ; return [ createCodeFixActionWithoutFixAll ( "convertToEsModule" , changes , Diagnostics . Convert_to_ES_module ) ] ; } , } ) ; function fixImportOfModuleExports ( importingFile , exportingFile , changes , quotePreference ) { for ( const moduleSpecifier of importingFile . imports ) { const imported = getResolvedModule ( importingFile , moduleSpecifier . text , getModeForUsageLocation ( importingFile , moduleSpecifier ) ) ; if ( ! imported || imported . resolvedFileName !== exportingFile . fileName ) { continue ; } const importNode = importFromModuleSpecifier ( moduleSpecifier ) ; switch ( importNode . kind ) { case SyntaxKind . ImportEqualsDeclaration : changes . replaceNode ( importingFile , importNode , makeImport ( importNode . name , undefined , moduleSpecifier , quotePreference ) ) ; break ; case SyntaxKind . CallExpression : if ( isRequireCall ( importNode , false ) ) { changes . replaceNode ( importingFile , importNode , factory . createPropertyAccessExpression ( getSynthesizedDeepClone ( importNode ) , "default" ) ) ; } break ; } } } returns function convertFileToEsModule ( sourceFile , checker , changes , target , quotePreference ) { const identifiers = { original : collectFreeIdentifiers ( sourceFile ) , additional : new Set ( ) } ; const exports = collectExportRenames ( sourceFile , checker , identifiers ) ; convertExportsAccesses ( sourceFile , exports , changes ) ; let moduleExportsChangedToDefault = false ; let useSitesToUnqualify ; for ( const statement of filter ( sourceFile . statements , isVariableStatement ) ) { const newUseSites = convertVariableStatement ( sourceFile , statement , changes , checker , identifiers , target , quotePreference ) ; if ( newUseSites ) { copyEntries ( newUseSites , useSitesToUnqualify ??= new Map ( ) ) ; } } for ( const statement of filter ( sourceFile . statements , s => ! isVariableStatement ( s ) ) ) { const moduleExportsChanged = convertStatement ( sourceFile , statement , checker , changes , identifiers , target , exports , useSitesToUnqualify , quotePreference ) ; moduleExportsChangedToDefault = moduleExportsChangedToDefault || moduleExportsChanged ; } useSitesToUnqualify ?. forEach ( ( replacement , original ) => { changes . replaceNode ( sourceFile , original , replacement ) ; } ) ; return moduleExportsChangedToDefault ; } /**
     * Contains an entry for each renamed export.
     * This is necessary because `exports.x = 0;` does not declare a local variable.
     * Converting this to `export const x = 0;` would declare a local, so we must be careful to avoid shadowing.
     * If there would be shadowing at either the declaration or at any reference to `exports.x` (now just `x`), we must convert to:
     *     const _x = 0;
     *     export { _x as x };
     * This conversion also must place if the exported name is not a valid identifier, e.g. `exports.class = 0;`.
     */ type ExportRenames = ReadonlyESMap < string , string > ; function collectExportRenames ( sourceFile , checker , identifiers ) { const res = new Map < string , string > ( ) ; forEachExportReference ( sourceFile , node => { const { text , originalKeywordKind } = node . name ; if ( ! res . has ( text ) && ( originalKeywordKind !== undefined && isNonContextualKeyword ( originalKeywordKind ) || checker . resolveName ( text , node , SymbolFlags . Value , true ) ) ) { res . set ( text , makeUniqueName ( `_${ text }` , identifiers ) ) ; } } ) ; return res ; } function convertExportsAccesses ( sourceFile , exports , changes ) { forEachExportReference ( sourceFile , ( node , isAssignmentLhs ) => { if ( isAssignmentLhs ) { return ; } const { text } = node . name ; changes . replaceNode ( sourceFile , node , factory . createIdentifier ( exports . get ( text ) || text ) ) ; } ) ; } function forEachExportReference ( sourceFile , cb ) { sourceFile . forEachChild ( function recur ( node ) { if ( isPropertyAccessExpression ( node ) && isExportsOrModuleExportsOrAlias ( sourceFile , node . expression ) && isIdentifier ( node . name ) ) { const { parent } = node ; cb ( node as typeof node & { name : Identifier ; } , isBinaryExpression ( parent ) && parent . left === node && parent . operatorToken . kind === SyntaxKind . EqualsToken ) ; } node . forEachChild ( recur ) ; } ) ; } /** Whether `module.exports =` was changed to `export default` */ type ModuleExportsChanged = boolean ; function convertStatement ( sourceFile , statement , checker , changes , identifiers , target , exports , useSitesToUnqualify , quotePreference ) { switch ( statement . kind ) { case SyntaxKind . VariableStatement : convertVariableStatement ( sourceFile , statement as VariableStatement , changes , checker , identifiers , target , quotePreference ) ; return false ; case SyntaxKind . ExpressionStatement : { const { expression } = statement as ExpressionStatement ; switch ( expression . kind ) { case SyntaxKind . CallExpression : { if ( isRequireCall ( expression , true ) ) { changes . replaceNode ( sourceFile , statement , makeImport ( undefined , undefined , expression . arguments [ 0 ] , quotePreference ) ) ; } return false ; } case SyntaxKind . BinaryExpression : { const { operatorToken } = expression as BinaryExpression ; return operatorToken . kind === SyntaxKind . EqualsToken && convertAssignment ( sourceFile , checker , expression as BinaryExpression , changes , exports , useSitesToUnqualify ) ; } } } default : return false ; } } function convertVariableStatement ( sourceFile , statement , changes , checker , identifiers , target , quotePreference ) { const { declarationList } = statement ; let foundImport = false ; const converted = map ( declarationList . declarations , decl => { const { name , initializer } = decl ; if ( initializer ) { if ( isExportsOrModuleExportsOrAlias ( sourceFile , initializer ) ) { foundImport = true ; return convertedImports ( [ ] ) ; } else if ( isRequireCall ( initializer , true ) ) { foundImport = true ; return convertSingleImport ( name , initializer . arguments [ 0 ] , checker , identifiers , target , quotePreference ) ; } else if ( isPropertyAccessExpression ( initializer ) && isRequireCall ( initializer . expression , true ) ) { foundImport = true ; return convertPropertyAccessImport ( name , initializer . name . text , initializer . expression . arguments [ 0 ] , identifiers , quotePreference ) ; } } return convertedImports ( [ factory . createVariableStatement ( undefined , factory . createVariableDeclarationList ( [ decl ] , declarationList . flags ) ) ] ) ; } ) ; if ( foundImport ) { changes . replaceNodeWithNodes ( sourceFile , statement , flatMap ( converted , c => c . newImports ) ) ; let combinedUseSites ; forEach ( converted , c => { if ( c . useSitesToUnqualify ) { copyEntries ( c . useSitesToUnqualify , combinedUseSites ??= new Map ( ) ) ; } } ) ; return combinedUseSites ; } } /** Converts `const name = require("moduleSpecifier").propertyName` */ function convertPropertyAccessImport ( name , propertyName , moduleSpecifier , identifiers , quotePreference ) { switch ( name . kind ) { case SyntaxKind . ObjectBindingPattern : case SyntaxKind . ArrayBindingPattern : { const tmp = makeUniqueName ( propertyName , identifiers ) ; return convertedImports ( [ makeSingleImport ( tmp , propertyName , moduleSpecifier , quotePreference ) , makeConst ( undefined , name , factory . createIdentifier ( tmp ) ) , ] ) ; } case SyntaxKind . Identifier : return convertedImports ( [ makeSingleImport ( name . text , propertyName , moduleSpecifier , quotePreference ) ] ) ; default : return Debug . assertNever ( name , `Convert to ES module got invalid syntax form ${ ( name as BindingName ) . kind }` ) ; } } function convertAssignment ( sourceFile , checker , assignment , changes , exports , useSitesToUnqualify ) { const { left , right } = assignment ; if ( ! isPropertyAccessExpression ( left ) ) { return false ; } if ( isExportsOrModuleExportsOrAlias ( sourceFile , left ) ) { if ( isExportsOrModuleExportsOrAlias ( sourceFile , right ) ) { changes . delete ( sourceFile , assignment . parent ) ; } else { const replacement = isObjectLiteralExpression ( right ) ? tryChangeModuleExportsObject ( right , useSitesToUnqualify ) : isRequireCall ( right , true ) ? convertReExportAll ( right . arguments [ 0 ] , checker ) : undefined ; if ( replacement ) { changes . replaceNodeWithNodes ( sourceFile , assignment . parent , replacement [ 0 ] ) ; return replacement [ 1 ] ; } else { changes . replaceRangeWithText ( sourceFile , createRange ( left . getStart ( sourceFile ) , right . pos ) , "export default" ) ; return true ; } } } else if ( isExportsOrModuleExportsOrAlias ( sourceFile , left . expression ) ) { convertNamedExport ( sourceFile , assignment as BinaryExpression & { left : PropertyAccessExpression ; } , changes , exports ) ; } return false ; } /**
     * Convert `module.exports = { ... }` to individual exports..
     * We can't always do this if the module has interesting members -- then it will be a default export instead.
     */ function tryChangeModuleExportsObject ( object , useSitesToUnqualify ) { const statements = mapAllOrFail ( object . properties , prop => { switch ( prop . kind ) { case SyntaxKind . GetAccessor : case SyntaxKind . SetAccessor : case SyntaxKind . ShorthandPropertyAssignment : case SyntaxKind . SpreadAssignment : return undefined ; case SyntaxKind . PropertyAssignment : return ! isIdentifier ( prop . name ) ? undefined : convertExportsDotXEquals_replaceNode ( prop . name . text , prop . initializer , useSitesToUnqualify ) ; case SyntaxKind . MethodDeclaration : return ! isIdentifier ( prop . name ) ? undefined : functionExpressionToDeclaration ( prop . name . text , [ factory . createToken ( SyntaxKind . ExportKeyword ) ] , prop , useSitesToUnqualify ) ; default : Debug . assertNever ( prop , `Convert to ES6 got invalid prop kind ${ ( prop as ObjectLiteralElementLike ) . kind }` ) ; } } ) ; return statements && [ statements , false ] ; } function convertNamedExport ( sourceFile , assignment , changes , exports ) { const { text } = assignment . left . name ; const rename = exports . get ( text ) ; if ( rename !== undefined ) { const newNodes = [ makeConst ( undefined , rename , assignment . right ) , makeExportDeclaration ( [ factory . createExportSpecifier ( false , rename , text ) ] ) , ] ; changes . replaceNodeWithNodes ( sourceFile , assignment . parent , newNodes ) ; } else { convertExportsPropertyAssignment ( assignment , sourceFile , changes ) ; } } function convertReExportAll ( reExported , checker ) { const moduleSpecifier = reExported . text ; const moduleSymbol = checker . getSymbolAtLocation ( reExported ) ; const exports = moduleSymbol ? moduleSymbol . exports ! : emptyMap as ReadonlyCollection < __String > ; return exports . has ( InternalSymbolName . ExportEquals ) ? [ [ reExportDefault ( moduleSpecifier ) ] , true ] : ! exports . has ( InternalSymbolName . Default ) ? [ [ reExportStar ( moduleSpecifier ) ] , false ] : exports . size > 1 ? [ [ reExportStar ( moduleSpecifier ) , reExportDefault ( moduleSpecifier ) ] , true ] : [ [ reExportDefault ( moduleSpecifier ) ] , true ] ; } function reExportStar ( moduleSpecifier ) { return makeExportDeclaration ( undefined , moduleSpecifier ) ; } function reExportDefault ( moduleSpecifier ) { return makeExportDeclaration ( [ factory . createExportSpecifier ( false , undefined , "default" ) ] , moduleSpecifier ) ; } function convertExportsPropertyAssignment ( { left , right , parent } , sourceFile , changes ) { const name = left . name . text ; if ( ( isFunctionExpression ( right ) || isArrowFunction ( right ) || isClassExpression ( right ) ) && ( ! right . name || right . name . text === name ) ) { changes . replaceRange ( sourceFile , { pos : left . getStart ( sourceFile ) , end : right . getStart ( sourceFile ) } , factory . createToken ( SyntaxKind . ExportKeyword ) , { suffix : " " } ) ; if ( ! right . name ) changes . insertName ( sourceFile , right , name ) ; const semi = findChildOfKind ( parent , SyntaxKind . SemicolonToken , sourceFile ) ; if ( semi ) changes . delete ( sourceFile , semi ) ; } else { changes . replaceNodeRangeWithNodes ( sourceFile , left . expression , findChildOfKind ( left , SyntaxKind . DotToken , sourceFile ) ! , [ factory . createToken ( SyntaxKind . ExportKeyword ) , factory . createToken ( SyntaxKind . ConstKeyword ) ] , { joiner : " " , suffix : " " } ) ; } } function convertExportsDotXEquals_replaceNode ( name , exported , useSitesToUnqualify ) { const modifiers = [ factory . createToken ( SyntaxKind . ExportKeyword ) ] ; switch ( exported . kind ) { case SyntaxKind . FunctionExpression : { const { name : expressionName } = exported as FunctionExpression ; if ( expressionName && expressionName . text !== name ) { return exportConst ( ) ; } } case SyntaxKind . ArrowFunction : return functionExpressionToDeclaration ( name , modifiers , exported as FunctionExpression | ArrowFunction , useSitesToUnqualify ) ; case SyntaxKind . ClassExpression : return classExpressionToDeclaration ( name , modifiers , exported as ClassExpression , useSitesToUnqualify ) ; default : return exportConst ( ) ; } function exportConst ( ) { return makeConst ( modifiers , factory . createIdentifier ( name ! ) , replaceImportUseSites ( exported , useSitesToUnqualify ) ) ; } } function replaceImportUseSites < T extends Node > ( node , useSitesToUnqualify ) ; function replaceImportUseSites < T extends Node > ( nodes , useSitesToUnqualify ) ; function replaceImportUseSites < T extends Node > ( nodeOrNodes , useSitesToUnqualify ) { if ( ! useSitesToUnqualify || ! some ( arrayFrom ( useSitesToUnqualify . keys ( ) ) , original => rangeContainsRange ( nodeOrNodes , original ) ) ) { return nodeOrNodes ; } return isArray ( nodeOrNodes ) ? getSynthesizedDeepClonesWithReplacements ( nodeOrNodes , true , replaceNode ) : getSynthesizedDeepCloneWithReplacements ( nodeOrNodes , true , replaceNode ) ; function replaceNode ( original ) { if ( original . kind === SyntaxKind . PropertyAccessExpression ) { const replacement = useSitesToUnqualify ! . get ( original ) ; useSitesToUnqualify ! . delete ( original ) ; return replacement ; } } } /**
     * Converts `const <<name>> = require("x");`.
     * Returns nodes that will replace the variable declaration for the commonjs import.
     * May also make use `changes` to remove qualifiers at the use sites of imports, to change `mod.x` to `x`.
     */ function convertSingleImport ( name , moduleSpecifier , checker , identifiers , target , quotePreference ) { switch ( name . kind ) { case SyntaxKind . ObjectBindingPattern : { const importSpecifiers = mapAllOrFail ( name . elements , e => e . dotDotDotToken || e . initializer || e . propertyName && ! isIdentifier ( e . propertyName ) || ! isIdentifier ( e . name ) ? undefined : makeImportSpecifier ( e . propertyName && e . propertyName . text , e . name . text ) ) ; if ( importSpecifiers ) { return convertedImports ( [ makeImport ( undefined , importSpecifiers , moduleSpecifier , quotePreference ) ] ) ; } } case SyntaxKind . ArrayBindingPattern : { const tmp = makeUniqueName ( moduleSpecifierToValidIdentifier ( moduleSpecifier . text , target ) , identifiers ) ; return convertedImports ( [ makeImport ( factory . createIdentifier ( tmp ) , undefined , moduleSpecifier , quotePreference ) , makeConst ( undefined , getSynthesizedDeepClone ( name ) , factory . createIdentifier ( tmp ) ) , ] ) ; } case SyntaxKind . Identifier : return convertSingleIdentifierImport ( name , moduleSpecifier , checker , identifiers , quotePreference ) ; default : return Debug . assertNever ( name , `Convert to ES module got invalid name kind ${ ( name as BindingName ) . kind }` ) ; } } /**
     * Convert `import x = require("x").`
     * Also:
     * - Convert `x.default()` to `x()` to handle ES6 default export
     * - Converts uses like `x.y()` to `y()` and uses a named import.
     */ function convertSingleIdentifierImport ( name , moduleSpecifier , checker , identifiers , quotePreference ) { const nameSymbol = checker . getSymbolAtLocation ( name ) ; const namedBindingsNames = new Map < string , string > ( ) ; let needDefaultImport = false ; let useSitesToUnqualify ; for ( const use of identifiers . original . get ( name . text ) ! ) { if ( checker . getSymbolAtLocation ( use ) !== nameSymbol || use === name ) { continue ; } const { parent } = use ; if ( isPropertyAccessExpression ( parent ) ) { const { name : { text : propertyName } } = parent ; if ( propertyName === "default" ) { needDefaultImport = true ; const importDefaultName = use . getText ( ) ; ( useSitesToUnqualify ??= new Map ( ) ) . set ( parent , factory . createIdentifier ( importDefaultName ) ) ; } else { Debug . assert ( parent . expression === use , "Didn't expect expression === use" ) ; let idName = namedBindingsNames . get ( propertyName ) ; if ( idName === undefined ) { idName = makeUniqueName ( propertyName , identifiers ) ; namedBindingsNames . set ( propertyName , idName ) ; } ( useSitesToUnqualify ??= new Map ( ) ) . set ( parent , factory . createIdentifier ( idName ) ) ; } } else { needDefaultImport = true ; } } const namedBindings = namedBindingsNames . size === 0 ? undefined : arrayFrom ( mapIterator ( namedBindingsNames . entries ( ) , ( [ propertyName , idName ] ) => factory . createImportSpecifier ( false , propertyName === idName ? undefined : factory . createIdentifier ( propertyName ) , factory . createIdentifier ( idName ) ) ) ) ; if ( ! namedBindings ) { needDefaultImport = true ; } return convertedImports ( [ makeImport ( needDefaultImport ? getSynthesizedDeepClone ( name ) : undefined , namedBindings , moduleSpecifier , quotePreference ) ] , useSitesToUnqualify ) ; } function makeUniqueName ( name , identifiers ) { while ( identifiers . original . has ( name ) || identifiers . additional . has ( name ) ) { name = `_${ name }` ; } identifiers . additional . add ( name ) ; return name ; } /**
     * Helps us create unique identifiers.
     * `original` refers to the local variable names in the original source file.
     * `additional` is any new unique identifiers we've generated. (e.g., we'll generate `_x`, then `__x`.)
     */ interface Identifiers { readonly original : FreeIdentifiers ; readonly additional : Set < string > ; } type FreeIdentifiers = ReadonlyESMap < string , readonly Identifier [ ] > ; function collectFreeIdentifiers ( file ) { const map = createMultiMap < Identifier > ( ) ; forEachFreeIdentifier ( file , id => map . add ( id . text , id ) ) ; return map ; } /**
     * A free identifier is an identifier that can be accessed through name lookup as a local variable.
     * In the expression `x.y`, `x` is a free identifier, but `y` is not.
     */ function forEachFreeIdentifier ( node , cb ) { if ( isIdentifier ( node ) && isFreeIdentifier ( node ) ) cb ( node ) ; node . forEachChild ( child => forEachFreeIdentifier ( child , cb ) ) ; } function isFreeIdentifier ( node ) { const { parent } = node ; switch ( parent . kind ) { case SyntaxKind . PropertyAccessExpression : return ( parent as PropertyAccessExpression ) . name !== node ; case SyntaxKind . BindingElement : return ( parent as BindingElement ) . propertyName !== node ; case SyntaxKind . ImportSpecifier : return ( parent as ImportSpecifier ) . propertyName !== node ; default : return true ; } } function functionExpressionToDeclaration ( name , additionalModifiers , fn , useSitesToUnqualify ) { return factory . createFunctionDeclaration ( getSynthesizedDeepClones ( fn . decorators ) , concatenate ( additionalModifiers , getSynthesizedDeepClones ( fn . modifiers ) ) , getSynthesizedDeepClone ( fn . asteriskToken ) , name , getSynthesizedDeepClones ( fn . typeParameters ) , getSynthesizedDeepClones ( fn . parameters ) , getSynthesizedDeepClone ( fn . type ) , factory . converters . convertToFunctionBlock ( replaceImportUseSites ( fn . body ! , useSitesToUnqualify ) ) ) ; } function classExpressionToDeclaration ( name , additionalModifiers , cls , useSitesToUnqualify ) { return factory . createClassDeclaration ( getSynthesizedDeepClones ( cls . decorators ) , concatenate ( additionalModifiers , getSynthesizedDeepClones ( cls . modifiers ) ) , name , getSynthesizedDeepClones ( cls . typeParameters ) , getSynthesizedDeepClones ( cls . heritageClauses ) , replaceImportUseSites ( cls . members , useSitesToUnqualify ) ) ; } function makeSingleImport ( localName , propertyName , moduleSpecifier , quotePreference ) { return propertyName === "default" ? makeImport ( factory . createIdentifier ( localName ) , undefined , moduleSpecifier , quotePreference ) : makeImport ( undefined , [ makeImportSpecifier ( propertyName , localName ) ] , moduleSpecifier , quotePreference ) ; } function makeImportSpecifier ( propertyName , name ) { return factory . createImportSpecifier ( false , propertyName !== undefined && propertyName !== name ? factory . createIdentifier ( propertyName ) : undefined , factory . createIdentifier ( name ) ) ; } function makeConst ( modifiers , name , init ) { return factory . createVariableStatement ( modifiers , factory . createVariableDeclarationList ( [ factory . createVariableDeclaration ( name , undefined , undefined , init ) ] , NodeFlags . Const ) ) ; } function makeExportDeclaration ( exportSpecifiers , moduleSpecifier ? ) { return factory . createExportDeclaration ( undefined , undefined , false , exportSpecifiers && factory . createNamedExports ( exportSpecifiers ) , moduleSpecifier === undefined ? undefined : factory . createStringLiteral ( moduleSpecifier ) ) ; } interface ConvertedImports { newImports : readonly Node [ ] ; useSitesToUnqualify ? : ESMap < Node , Node > ; } function convertedImports ( newImports , useSitesToUnqualify ? ) { return { newImports , useSitesToUnqualify } ; } }