@@ -51,9 +51,11 @@ namespace ts.refactor.convertToOptionalChainExpression {
51
51
error : string ;
52
52
} ;
53
53
54
+ type Occurrence = PropertyAccessExpression | ElementAccessExpression | Identifier ;
55
+
54
56
interface Info {
55
- finalExpression : PropertyAccessExpression | CallExpression ,
56
- occurrences : ( PropertyAccessExpression | Identifier ) [ ] ,
57
+ finalExpression : PropertyAccessExpression | ElementAccessExpression | CallExpression ,
58
+ occurrences : Occurrence [ ] ,
57
59
expression : ValidExpression ,
58
60
} ;
59
61
@@ -107,7 +109,7 @@ namespace ts.refactor.convertToOptionalChainExpression {
107
109
108
110
if ( ! finalExpression || checker . isNullableType ( checker . getTypeAtLocation ( finalExpression ) ) ) {
109
111
return { error : getLocaleSpecificMessage ( Diagnostics . Could_not_find_convertible_access_expression ) } ;
110
- } ;
112
+ }
111
113
112
114
if ( ( isPropertyAccessExpression ( condition ) || isIdentifier ( condition ) )
113
115
&& getMatchingStart ( condition , finalExpression . expression ) ) {
@@ -136,8 +138,8 @@ namespace ts.refactor.convertToOptionalChainExpression {
136
138
/**
137
139
* Gets a list of property accesses that appear in matchTo and occur in sequence in expression.
138
140
*/
139
- function getOccurrencesInExpression ( matchTo : Expression , expression : Expression ) : ( PropertyAccessExpression | Identifier ) [ ] | undefined {
140
- const occurrences : ( PropertyAccessExpression | Identifier ) [ ] = [ ] ;
141
+ function getOccurrencesInExpression ( matchTo : Expression , expression : Expression ) : Occurrence [ ] | undefined {
142
+ const occurrences : Occurrence [ ] = [ ] ;
141
143
while ( isBinaryExpression ( expression ) && expression . operatorToken . kind === SyntaxKind . AmpersandAmpersandToken ) {
142
144
const match = getMatchingStart ( skipParentheses ( matchTo ) , skipParentheses ( expression . right ) ) ;
143
145
if ( ! match ) {
@@ -157,31 +159,46 @@ namespace ts.refactor.convertToOptionalChainExpression {
157
159
/**
158
160
* Returns subchain if chain begins with subchain syntactically.
159
161
*/
160
- function getMatchingStart ( chain : Expression , subchain : Expression ) : PropertyAccessExpression | Identifier | undefined {
161
- return ( isIdentifier ( subchain ) || isPropertyAccessExpression ( subchain ) ) &&
162
- chainStartsWith ( chain , subchain ) ? subchain : undefined ;
162
+ function getMatchingStart ( chain : Expression , subchain : Expression ) : PropertyAccessExpression | ElementAccessExpression | Identifier | undefined {
163
+ if ( ! isIdentifier ( subchain ) && ! isPropertyAccessExpression ( subchain ) && ! isElementAccessExpression ( subchain ) ) {
164
+ return undefined ;
165
+ }
166
+ return chainStartsWith ( chain , subchain ) ? subchain : undefined ;
163
167
}
164
168
165
169
/**
166
170
* Returns true if chain begins with subchain syntactically.
167
171
*/
168
172
function chainStartsWith ( chain : Node , subchain : Node ) : boolean {
169
173
// skip until we find a matching identifier.
170
- while ( isCallExpression ( chain ) || isPropertyAccessExpression ( chain ) ) {
171
- const subchainName = isPropertyAccessExpression ( subchain ) ? subchain . name . getText ( ) : subchain . getText ( ) ;
172
- if ( isPropertyAccessExpression ( chain ) && chain . name . getText ( ) === subchainName ) break ;
174
+ while ( isCallExpression ( chain ) || isPropertyAccessExpression ( chain ) || isElementAccessExpression ( chain ) ) {
175
+ if ( getTextOfChainNode ( chain ) === getTextOfChainNode ( subchain ) ) break ;
173
176
chain = chain . expression ;
174
177
}
175
- // check that the chains match at each access. Call chains in subchain are not valid.
176
- while ( isPropertyAccessExpression ( chain ) && isPropertyAccessExpression ( subchain ) ) {
177
- if ( chain . name . getText ( ) !== subchain . name . getText ( ) ) return false ;
178
+ // check that the chains match at each access. Call chains in subchain are not valid.
179
+ while ( ( isPropertyAccessExpression ( chain ) && isPropertyAccessExpression ( subchain ) ) ||
180
+ ( isElementAccessExpression ( chain ) && isElementAccessExpression ( subchain ) ) ) {
181
+ if ( getTextOfChainNode ( chain ) !== getTextOfChainNode ( subchain ) ) return false ;
178
182
chain = chain . expression ;
179
183
subchain = subchain . expression ;
180
184
}
181
185
// check if we have reached a final identifier.
182
186
return isIdentifier ( chain ) && isIdentifier ( subchain ) && chain . getText ( ) === subchain . getText ( ) ;
183
187
}
184
188
189
+ function getTextOfChainNode ( node : Node ) : string | undefined {
190
+ if ( isIdentifier ( node ) || isStringOrNumericLiteralLike ( node ) ) {
191
+ return node . getText ( ) ;
192
+ }
193
+ if ( isPropertyAccessExpression ( node ) ) {
194
+ return getTextOfChainNode ( node . name ) ;
195
+ }
196
+ if ( isElementAccessExpression ( node ) ) {
197
+ return getTextOfChainNode ( node . argumentExpression ) ;
198
+ }
199
+ return undefined ;
200
+ }
201
+
185
202
/**
186
203
* Find the least ancestor of the input node that is a valid type for extraction and contains the input span.
187
204
*/
@@ -229,15 +246,15 @@ namespace ts.refactor.convertToOptionalChainExpression {
229
246
* it is followed by a different binary operator.
230
247
* @param node the right child of a binary expression or a call expression.
231
248
*/
232
- function getFinalExpressionInChain ( node : Expression ) : CallExpression | PropertyAccessExpression | undefined {
249
+ function getFinalExpressionInChain ( node : Expression ) : CallExpression | PropertyAccessExpression | ElementAccessExpression | undefined {
233
250
// foo && |foo.bar === 1|; - here the right child of the && binary expression is another binary expression.
234
251
// the rightmost member of the && chain should be the leftmost child of that expression.
235
252
node = skipParentheses ( node ) ;
236
253
if ( isBinaryExpression ( node ) ) {
237
254
return getFinalExpressionInChain ( node . left ) ;
238
255
}
239
256
// foo && |foo.bar()()| - nested calls are treated like further accesses.
240
- else if ( ( isPropertyAccessExpression ( node ) || isCallExpression ( node ) ) && ! isOptionalChain ( node ) ) {
257
+ else if ( ( isPropertyAccessExpression ( node ) || isElementAccessExpression ( node ) || isCallExpression ( node ) ) && ! isOptionalChain ( node ) ) {
241
258
return node ;
242
259
}
243
260
return undefined ;
@@ -246,8 +263,8 @@ namespace ts.refactor.convertToOptionalChainExpression {
246
263
/**
247
264
* Creates an access chain from toConvert with '?.' accesses at expressions appearing in occurrences.
248
265
*/
249
- function convertOccurrences ( checker : TypeChecker , toConvert : Expression , occurrences : ( PropertyAccessExpression | Identifier ) [ ] ) : Expression {
250
- if ( isPropertyAccessExpression ( toConvert ) || isCallExpression ( toConvert ) ) {
266
+ function convertOccurrences ( checker : TypeChecker , toConvert : Expression , occurrences : Occurrence [ ] ) : Expression {
267
+ if ( isPropertyAccessExpression ( toConvert ) || isElementAccessExpression ( toConvert ) || isCallExpression ( toConvert ) ) {
251
268
const chain = convertOccurrences ( checker , toConvert . expression , occurrences ) ;
252
269
const lastOccurrence = occurrences . length > 0 ? occurrences [ occurrences . length - 1 ] : undefined ;
253
270
const isOccurrence = lastOccurrence ?. getText ( ) === toConvert . expression . getText ( ) ;
@@ -262,6 +279,11 @@ namespace ts.refactor.convertToOptionalChainExpression {
262
279
factory . createPropertyAccessChain ( chain , factory . createToken ( SyntaxKind . QuestionDotToken ) , toConvert . name ) :
263
280
factory . createPropertyAccessChain ( chain , toConvert . questionDotToken , toConvert . name ) ;
264
281
}
282
+ else if ( isElementAccessExpression ( toConvert ) ) {
283
+ return isOccurrence ?
284
+ factory . createElementAccessChain ( chain , factory . createToken ( SyntaxKind . QuestionDotToken ) , toConvert . argumentExpression ) :
285
+ factory . createElementAccessChain ( chain , toConvert . questionDotToken , toConvert . argumentExpression ) ;
286
+ }
265
287
}
266
288
return toConvert ;
267
289
}
@@ -270,7 +292,7 @@ namespace ts.refactor.convertToOptionalChainExpression {
270
292
const { finalExpression, occurrences, expression } = info ;
271
293
const firstOccurrence = occurrences [ occurrences . length - 1 ] ;
272
294
const convertedChain = convertOccurrences ( checker , finalExpression , occurrences ) ;
273
- if ( convertedChain && ( isPropertyAccessExpression ( convertedChain ) || isCallExpression ( convertedChain ) ) ) {
295
+ if ( convertedChain && ( isPropertyAccessExpression ( convertedChain ) || isElementAccessExpression ( convertedChain ) || isCallExpression ( convertedChain ) ) ) {
274
296
if ( isBinaryExpression ( expression ) ) {
275
297
changes . replaceNodeRange ( sourceFile , firstOccurrence , finalExpression , convertedChain ) ;
276
298
}
0 commit comments