@@ -30,58 +30,72 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
30
30
}
31
31
32
32
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 ) ) ;
35
33
const checker = program . getTypeChecker ( ) ;
36
-
37
34
if ( toConvert . kind === SyntaxKind . NamespaceImport ) {
38
- doChangeNamespaceToNamed ( sourceFile , checker , changes , toConvert , usedIdentifiers , getAllowSyntheticDefaultImports ( program . getCompilerOptions ( ) ) ) ;
35
+ doChangeNamespaceToNamed ( sourceFile , checker , changes , toConvert , getAllowSyntheticDefaultImports ( program . getCompilerOptions ( ) ) ) ;
39
36
}
40
37
else {
41
- doChangeNamedToNamespace ( sourceFile , checker , changes , toConvert , usedIdentifiers ) ;
38
+ doChangeNamedToNamespace ( sourceFile , checker , changes , toConvert ) ;
42
39
}
43
40
}
44
41
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 {
48
43
let usedAsNamespaceOrDefault = false ;
49
44
45
+ const nodesToReplace : PropertyAccessExpression [ ] = [ ] ;
46
+ const conflictingNames = createMap < true > ( ) ;
47
+
50
48
FindAllReferences . Core . eachSymbolReferenceInFile ( toConvert . name , checker , sourceFile , id => {
51
49
if ( ! isPropertyAccessExpression ( id . parent ) ) {
52
50
usedAsNamespaceOrDefault = true ;
53
51
}
54
52
else {
55
53
const parent = cast ( id . parent , isPropertyAccessExpression ) ;
56
54
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 ) ;
60
57
}
61
58
Debug . assert ( parent . expression === id ) ;
62
- changes . replaceNode ( sourceFile , parent , createIdentifier ( name ) ) ;
59
+ nodesToReplace . push ( parent ) ;
63
60
}
64
61
} ) ;
65
62
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 [ ] = [ ] ;
67
76
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 ) ) ) ;
69
78
} ) ;
70
79
71
80
const importDecl = toConvert . parent . parent ;
72
81
if ( usedAsNamespaceOrDefault && ! allowSyntheticDefaultImports ) {
73
82
// 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 ) ) ;
75
84
}
76
85
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 ) ) ;
78
87
}
79
88
}
80
89
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 ;
85
99
86
100
const neededNamedImports : ImportSpecifier [ ] = [ ] ;
87
101
@@ -105,7 +119,6 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
105
119
106
120
changes . replaceNode ( sourceFile , toConvert , createNamespaceImport ( createIdentifier ( namespaceImportName ) ) ) ;
107
121
if ( neededNamedImports . length ) {
108
- const importDecl = toConvert . parent . parent ;
109
122
changes . insertNodeAfter ( sourceFile , toConvert . parent . parent , updateImport ( importDecl , /*defaultImportName*/ undefined , neededNamedImports ) ) ;
110
123
}
111
124
}
@@ -114,11 +127,4 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
114
127
return createImportDeclaration ( /*decorators*/ undefined , /*modifiers*/ undefined ,
115
128
createImportClause ( defaultImportName , elements && elements . length ? createNamedImports ( elements ) : undefined ) , old . moduleSpecifier ) ;
116
129
}
117
-
118
- function generateName ( name : string , usedIdentifiers : ReadonlyMap < true > ) : string {
119
- while ( usedIdentifiers . has ( name ) ) {
120
- name = `_${ name } ` ;
121
- }
122
- return name ;
123
- }
124
130
}
0 commit comments