@@ -14,6 +14,14 @@ namespace ts.codefix {
14
14
getAllCodeActions : context => codeFixAll ( context , errorCodes , ( changes , err ) => convertToAsyncFunction ( changes , err . file , err . start , context . program . getTypeChecker ( ) , context ) ) ,
15
15
} ) ;
16
16
17
+ type SynthBindingName = SynthBindingPattern | SynthIdentifier ;
18
+
19
+ interface SynthBindingPattern {
20
+ readonly elements : ReadonlyArray < SynthBindingName > ;
21
+ readonly bindingPattern : BindingPattern ;
22
+ readonly types : Type [ ] ;
23
+ }
24
+
17
25
interface SynthIdentifier {
18
26
readonly identifier : Identifier ;
19
27
readonly types : Type [ ] ;
@@ -266,7 +274,7 @@ namespace ts.codefix {
266
274
267
275
// dispatch function to recursively build the refactoring
268
276
// should be kept up to date with isFixablePromiseHandler in suggestionDiagnostics.ts
269
- function transformExpression ( node : Expression , transformer : Transformer , outermostParent : CallExpression , prevArgName ?: SynthIdentifier ) : ReadonlyArray < Statement > {
277
+ function transformExpression ( node : Expression , transformer : Transformer , outermostParent : CallExpression , prevArgName ?: SynthBindingName ) : ReadonlyArray < Statement > {
270
278
if ( ! node ) {
271
279
return emptyArray ;
272
280
}
@@ -277,7 +285,7 @@ namespace ts.codefix {
277
285
if ( isCallExpression ( node ) && hasPropertyAccessExpressionWithName ( node , "then" ) && nodeType && ! ! transformer . checker . getPromisedTypeOfPromise ( nodeType ) ) {
278
286
return transformThen ( node , transformer , outermostParent , prevArgName ) ;
279
287
}
280
- else if ( isCallExpression ( node ) && hasPropertyAccessExpressionWithName ( node , "catch" ) && nodeType && ! ! transformer . checker . getPromisedTypeOfPromise ( nodeType ) ) {
288
+ else if ( isCallExpression ( node ) && hasPropertyAccessExpressionWithName ( node , "catch" ) && nodeType && ! ! transformer . checker . getPromisedTypeOfPromise ( nodeType ) && ( ! prevArgName || "identifier" in prevArgName ) ) {
281
289
return transformCatch ( node , transformer , prevArgName ) ;
282
290
}
283
291
else if ( isPropertyAccessExpression ( node ) ) {
@@ -293,7 +301,7 @@ namespace ts.codefix {
293
301
294
302
function transformCatch ( node : CallExpression , transformer : Transformer , prevArgName ?: SynthIdentifier ) : ReadonlyArray < Statement > {
295
303
const func = node . arguments [ 0 ] ;
296
- const argName = getArgName ( func , transformer ) ;
304
+ const argName = getArgBindingName ( func , transformer ) ;
297
305
const shouldReturn = transformer . setOfExpressionsToReturn . get ( getNodeId ( node ) . toString ( ) ) ;
298
306
299
307
/*
@@ -319,8 +327,9 @@ namespace ts.codefix {
319
327
const tryBlock = createBlock ( transformExpression ( node . expression , transformer , node , prevArgName ) ) ;
320
328
321
329
const transformationBody = getTransformationBody ( func , prevArgName , argName , node , transformer ) ;
322
- const catchArg = argName ? argName . identifier . text : "e" ;
323
- const catchClause = createCatchClause ( catchArg , createBlock ( transformationBody ) ) ;
330
+ const catchArg = argName ? "identifier" in argName ? argName . identifier . text : argName . bindingPattern : "e" ;
331
+ const catchVariableDeclaration = createVariableDeclaration ( catchArg ) ;
332
+ const catchClause = createCatchClause ( catchVariableDeclaration , createBlock ( transformationBody ) ) ;
324
333
325
334
/*
326
335
In order to avoid an implicit any, we will synthesize a type for the declaration using the unions of the types of both paths (try block and catch block)
@@ -338,44 +347,54 @@ namespace ts.codefix {
338
347
return varDeclList ? [ varDeclList , tryStatement ] : [ tryStatement ] ;
339
348
}
340
349
350
+ function getIdentifierTextsFromBindingName ( bindingName : BindingName ) : ReadonlyArray < string > {
351
+ if ( isIdentifier ( bindingName ) ) return [ bindingName . text ] ;
352
+ return flatMap ( bindingName . elements , element => {
353
+ if ( isOmittedExpression ( element ) ) return [ ] ;
354
+ return getIdentifierTextsFromBindingName ( element . name ) ;
355
+ } ) ;
356
+ }
357
+
341
358
function createUniqueSynthName ( prevArgName : SynthIdentifier ) {
342
359
const renamedPrevArg = createOptimisticUniqueName ( prevArgName . identifier . text ) ;
343
360
const newSynthName = { identifier : renamedPrevArg , types : [ ] , numberOfAssignmentsOriginal : 0 } ;
344
361
return newSynthName ;
345
362
}
346
363
347
- function transformThen ( node : CallExpression , transformer : Transformer , outermostParent : CallExpression , prevArgName ?: SynthIdentifier ) : ReadonlyArray < Statement > {
364
+ function transformThen ( node : CallExpression , transformer : Transformer , outermostParent : CallExpression , prevArgName ?: SynthBindingName ) : ReadonlyArray < Statement > {
348
365
const [ res , rej ] = node . arguments ;
349
366
350
367
if ( ! res ) {
351
368
return transformExpression ( node . expression , transformer , outermostParent ) ;
352
369
}
353
370
354
- const argNameRes = getArgName ( res , transformer ) ;
371
+ const argNameRes = getArgBindingName ( res , transformer ) ;
355
372
const transformationBody = getTransformationBody ( res , prevArgName , argNameRes , node , transformer ) ;
356
373
357
374
if ( rej ) {
358
- const argNameRej = getArgName ( rej , transformer ) ;
375
+ const argNameRej = getArgBindingName ( rej , transformer ) ;
359
376
360
377
const tryBlock = createBlock ( transformExpression ( node . expression , transformer , node , argNameRes ) . concat ( transformationBody ) ) ;
361
378
362
379
const transformationBody2 = getTransformationBody ( rej , prevArgName , argNameRej , node , transformer ) ;
363
380
364
- const catchArg = argNameRej ? argNameRej . identifier . text : "e" ;
365
- const catchClause = createCatchClause ( catchArg , createBlock ( transformationBody2 ) ) ;
381
+ const catchArg = argNameRej ? "identifier" in argNameRej ? argNameRej . identifier . text : argNameRej . bindingPattern : "e" ;
382
+ const catchVariableDeclaration = createVariableDeclaration ( catchArg ) ;
383
+ const catchClause = createCatchClause ( catchVariableDeclaration , createBlock ( transformationBody2 ) ) ;
366
384
367
385
return [ createTry ( tryBlock , catchClause , /* finallyBlock */ undefined ) ] ;
368
386
}
369
387
370
388
return transformExpression ( node . expression , transformer , node , argNameRes ) . concat ( transformationBody ) ;
371
389
}
372
390
373
- function getFlagOfIdentifier ( node : Identifier , constIdentifiers : ReadonlyArray < Identifier > ) : NodeFlags {
374
- const inArr : boolean = constIdentifiers . some ( elem => elem . text === node . text ) ;
391
+ function getFlagOfBindingName ( bindingName : SynthBindingName , constIdentifiers : ReadonlyArray < Identifier > ) : NodeFlags {
392
+ const identifiers = getIdentifierTextsFromBindingName ( getNode ( bindingName ) ) ;
393
+ const inArr : boolean = constIdentifiers . some ( elem => contains ( identifiers , elem . text ) ) ;
375
394
return inArr ? NodeFlags . Const : NodeFlags . Let ;
376
395
}
377
396
378
- function transformPromiseCall ( node : Expression , transformer : Transformer , prevArgName ?: SynthIdentifier ) : ReadonlyArray < Statement > {
397
+ function transformPromiseCall ( node : Expression , transformer : Transformer , prevArgName ?: SynthBindingName ) : ReadonlyArray < Statement > {
379
398
const shouldReturn = transformer . setOfExpressionsToReturn . get ( getNodeId ( node ) . toString ( ) ) ;
380
399
// the identifier is empty when the handler (.then()) ignores the argument - In this situation we do not need to save the result of the promise returning call
381
400
const originalNodeParent = node . original ? node . original . parent : node . parent ;
@@ -389,23 +408,23 @@ namespace ts.codefix {
389
408
return [ createReturn ( getSynthesizedDeepClone ( node ) ) ] ;
390
409
}
391
410
392
- function createTransformedStatement ( prevArgName : SynthIdentifier | undefined , rightHandSide : Expression , transformer : Transformer ) : ReadonlyArray < Statement > {
393
- if ( ! prevArgName || prevArgName . identifier . text . length === 0 ) {
411
+ function createTransformedStatement ( prevArgName : SynthBindingName | undefined , rightHandSide : Expression , transformer : Transformer ) : ReadonlyArray < Statement > {
412
+ if ( ! prevArgName || isEmpty ( prevArgName ) ) {
394
413
// if there's no argName to assign to, there still might be side effects
395
414
return [ createStatement ( rightHandSide ) ] ;
396
415
}
397
416
398
- if ( prevArgName . types . length < prevArgName . numberOfAssignmentsOriginal ) {
417
+ if ( "identifier" in prevArgName && prevArgName . types . length < prevArgName . numberOfAssignmentsOriginal ) {
399
418
// if the variable has already been declared, we don't need "let" or "const"
400
419
return [ createStatement ( createAssignment ( getSynthesizedDeepClone ( prevArgName . identifier ) , rightHandSide ) ) ] ;
401
420
}
402
421
403
422
return [ createVariableStatement ( /*modifiers*/ undefined ,
404
- ( createVariableDeclarationList ( [ createVariableDeclaration ( getSynthesizedDeepClone ( prevArgName . identifier ) , /*type*/ undefined , rightHandSide ) ] , getFlagOfIdentifier ( prevArgName . identifier , transformer . constIdentifiers ) ) ) ) ] ;
423
+ ( createVariableDeclarationList ( [ createVariableDeclaration ( getSynthesizedDeepClone ( getNode ( prevArgName ) ) , /*type*/ undefined , rightHandSide ) ] , getFlagOfBindingName ( prevArgName , transformer . constIdentifiers ) ) ) ) ] ;
405
424
}
406
425
407
426
// should be kept up to date with isFixablePromiseArgument in suggestionDiagnostics.ts
408
- function getTransformationBody ( func : Expression , prevArgName : SynthIdentifier | undefined , argName : SynthIdentifier | undefined , parent : CallExpression , transformer : Transformer ) : ReadonlyArray < Statement > {
427
+ function getTransformationBody ( func : Expression , prevArgName : SynthBindingName | undefined , argName : SynthBindingName | undefined , parent : CallExpression , transformer : Transformer ) : ReadonlyArray < Statement > {
409
428
410
429
const shouldReturn = transformer . setOfExpressionsToReturn . get ( getNodeId ( parent ) . toString ( ) ) ;
411
430
switch ( func . kind ) {
@@ -418,7 +437,7 @@ namespace ts.codefix {
418
437
break ;
419
438
}
420
439
421
- const synthCall = createCall ( getSynthesizedDeepClone ( func as Identifier ) , /*typeArguments*/ undefined , [ argName . identifier ] ) ;
440
+ const synthCall = createCall ( getSynthesizedDeepClone ( func as Identifier ) , /*typeArguments*/ undefined , "identifier" in argName ? [ argName . identifier ] : [ ] ) ;
422
441
if ( shouldReturn ) {
423
442
return [ createReturn ( synthCall ) ] ;
424
443
}
@@ -461,7 +480,7 @@ namespace ts.codefix {
461
480
return shouldReturn ? refactoredStmts . map ( s => getSynthesizedDeepClone ( s ) ) :
462
481
removeReturns (
463
482
refactoredStmts ,
464
- prevArgName === undefined ? undefined : prevArgName . identifier ,
483
+ prevArgName ,
465
484
transformer ,
466
485
seenReturnStatement ) ;
467
486
}
@@ -503,7 +522,7 @@ namespace ts.codefix {
503
522
}
504
523
505
524
506
- function removeReturns ( stmts : ReadonlyArray < Statement > , prevArgName : Identifier | undefined , transformer : Transformer , seenReturnStatement : boolean ) : ReadonlyArray < Statement > {
525
+ function removeReturns ( stmts : ReadonlyArray < Statement > , prevArgName : SynthBindingName | undefined , transformer : Transformer , seenReturnStatement : boolean ) : ReadonlyArray < Statement > {
507
526
const ret : Statement [ ] = [ ] ;
508
527
for ( const stmt of stmts ) {
509
528
if ( isReturnStatement ( stmt ) ) {
@@ -514,7 +533,7 @@ namespace ts.codefix {
514
533
}
515
534
else {
516
535
ret . push ( createVariableStatement ( /*modifiers*/ undefined ,
517
- ( createVariableDeclarationList ( [ createVariableDeclaration ( prevArgName , /*type*/ undefined , possiblyAwaitedExpression ) ] , getFlagOfIdentifier ( prevArgName , transformer . constIdentifiers ) ) ) ) ) ;
536
+ ( createVariableDeclarationList ( [ createVariableDeclaration ( getNode ( prevArgName ) , /*type*/ undefined , possiblyAwaitedExpression ) ] , getFlagOfBindingName ( prevArgName , transformer . constIdentifiers ) ) ) ) ) ;
518
537
}
519
538
}
520
539
}
@@ -526,14 +545,14 @@ namespace ts.codefix {
526
545
// if block has no return statement, need to define prevArgName as undefined to prevent undeclared variables
527
546
if ( ! seenReturnStatement && prevArgName !== undefined ) {
528
547
ret . push ( createVariableStatement ( /*modifiers*/ undefined ,
529
- ( createVariableDeclarationList ( [ createVariableDeclaration ( prevArgName , /*type*/ undefined , createIdentifier ( "undefined" ) ) ] , getFlagOfIdentifier ( prevArgName , transformer . constIdentifiers ) ) ) ) ) ;
548
+ ( createVariableDeclarationList ( [ createVariableDeclaration ( getNode ( prevArgName ) , /*type*/ undefined , createIdentifier ( "undefined" ) ) ] , getFlagOfBindingName ( prevArgName , transformer . constIdentifiers ) ) ) ) ) ;
530
549
}
531
550
532
551
return ret ;
533
552
}
534
553
535
554
536
- function getInnerTransformationBody ( transformer : Transformer , innerRetStmts : ReadonlyArray < Node > , prevArgName ?: SynthIdentifier ) {
555
+ function getInnerTransformationBody ( transformer : Transformer , innerRetStmts : ReadonlyArray < Node > , prevArgName ?: SynthBindingName ) {
537
556
538
557
let innerCbBody : Statement [ ] = [ ] ;
539
558
for ( const stmt of innerRetStmts ) {
@@ -553,30 +572,40 @@ namespace ts.codefix {
553
572
return innerCbBody ;
554
573
}
555
574
556
- function getArgName ( funcNode : Expression , transformer : Transformer ) : SynthIdentifier | undefined {
575
+ function getArgBindingName ( funcNode : Expression , transformer : Transformer ) : SynthBindingName | undefined {
557
576
558
577
const numberOfAssignmentsOriginal = 0 ;
559
578
const types : Type [ ] = [ ] ;
560
579
561
- let name : SynthIdentifier | undefined ;
580
+ let name : SynthBindingName | undefined ;
562
581
563
582
if ( isFunctionLikeDeclaration ( funcNode ) ) {
564
583
if ( funcNode . parameters . length > 0 ) {
565
- const param = funcNode . parameters [ 0 ] . name as Identifier ;
566
- name = getMapEntryOrDefault ( param ) ;
584
+ const param = funcNode . parameters [ 0 ] . name ;
585
+ name = getMappedBindingNameOrDefault ( param ) ;
567
586
}
568
587
}
569
588
else if ( isIdentifier ( funcNode ) ) {
570
589
name = getMapEntryOrDefault ( funcNode ) ;
571
590
}
572
591
573
592
// return undefined argName when arg is null or undefined
574
- if ( ! name || name . identifier . text === "undefined" ) {
593
+ if ( ! name || "identifier" in name && name . identifier . text === "undefined" ) {
575
594
return undefined ;
576
595
}
577
596
578
597
return name ;
579
598
599
+ function getMappedBindingNameOrDefault ( bindingName : BindingName ) : SynthBindingName {
600
+ if ( isIdentifier ( bindingName ) ) return getMapEntryOrDefault ( bindingName ) ;
601
+ const elements = flatMap ( bindingName . elements , element => {
602
+ if ( isOmittedExpression ( element ) ) return [ ] ;
603
+ return [ getMappedBindingNameOrDefault ( element . name ) ] ;
604
+ } ) ;
605
+
606
+ return { elements, bindingPattern : bindingName , types : [ ] } ;
607
+ }
608
+
580
609
function getMapEntryOrDefault ( identifier : Identifier ) : SynthIdentifier {
581
610
const originalNode = getOriginalNode ( identifier ) ;
582
611
const symbol = getSymbol ( originalNode ) ;
@@ -597,4 +626,18 @@ namespace ts.codefix {
597
626
return node . original ? node . original : node ;
598
627
}
599
628
}
629
+
630
+ function isEmpty ( bindingName : SynthBindingName | undefined ) : boolean {
631
+ if ( ! bindingName ) {
632
+ return true ;
633
+ }
634
+ if ( "identifier" in bindingName ) {
635
+ return ! bindingName . identifier . text ;
636
+ }
637
+ return every ( bindingName . elements , isEmpty ) ;
638
+ }
639
+
640
+ function getNode ( bindingName : SynthBindingName ) {
641
+ return "identifier" in bindingName ? bindingName . identifier : bindingName . bindingPattern ;
642
+ }
600
643
}
0 commit comments