@@ -30,58 +30,72 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
3030 }
3131
3232 function doChange ( sourceFile : SourceFile , program : Program , changes : textChanges . ChangeTracker , toConvert : NamedImportBindings ) : void {
33- const usedIdentifiers = createMap < true > ( ) ;
34- forEachFreeIdentifier ( sourceFile , id => usedIdentifiers . set ( id . text , true ) ) ;
3533 const checker = program . getTypeChecker ( ) ;
36-
3734 if ( toConvert . kind === SyntaxKind . NamespaceImport ) {
38- doChangeNamespaceToNamed ( sourceFile , checker , changes , toConvert , usedIdentifiers , getAllowSyntheticDefaultImports ( program . getCompilerOptions ( ) ) ) ;
35+ doChangeNamespaceToNamed ( sourceFile , checker , changes , toConvert , getAllowSyntheticDefaultImports ( program . getCompilerOptions ( ) ) ) ;
3936 }
4037 else {
41- doChangeNamedToNamespace ( sourceFile , checker , changes , toConvert , usedIdentifiers ) ;
38+ doChangeNamedToNamespace ( sourceFile , checker , changes , toConvert ) ;
4239 }
4340 }
4441
45- function doChangeNamespaceToNamed ( sourceFile : SourceFile , checker : TypeChecker , changes : textChanges . ChangeTracker , toConvert : NamespaceImport , usedIdentifiers : ReadonlyMap < true > , allowSyntheticDefaultImports : boolean ) : void {
46- // We may need to change `mod.x` to `_x` to avoid a name conflict.
47- const exportNameToImportName = createMap < string > ( ) ;
42+ function doChangeNamespaceToNamed ( sourceFile : SourceFile , checker : TypeChecker , changes : textChanges . ChangeTracker , toConvert : NamespaceImport , allowSyntheticDefaultImports : boolean ) : void {
4843 let usedAsNamespaceOrDefault = false ;
4944
45+ const nodesToReplace : PropertyAccessExpression [ ] = [ ] ;
46+ const conflictingNames = createMap < true > ( ) ;
47+
5048 FindAllReferences . Core . eachSymbolReferenceInFile ( toConvert . name , checker , sourceFile , id => {
5149 if ( ! isPropertyAccessExpression ( id . parent ) ) {
5250 usedAsNamespaceOrDefault = true ;
5351 }
5452 else {
5553 const parent = cast ( id . parent , isPropertyAccessExpression ) ;
5654 const exportName = parent . name . text ;
57- let name = exportNameToImportName . get ( exportName ) ;
58- if ( name === undefined ) {
59- exportNameToImportName . set ( exportName , name = generateName ( exportName , usedIdentifiers ) ) ;
55+ if ( checker . resolveName ( exportName , id , SymbolFlags . All , /*excludeGlobals*/ true ) ) {
56+ conflictingNames . set ( exportName , true ) ;
6057 }
6158 Debug . assert ( parent . expression === id ) ;
62- changes . replaceNode ( sourceFile , parent , createIdentifier ( name ) ) ;
59+ nodesToReplace . push ( parent ) ;
6360 }
6461 } ) ;
6562
66- const elements : ImportSpecifier [ ] = [ ] ;
63+ // We may need to change `mod.x` to `_x` to avoid a name conflict.
64+ const exportNameToImportName = createMap < string > ( ) ;
65+
66+ for ( const propertyAccess of nodesToReplace ) {
67+ const exportName = propertyAccess . name . text ;
68+ let importName = exportNameToImportName . get ( exportName ) ;
69+ if ( importName === undefined ) {
70+ exportNameToImportName . set ( exportName , importName = conflictingNames . has ( exportName ) ? getUniqueName ( exportName , sourceFile ) : exportName ) ;
71+ }
72+ changes . replaceNode ( sourceFile , propertyAccess , createIdentifier ( importName ) ) ;
73+ }
74+
75+ const importSpecifiers : ImportSpecifier [ ] = [ ] ;
6776 exportNameToImportName . forEach ( ( name , propertyName ) => {
68- elements . push ( createImportSpecifier ( name === propertyName ? undefined : createIdentifier ( propertyName ) , createIdentifier ( name ) ) ) ;
77+ importSpecifiers . push ( createImportSpecifier ( name === propertyName ? undefined : createIdentifier ( propertyName ) , createIdentifier ( name ) ) ) ;
6978 } ) ;
7079
7180 const importDecl = toConvert . parent . parent ;
7281 if ( usedAsNamespaceOrDefault && ! allowSyntheticDefaultImports ) {
7382 // Need to leave the namespace import alone
74- changes . insertNodeAfter ( sourceFile , importDecl , updateImport ( importDecl , /*defaultImportName*/ undefined , elements ) ) ;
83+ changes . insertNodeAfter ( sourceFile , importDecl , updateImport ( importDecl , /*defaultImportName*/ undefined , importSpecifiers ) ) ;
7584 }
7685 else {
77- changes . replaceNode ( sourceFile , importDecl , updateImport ( importDecl , usedAsNamespaceOrDefault ? createIdentifier ( toConvert . name . text ) : undefined , elements ) ) ;
86+ changes . replaceNode ( sourceFile , importDecl , updateImport ( importDecl , usedAsNamespaceOrDefault ? createIdentifier ( toConvert . name . text ) : undefined , importSpecifiers ) ) ;
7887 }
7988 }
8089
81- function doChangeNamedToNamespace ( sourceFile : SourceFile , checker : TypeChecker , changes : textChanges . ChangeTracker , toConvert : NamedImports , usedIdentifiers : ReadonlyMap < true > ) : void {
82- const { moduleSpecifier } = toConvert . parent . parent ;
83- // We know the user is using at least ScriptTarget.ES6, and moduleSpecifierToValidIdentifier only cares if we're using ES5+, so just set ScriptTarget.ESNext
84- const namespaceImportName = generateName ( moduleSpecifier && isStringLiteral ( moduleSpecifier ) ? codefix . moduleSpecifierToValidIdentifier ( moduleSpecifier . text , ScriptTarget . ESNext ) : "module" , usedIdentifiers ) ;
90+ function doChangeNamedToNamespace ( sourceFile : SourceFile , checker : TypeChecker , changes : textChanges . ChangeTracker , toConvert : NamedImports ) : void {
91+ const importDecl = toConvert . parent . parent ;
92+ const { moduleSpecifier } = importDecl ;
93+
94+ const preferredName = moduleSpecifier && isStringLiteral ( moduleSpecifier ) ? codefix . moduleSpecifierToValidIdentifier ( moduleSpecifier . text , ScriptTarget . ESNext ) : "module" ;
95+ const namespaceNameConflicts = toConvert . elements . some ( element =>
96+ FindAllReferences . Core . eachSymbolReferenceInFile ( element . name , checker , sourceFile , id =>
97+ ! ! checker . resolveName ( preferredName , id , SymbolFlags . All , /*excludeGlobals*/ true ) ) || false ) ;
98+ const namespaceImportName = namespaceNameConflicts ? getUniqueName ( preferredName , sourceFile ) : preferredName ;
8599
86100 const neededNamedImports : ImportSpecifier [ ] = [ ] ;
87101
@@ -105,7 +119,6 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
105119
106120 changes . replaceNode ( sourceFile , toConvert , createNamespaceImport ( createIdentifier ( namespaceImportName ) ) ) ;
107121 if ( neededNamedImports . length ) {
108- const importDecl = toConvert . parent . parent ;
109122 changes . insertNodeAfter ( sourceFile , toConvert . parent . parent , updateImport ( importDecl , /*defaultImportName*/ undefined , neededNamedImports ) ) ;
110123 }
111124 }
@@ -114,11 +127,4 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
114127 return createImportDeclaration ( /*decorators*/ undefined , /*modifiers*/ undefined ,
115128 createImportClause ( defaultImportName , elements && elements . length ? createNamedImports ( elements ) : undefined ) , old . moduleSpecifier ) ;
116129 }
117-
118- function generateName ( name : string , usedIdentifiers : ReadonlyMap < true > ) : string {
119- while ( usedIdentifiers . has ( name ) ) {
120- name = `_${ name } ` ;
121- }
122- return name ;
123- }
124130}
0 commit comments