@@ -37,33 +37,49 @@ namespace ts.refactor {
37
37
return { renameFilename : undefined , renameLocation : undefined , edits } ;
38
38
}
39
39
40
- function doChange ( changeTracker : textChanges . ChangeTracker , file : SourceFile , info : Info ) {
41
- const nameMap = new Map < string , string > ( ) ;
42
- const bindingElements : BindingElement [ ] = [ ] ;
43
- info . referencedAccessExpression . forEach ( ( [ expr , name ] ) => {
44
- if ( ! nameMap . has ( name ) ) {
45
- const needAlias = info . namesNeedUniqueName . has ( name ) ;
46
- const uniqueName = needAlias ? getUniqueName ( name , file ) : name ;
47
- nameMap . set ( name , uniqueName ) ;
48
- bindingElements . push ( getUniqueDestructionName ( expr , needAlias , uniqueName ) ) ;
40
+ function getUniqueNumericAccessVariable ( name : string | number , file : SourceFile ) {
41
+ const tempName = `index_${ name } ` ;
42
+ return isFileLevelUniqueName ( file , tempName ) ? tempName : getUniqueName ( tempName , file ) ;
43
+ }
44
+
45
+ /**
46
+ * `Dense` means we use array literal pattern to destruction the expression.
47
+ * We allowed index up to 15 to avoid many omit expression.
48
+ */
49
+ function getDenseNumericAccessInfo ( infos : ReferencedAccessInfo [ ] ) : [ max : number , indexSet : Set < number > ] | undefined {
50
+ let min = Infinity ;
51
+ let max = - Infinity ;
52
+ const indexSet = new Set < number > ( ) ;
53
+ for ( const info of infos ) {
54
+ if ( ! info . isNumericAccess ) {
55
+ return undefined ;
49
56
}
50
57
51
- const newName = nameMap . get ( name ) ;
52
- Debug . assertIsDefined ( newName ) ;
58
+ const value = parseInt ( info . name ) ;
59
+ min = Math . min ( min , value ) ;
60
+ max = Math . max ( max , value ) ;
61
+ indexSet . add ( value ) ;
53
62
54
- changeTracker . replaceNode (
55
- file ,
56
- expr ,
57
- factory . createIdentifier ( newName )
58
- ) ;
59
- } ) ;
63
+ if ( isNaN ( min ) || isNaN ( max ) || min < 0 || max < 0 ) {
64
+ return undefined ;
65
+ }
66
+ }
67
+
68
+ if ( max > 15 ) {
69
+ return undefined ;
70
+ }
60
71
72
+ return [ max , indexSet ] ;
73
+ }
74
+
75
+ function doChange ( changeTracker : textChanges . ChangeTracker , file : SourceFile , info : Info ) {
76
+ const bindingPattern = getBindingPattern ( info , file , changeTracker ) ;
61
77
const newBinding = factory . createVariableStatement (
62
78
/* modifiers*/ undefined ,
63
79
factory . createVariableDeclarationList (
64
80
[
65
81
factory . createVariableDeclaration (
66
- factory . createObjectBindingPattern ( bindingElements ) ,
82
+ bindingPattern ,
67
83
/*exclamationToken*/ undefined ,
68
84
/*type*/ undefined ,
69
85
info . replacementExpression
@@ -80,6 +96,80 @@ namespace ts.refactor {
80
96
) ;
81
97
}
82
98
99
+ function getBindingPattern ( info : Info , file : SourceFile , changeTracker : textChanges . ChangeTracker ) : BindingPattern {
100
+ const denseNumericInfo = getDenseNumericAccessInfo ( info . referencedAccessExpression ) ;
101
+ if ( denseNumericInfo ) {
102
+ const [ max , indexSet ] = denseNumericInfo ;
103
+ return getdenseNumericBindingPattern ( info , file , max , indexSet , changeTracker ) ;
104
+ }
105
+ return getObjectBindingPattern ( info , file , changeTracker ) ;
106
+ }
107
+
108
+ function getObjectBindingPattern ( info : Info , file : SourceFile , changeTracker : textChanges . ChangeTracker ) {
109
+ const nameMap = new Map < string , string > ( ) ;
110
+ const bindingElements : BindingElement [ ] = [ ] ;
111
+ info . referencedAccessExpression . forEach ( ( { expression, name, isNumericAccess } ) => {
112
+ if ( ! nameMap . has ( name ) ) {
113
+ const needAlias = isNumericAccess || info . namesNeedUniqueName . has ( name ) ;
114
+ const uniqueName = isNumericAccess ? getUniqueNumericAccessVariable ( name , file ) :
115
+ needAlias ? getUniqueName ( name , file ) : name ;
116
+ nameMap . set ( name , uniqueName ) ;
117
+ bindingElements . push ( getUniqueDestructionName ( expression , needAlias , uniqueName ) ) ;
118
+ }
119
+
120
+ const newName = nameMap . get ( name ) ;
121
+ Debug . assertIsDefined ( newName ) ;
122
+
123
+ changeTracker . replaceNode (
124
+ file ,
125
+ expression ,
126
+ factory . createIdentifier ( newName )
127
+ ) ;
128
+ } ) ;
129
+ return factory . createObjectBindingPattern ( bindingElements ) ;
130
+ }
131
+
132
+ function getdenseNumericBindingPattern ( info : Info , file : SourceFile , max : number , indexSet : Set < number > , changeTracker : textChanges . ChangeTracker ) : ArrayBindingPattern {
133
+ const nameMap = new Map < number , string > ( ) ;
134
+ const bindingElements : ArrayBindingElement [ ] = [ ] ;
135
+
136
+ for ( let i = 0 ; i <= max ; ++ i ) {
137
+ if ( indexSet . has ( i ) ) {
138
+ if ( ! nameMap . has ( i ) ) {
139
+ const name = getUniqueNumericAccessVariable ( i , file ) ;
140
+ nameMap . set ( i , name ) ;
141
+ }
142
+
143
+ const name = nameMap . get ( i ) ;
144
+ Debug . assertIsDefined ( name ) ;
145
+
146
+ bindingElements . push ( factory . createBindingElement (
147
+ /* dotDotDotToken*/ undefined ,
148
+ /*propertyName*/ undefined ,
149
+ factory . createIdentifier ( name )
150
+ ) ) ;
151
+ }
152
+ else {
153
+ bindingElements . push ( factory . createOmittedExpression ( ) ) ;
154
+ }
155
+ }
156
+
157
+ info . referencedAccessExpression . forEach ( ( { expression, name } ) => {
158
+ const index = parseInt ( name ) ;
159
+
160
+ const newName = nameMap . get ( index ) ;
161
+ Debug . assertIsDefined ( newName ) ;
162
+
163
+ changeTracker . replaceNode (
164
+ file ,
165
+ expression ,
166
+ factory . createIdentifier ( newName )
167
+ ) ;
168
+ } ) ;
169
+
170
+ return factory . createArrayBindingPattern ( bindingElements ) ;
171
+ }
172
+
83
173
function getUniqueDestructionName ( expr : AccessExpression , needAlias : boolean , newName : string ) {
84
174
if ( isPropertyAccessExpression ( expr ) ) {
85
175
const bindingName = cast ( expr . name , isIdentifier ) ;
@@ -99,9 +189,15 @@ namespace ts.refactor {
99
189
}
100
190
}
101
191
192
+ interface ReferencedAccessInfo {
193
+ expression : AccessExpression
194
+ name : string ,
195
+ isNumericAccess : boolean
196
+ }
197
+
102
198
interface Info {
103
199
replacementExpression : Expression ,
104
- referencedAccessExpression : [ AccessExpression , string ] [ ]
200
+ referencedAccessExpression : ReferencedAccessInfo [ ]
105
201
firstReferenced : Expression
106
202
firstReferencedStatement : Statement
107
203
namesNeedUniqueName : Set < string >
@@ -122,7 +218,7 @@ namespace ts.refactor {
122
218
const references = FindAllReferences . getReferenceEntriesForNode ( - 1 , node , program , [ file ] , cancellationToken ) ;
123
219
let firstReferenced : Expression | undefined ;
124
220
let firstReferencedStatement : Statement | undefined ;
125
- const referencedAccessExpression : [ AccessExpression , string ] [ ] = [ ] ;
221
+ const referencedAccessExpression : ReferencedAccessInfo [ ] = [ ] ;
126
222
const allReferencedAcccessExpression : AccessExpression [ ] = [ ] ;
127
223
const container = isParameter ( symbol . valueDeclaration ) ? symbol . valueDeclaration : findAncestor ( symbol . valueDeclaration , or ( isStatement , isSourceFile ) ) ;
128
224
Debug . assertIsDefined ( container ) ;
@@ -150,23 +246,32 @@ namespace ts.refactor {
150
246
}
151
247
152
248
if ( isElementAccessExpression ( accessExpression ) ) {
153
- if ( ! isStringLiteralLike ( accessExpression . argumentExpression ) ) {
249
+ let isNumericAccess = false ;
250
+ if ( ! isStringLiteralLike ( accessExpression . argumentExpression ) && ! isNumericLiteral ( accessExpression . argumentExpression ) ) {
154
251
return ;
155
252
}
156
- if ( ! isIdentifierText ( accessExpression . argumentExpression . text , compilerOptions . target , compilerOptions . jsx ? LanguageVariant . JSX : LanguageVariant . Standard ) ) {
253
+ if ( isNumericLiteral ( accessExpression . argumentExpression ) ) {
254
+ isNumericAccess = true ;
255
+ }
256
+ else if ( ! isIdentifierText ( accessExpression . argumentExpression . text , compilerOptions . target , compilerOptions . jsx ? LanguageVariant . JSX : LanguageVariant . Standard ) ) {
157
257
return ;
158
258
}
159
259
160
- referencedAccessExpression . push ( [ accessExpression , accessExpression . argumentExpression . text ] ) ;
161
- return ;
162
- }
163
- if ( ! isIdentifierText ( accessExpression . name . text , compilerOptions . target , compilerOptions . jsx ? LanguageVariant . JSX : LanguageVariant . Standard ) ) {
260
+ referencedAccessExpression . push ( {
261
+ expression : accessExpression ,
262
+ name : accessExpression . argumentExpression . text ,
263
+ isNumericAccess
264
+ } ) ;
164
265
return ;
165
266
}
166
267
167
- referencedAccessExpression . push ( [ accessExpression , accessExpression . name . text ] ) ;
268
+ referencedAccessExpression . push ( {
269
+ expression : accessExpression ,
270
+ name : accessExpression . name . text ,
271
+ isNumericAccess : false
272
+ } ) ;
168
273
} ) ;
169
- if ( ! firstReferenced || ! firstReferencedStatement || ! referencedAccessExpression . length || ! some ( referencedAccessExpression , ( [ r ] ) => rangeContainsRange ( r , current ) ) ) return undefined ;
274
+ if ( ! firstReferenced || ! firstReferencedStatement || ! referencedAccessExpression . length || ! some ( referencedAccessExpression , ( { expression } ) => rangeContainsRange ( expression , current ) ) ) return undefined ;
170
275
171
276
let hasUnconvertableReference = false ;
172
277
const namesNeedUniqueName = new Set < string > ( ) ;
@@ -175,7 +280,7 @@ namespace ts.refactor {
175
280
const referenceType = checker . getTypeAtLocation ( expr ) ;
176
281
if ( referenceType !== type ) {
177
282
const propName = isElementAccessExpression ( expr ) ?
178
- cast ( expr . argumentExpression , isStringLiteralLike ) . text :
283
+ cast ( expr . argumentExpression , isStringOrNumericLiteralLike ) . text :
179
284
checker . getSymbolAtLocation ( expr ) ?. name ;
180
285
181
286
const accessType = checker . getTypeAtLocation ( expr ) ;
@@ -190,7 +295,7 @@ namespace ts.refactor {
190
295
if ( hasUnconvertableReference ) return undefined ;
191
296
192
297
if ( resolveUniqueName ) {
193
- forEach ( referencedAccessExpression , ( [ , name ] ) => {
298
+ forEach ( referencedAccessExpression , ( { name } ) => {
194
299
const symbol = checker . resolveName ( name , /*location*/ undefined , SymbolFlags . Value , /*excludeGlobals*/ false ) ;
195
300
if ( symbol ) {
196
301
namesNeedUniqueName . add ( name ) ;
0 commit comments