@@ -295,20 +295,19 @@ namespace ts.codefix {
295
295
const contextualType = isJs ? undefined : checker . getContextualType ( call ) ;
296
296
const names = map ( args , arg =>
297
297
isIdentifier ( arg ) ? arg . text : isPropertyAccessExpression ( arg ) && isIdentifier ( arg . name ) ? arg . name . text : undefined ) ;
298
- const types = isJs ? [ ] : map ( args , arg =>
299
- typeToAutoImportableTypeNode ( checker , importAdder , checker . getBaseTypeOfLiteralType ( checker . getTypeAtLocation ( arg ) ) , contextNode , scriptTarget , /*flags*/ undefined , tracker ) ) ;
298
+ const instanceTypes = isJs ? [ ] : map ( args , arg => checker . getTypeAtLocation ( arg ) ) ;
299
+ const { argumentTypeNodes, argumentTypeParameters } = getArgumentTypesAndTypeParameters (
300
+ checker , importAdder , instanceTypes , contextNode , scriptTarget , /*flags*/ undefined , tracker
301
+ ) ;
300
302
301
303
const modifiers = modifierFlags
302
304
? factory . createNodeArray ( factory . createModifiersFromModifierFlags ( modifierFlags ) )
303
305
: undefined ;
304
306
const asteriskToken = isYieldExpression ( parent )
305
307
? factory . createToken ( SyntaxKind . AsteriskToken )
306
308
: undefined ;
307
- const typeParameters = isJs || typeArguments === undefined
308
- ? undefined
309
- : map ( typeArguments , ( _ , i ) =>
310
- factory . createTypeParameterDeclaration ( /*modifiers*/ undefined , CharacterCodes . T + typeArguments . length - 1 <= CharacterCodes . Z ? String . fromCharCode ( CharacterCodes . T + i ) : `T${ i } ` ) ) ;
311
- const parameters = createDummyParameters ( args . length , names , types , /*minArgumentCount*/ undefined , isJs ) ;
309
+ const typeParameters = isJs ? undefined : createTypeParametersForArguments ( checker , argumentTypeParameters , typeArguments ) ;
310
+ const parameters = createDummyParameters ( args . length , names , argumentTypeNodes , /*minArgumentCount*/ undefined , isJs ) ;
312
311
const type = isJs || contextualType === undefined
313
312
? undefined
314
313
: checker . typeToTypeNode ( contextualType , contextNode , /*flags*/ undefined , tracker ) ;
@@ -349,6 +348,35 @@ namespace ts.codefix {
349
348
}
350
349
}
351
350
351
+ interface ArgumentTypeParameterAndConstraint {
352
+ argumentType : Type ;
353
+ constraint ?: TypeNode ;
354
+ }
355
+
356
+ function createTypeParametersForArguments ( checker : TypeChecker , argumentTypeParameters : [ string , ArgumentTypeParameterAndConstraint | undefined ] [ ] , typeArguments : NodeArray < TypeNode > | undefined ) {
357
+ const usedNames = new Set ( argumentTypeParameters . map ( pair => pair [ 0 ] ) ) ;
358
+ const constraintsByName = new Map ( argumentTypeParameters ) ;
359
+
360
+ if ( typeArguments ) {
361
+ const typeArgumentsWithNewTypes = typeArguments . filter ( typeArgument => ! argumentTypeParameters . some ( pair => checker . getTypeAtLocation ( typeArgument ) === pair [ 1 ] ?. argumentType ) ) ;
362
+ const targetSize = usedNames . size + typeArgumentsWithNewTypes . length ;
363
+ for ( let i = 0 ; usedNames . size < targetSize ; i += 1 ) {
364
+ usedNames . add ( createTypeParameterName ( i ) ) ;
365
+ }
366
+ }
367
+
368
+ return map (
369
+ arrayFrom ( usedNames . values ( ) ) ,
370
+ usedName => factory . createTypeParameterDeclaration ( /*modifiers*/ undefined , usedName , constraintsByName . get ( usedName ) ?. constraint ) ,
371
+ ) ;
372
+ }
373
+
374
+ function createTypeParameterName ( index : number ) {
375
+ return CharacterCodes . T + index <= CharacterCodes . Z
376
+ ? String . fromCharCode ( CharacterCodes . T + index )
377
+ : `T${ index } ` ;
378
+ }
379
+
352
380
export function typeToAutoImportableTypeNode ( checker : TypeChecker , importAdder : ImportAdder , type : Type , contextNode : Node | undefined , scriptTarget : ScriptTarget , flags ?: NodeBuilderFlags , tracker ?: SymbolTracker ) : TypeNode | undefined {
353
381
let typeNode = checker . typeToTypeNode ( type , contextNode , flags , tracker ) ;
354
382
if ( typeNode && isImportTypeNode ( typeNode ) ) {
@@ -358,19 +386,124 @@ namespace ts.codefix {
358
386
typeNode = importableReference . typeNode ;
359
387
}
360
388
}
389
+
361
390
// Ensure nodes are fresh so they can have different positions when going through formatting.
362
391
return getSynthesizedDeepClone ( typeNode ) ;
363
392
}
364
393
394
+ function typeContainsTypeParameter ( type : Type ) {
395
+ if ( type . isUnionOrIntersection ( ) ) {
396
+ return type . types . some ( typeContainsTypeParameter ) ;
397
+ }
398
+
399
+ return type . flags & TypeFlags . TypeParameter ;
400
+ }
401
+
402
+ export function getArgumentTypesAndTypeParameters ( checker : TypeChecker , importAdder : ImportAdder , instanceTypes : Type [ ] , contextNode : Node | undefined , scriptTarget : ScriptTarget , flags ?: NodeBuilderFlags , tracker ?: SymbolTracker ) {
403
+ // Types to be used as the types of the parameters in the new function
404
+ // E.g. from this source:
405
+ // added("", 0)
406
+ // The value will look like:
407
+ // [{ typeName: { text: "string" } }, { typeName: { text: "number" }]
408
+ // And in the output function will generate:
409
+ // function added(a: string, b: number) { ... }
410
+ const argumentTypeNodes : TypeNode [ ] = [ ] ;
411
+
412
+ // Names of type parameters provided as arguments to the call
413
+ // E.g. from this source:
414
+ // added<T, U>(value);
415
+ // The value will look like:
416
+ // [
417
+ // ["T", { argumentType: { typeName: { text: "T" } } } ],
418
+ // ["U", { argumentType: { typeName: { text: "U" } } } ],
419
+ // ]
420
+ // And in the output function will generate:
421
+ // function added<T, U>() { ... }
422
+ const argumentTypeParameters = new Map < string , ArgumentTypeParameterAndConstraint | undefined > ( ) ;
423
+
424
+ for ( let i = 0 ; i < instanceTypes . length ; i += 1 ) {
425
+ const instanceType = instanceTypes [ i ] ;
426
+
427
+ // If the instance type contains a deep reference to an existing type parameter,
428
+ // instead of copying the full union or intersection, create a new type parameter
429
+ // E.g. from this source:
430
+ // function existing<T, U>(value: T | U & string) {
431
+ // added/*1*/(value);
432
+ // We don't want to output this:
433
+ // function added<T>(value: T | U & string) { ... }
434
+ // We instead want to output:
435
+ // function added<T>(value: T) { ... }
436
+ if ( instanceType . isUnionOrIntersection ( ) && instanceType . types . some ( typeContainsTypeParameter ) ) {
437
+ const synthesizedTypeParameterName = createTypeParameterName ( i ) ;
438
+ argumentTypeNodes . push ( factory . createTypeReferenceNode ( synthesizedTypeParameterName ) ) ;
439
+ argumentTypeParameters . set ( synthesizedTypeParameterName , undefined ) ;
440
+ continue ;
441
+ }
442
+
443
+ // Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {"
444
+ const widenedInstanceType = checker . getBaseTypeOfLiteralType ( instanceType ) ;
445
+ const argumentTypeNode = typeToAutoImportableTypeNode ( checker , importAdder , widenedInstanceType , contextNode , scriptTarget , flags , tracker ) ;
446
+ if ( ! argumentTypeNode ) {
447
+ continue ;
448
+ }
449
+
450
+ argumentTypeNodes . push ( argumentTypeNode ) ;
451
+ const argumentTypeParameter = getFirstTypeParameterName ( instanceType ) ;
452
+
453
+ // If the instance type is a type parameter with a constraint (other than an anonymous object),
454
+ // remember that constraint for when we create the new type parameter
455
+ // E.g. from this source:
456
+ // function existing<T extends string>(value: T) {
457
+ // added/*1*/(value);
458
+ // We don't want to output this:
459
+ // function added<T>(value: T) { ... }
460
+ // We instead want to output:
461
+ // function added<T extends string>(value: T) { ... }
462
+ const instanceTypeConstraint = instanceType . isTypeParameter ( ) && instanceType . constraint && ! isAnonymousObjectConstraintType ( instanceType . constraint )
463
+ ? typeToAutoImportableTypeNode ( checker , importAdder , instanceType . constraint , contextNode , scriptTarget , flags , tracker )
464
+ : undefined ;
465
+
466
+ if ( argumentTypeParameter ) {
467
+ argumentTypeParameters . set ( argumentTypeParameter , { argumentType : instanceType , constraint : instanceTypeConstraint } ) ;
468
+ }
469
+ }
470
+
471
+ return { argumentTypeNodes, argumentTypeParameters : arrayFrom ( argumentTypeParameters . entries ( ) ) } ;
472
+ }
473
+
474
+ function isAnonymousObjectConstraintType ( type : Type ) {
475
+ return ( type . flags & TypeFlags . Object ) && ( type as ObjectType ) . objectFlags === ObjectFlags . Anonymous ;
476
+ }
477
+
478
+ function getFirstTypeParameterName ( type : Type ) : string | undefined {
479
+ if ( type . flags & ( TypeFlags . Union | TypeFlags . Intersection ) ) {
480
+ for ( const subType of ( type as UnionType | IntersectionType ) . types ) {
481
+ const subTypeName = getFirstTypeParameterName ( subType ) ;
482
+ if ( subTypeName ) {
483
+ return subTypeName ;
484
+ }
485
+ }
486
+ }
487
+
488
+ return type . flags & TypeFlags . TypeParameter
489
+ ? type . getSymbol ( ) ?. getName ( )
490
+ : undefined ;
491
+ }
492
+
365
493
function createDummyParameters ( argCount : number , names : ( string | undefined ) [ ] | undefined , types : ( TypeNode | undefined ) [ ] | undefined , minArgumentCount : number | undefined , inJs : boolean ) : ParameterDeclaration [ ] {
366
494
const parameters : ParameterDeclaration [ ] = [ ] ;
495
+ const parameterNameCounts = new Map < string , number > ( ) ;
367
496
for ( let i = 0 ; i < argCount ; i ++ ) {
497
+ const parameterName = names ?. [ i ] || `arg${ i } ` ;
498
+ const parameterNameCount = parameterNameCounts . get ( parameterName ) ;
499
+ parameterNameCounts . set ( parameterName , ( parameterNameCount || 0 ) + 1 ) ;
500
+
368
501
const newParameter = factory . createParameterDeclaration (
369
502
/*modifiers*/ undefined ,
370
503
/*dotDotDotToken*/ undefined ,
371
- /*name*/ names && names [ i ] || `arg ${ i } ` ,
504
+ /*name*/ parameterName + ( parameterNameCount || "" ) ,
372
505
/*questionToken*/ minArgumentCount !== undefined && i >= minArgumentCount ? factory . createToken ( SyntaxKind . QuestionToken ) : undefined ,
373
- /*type*/ inJs ? undefined : types && types [ i ] || factory . createKeywordTypeNode ( SyntaxKind . UnknownKeyword ) ,
506
+ /*type*/ inJs ? undefined : types ?. [ i ] || factory . createKeywordTypeNode ( SyntaxKind . UnknownKeyword ) ,
374
507
/*initializer*/ undefined ) ;
375
508
parameters . push ( newParameter ) ;
376
509
}
0 commit comments