@@ -295,20 +295,19 @@ namespace ts.codefix {
295295 const contextualType = isJs ? undefined : checker . getContextualType ( call ) ;
296296 const names = map ( args , arg =>
297297 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+ ) ;
300302
301303 const modifiers = modifierFlags
302304 ? factory . createNodeArray ( factory . createModifiersFromModifierFlags ( modifierFlags ) )
303305 : undefined ;
304306 const asteriskToken = isYieldExpression ( parent )
305307 ? factory . createToken ( SyntaxKind . AsteriskToken )
306308 : 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 ) ;
312311 const type = isJs || contextualType === undefined
313312 ? undefined
314313 : checker . typeToTypeNode ( contextualType , contextNode , /*flags*/ undefined , tracker ) ;
@@ -349,6 +348,35 @@ namespace ts.codefix {
349348 }
350349 }
351350
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+
352380 export function typeToAutoImportableTypeNode ( checker : TypeChecker , importAdder : ImportAdder , type : Type , contextNode : Node | undefined , scriptTarget : ScriptTarget , flags ?: NodeBuilderFlags , tracker ?: SymbolTracker ) : TypeNode | undefined {
353381 let typeNode = checker . typeToTypeNode ( type , contextNode , flags , tracker ) ;
354382 if ( typeNode && isImportTypeNode ( typeNode ) ) {
@@ -358,19 +386,124 @@ namespace ts.codefix {
358386 typeNode = importableReference . typeNode ;
359387 }
360388 }
389+
361390 // Ensure nodes are fresh so they can have different positions when going through formatting.
362391 return getSynthesizedDeepClone ( typeNode ) ;
363392 }
364393
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+
365493 function createDummyParameters ( argCount : number , names : ( string | undefined ) [ ] | undefined , types : ( TypeNode | undefined ) [ ] | undefined , minArgumentCount : number | undefined , inJs : boolean ) : ParameterDeclaration [ ] {
366494 const parameters : ParameterDeclaration [ ] = [ ] ;
495+ const parameterNameCounts = new Map < string , number > ( ) ;
367496 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+
368501 const newParameter = factory . createParameterDeclaration (
369502 /*modifiers*/ undefined ,
370503 /*dotDotDotToken*/ undefined ,
371- /*name*/ names && names [ i ] || `arg ${ i } ` ,
504+ /*name*/ parameterName + ( parameterNameCount || "" ) ,
372505 /*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 ) ,
374507 /*initializer*/ undefined ) ;
375508 parameters . push ( newParameter ) ;
376509 }
0 commit comments