1import { 2 AccessorDeclaration, append, arrayFrom, ArrowFunction, Block, CallExpression, CharacterCodes, ClassLikeDeclaration, 3 CodeFixContextBase, Debug, Diagnostics, emptyArray, EntityName, Expression, factory, find, flatMap, 4 FunctionDeclaration, FunctionExpression, GetAccessorDeclaration, getAllAccessorDeclarations, 5 getEffectiveModifierFlags, getEmitScriptTarget, getFirstIdentifier, getModuleSpecifierResolverHost, 6 getNameForExportedSymbol, getNameOfDeclaration, getQuotePreference, getSetAccessorValueParameter, 7 getSynthesizedDeepClone, getTokenAtPosition, getTsConfigObjectLiteralExpression, Identifier, idText, 8 IntersectionType, isArrowFunction, isAutoAccessorPropertyDeclaration, isFunctionDeclaration, isFunctionExpression, 9 isGetAccessorDeclaration, isIdentifier, isImportTypeNode, isInJSFile, isLiteralImportTypeNode, isMethodDeclaration, 10 isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSetAccessorDeclaration, 11 isStringLiteral, isYieldExpression, LanguageServiceHost, length, map, Map, MethodDeclaration, MethodSignature, Modifier, 12 ModifierFlags, Node, NodeArray, NodeBuilderFlags, NodeFlags, nullTransformationContext, ObjectFlags, 13 ObjectLiteralExpression, ObjectType, ParameterDeclaration, Program, PropertyAssignment, PropertyDeclaration, 14 PropertyName, QuotePreference, sameMap, ScriptTarget, Set, SetAccessorDeclaration, setTextRange, Signature, 15 SignatureDeclaration, signatureHasRestParameter, some, SourceFile, Symbol, SymbolFlags, SymbolTracker, SyntaxKind, 16 textChanges, TextSpan, textSpanEnd, tryCast, TsConfigSourceFile, Type, TypeChecker, TypeFlags, TypeNode, 17 TypeParameterDeclaration, UnionType, UserPreferences, visitEachChild, visitNode, 18} from "../_namespaces/ts"; 19import { ImportAdder } from "../_namespaces/ts.codefix"; 20 21/** 22 * Finds members of the resolved type that are missing in the class pointed to by class decl 23 * and generates source code for the missing members. 24 * @param possiblyMissingSymbols The collection of symbols to filter and then get insertions for. 25 * @param importAdder If provided, type annotations will use identifier type references instead of ImportTypeNodes, and the missing imports will be added to the importAdder. 26 * @returns Empty string iff there are no member insertions. 27 * 28 * @internal 29 */ 30export function createMissingMemberNodes( 31 classDeclaration: ClassLikeDeclaration, 32 possiblyMissingSymbols: readonly Symbol[], 33 sourceFile: SourceFile, 34 context: TypeConstructionContext, 35 preferences: UserPreferences, 36 importAdder: ImportAdder | undefined, 37 addClassElement: (node: AddNode) => void): void { 38 const classMembers = classDeclaration.symbol.members!; 39 for (const symbol of possiblyMissingSymbols) { 40 if (!classMembers.has(symbol.escapedName)) { 41 addNewNodeForMemberSymbol(symbol, classDeclaration, sourceFile, context, preferences, importAdder, addClassElement, /* body */ undefined); 42 } 43 } 44} 45 46/** @internal */ 47export function getNoopSymbolTrackerWithResolver(context: TypeConstructionContext): SymbolTracker { 48 return { 49 trackSymbol: () => false, 50 moduleResolverHost: getModuleSpecifierResolverHost(context.program, context.host), 51 }; 52} 53 54/** @internal */ 55export interface TypeConstructionContext { 56 program: Program; 57 host: LanguageServiceHost; 58} 59 60/** @internal */ 61export type AddNode = PropertyDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction; 62 63/** @internal */ 64export const enum PreserveOptionalFlags { 65 Method = 1 << 0, 66 Property = 1 << 1, 67 All = Method | Property 68} 69 70/** 71 * `addClassElement` will not be called if we can't figure out a representation for `symbol` in `enclosingDeclaration`. 72 * @param body If defined, this will be the body of the member node passed to `addClassElement`. Otherwise, the body will default to a stub. 73 * 74 * @internal 75 */ 76export function addNewNodeForMemberSymbol( 77 symbol: Symbol, 78 enclosingDeclaration: ClassLikeDeclaration, 79 sourceFile: SourceFile, 80 context: TypeConstructionContext, 81 preferences: UserPreferences, 82 importAdder: ImportAdder | undefined, 83 addClassElement: (node: AddNode) => void, 84 body: Block | undefined, 85 preserveOptional = PreserveOptionalFlags.All, 86 isAmbient = false, 87): void { 88 const declarations = symbol.getDeclarations(); 89 const declaration = declarations?.[0]; 90 const checker = context.program.getTypeChecker(); 91 const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions()); 92 93 /** 94 * (#49811) 95 * Note that there are cases in which the symbol declaration is not present. For example, in the code below both 96 * `MappedIndirect.ax` and `MappedIndirect.ay` have no declaration node attached (due to their mapped-type 97 * parent): 98 * 99 * ```ts 100 * type Base = { ax: number; ay: string }; 101 * type BaseKeys = keyof Base; 102 * type MappedIndirect = { [K in BaseKeys]: boolean }; 103 * ``` 104 * 105 * In such cases, we assume the declaration to be a `PropertySignature`. 106 */ 107 const kind = declaration?.kind ?? SyntaxKind.PropertySignature; 108 const declarationName = getSynthesizedDeepClone(getNameOfDeclaration(declaration), /*includeTrivia*/ false) as PropertyName; 109 const effectiveModifierFlags = declaration ? getEffectiveModifierFlags(declaration) : ModifierFlags.None; 110 let modifierFlags = 111 effectiveModifierFlags & ModifierFlags.Public ? ModifierFlags.Public : 112 effectiveModifierFlags & ModifierFlags.Protected ? ModifierFlags.Protected : 113 ModifierFlags.None; 114 if (declaration && isAutoAccessorPropertyDeclaration(declaration)) { 115 modifierFlags |= ModifierFlags.Accessor; 116 } 117 const modifiers = modifierFlags ? factory.createNodeArray(factory.createModifiersFromModifierFlags(modifierFlags)) : undefined; 118 const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); 119 const optional = !!(symbol.flags & SymbolFlags.Optional); 120 const ambient = !!(enclosingDeclaration.flags & NodeFlags.Ambient) || isAmbient; 121 const quotePreference = getQuotePreference(sourceFile, preferences); 122 123 switch (kind) { 124 case SyntaxKind.PropertySignature: 125 case SyntaxKind.PropertyDeclaration: 126 const flags = quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : undefined; 127 let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, flags, getNoopSymbolTrackerWithResolver(context)); 128 if (importAdder) { 129 const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); 130 if (importableReference) { 131 typeNode = importableReference.typeNode; 132 importSymbols(importAdder, importableReference.symbols); 133 } 134 } 135 addClassElement(factory.createPropertyDeclaration( 136 modifiers, 137 declaration ? createName(declarationName) : symbol.getName(), 138 optional && (preserveOptional & PreserveOptionalFlags.Property) ? factory.createToken(SyntaxKind.QuestionToken) : undefined, 139 typeNode, 140 /*initializer*/ undefined)); 141 break; 142 case SyntaxKind.GetAccessor: 143 case SyntaxKind.SetAccessor: { 144 Debug.assertIsDefined(declarations); 145 let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context)); 146 const allAccessors = getAllAccessorDeclarations(declarations, declaration as AccessorDeclaration); 147 const orderedAccessors = allAccessors.secondAccessor 148 ? [allAccessors.firstAccessor, allAccessors.secondAccessor] 149 : [allAccessors.firstAccessor]; 150 if (importAdder) { 151 const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); 152 if (importableReference) { 153 typeNode = importableReference.typeNode; 154 importSymbols(importAdder, importableReference.symbols); 155 } 156 } 157 for (const accessor of orderedAccessors) { 158 if (isGetAccessorDeclaration(accessor)) { 159 addClassElement(factory.createGetAccessorDeclaration( 160 modifiers, 161 createName(declarationName), 162 emptyArray, 163 createTypeNode(typeNode), 164 createBody(body, quotePreference, ambient))); 165 } 166 else { 167 Debug.assertNode(accessor, isSetAccessorDeclaration, "The counterpart to a getter should be a setter"); 168 const parameter = getSetAccessorValueParameter(accessor); 169 const parameterName = parameter && isIdentifier(parameter.name) ? idText(parameter.name) : undefined; 170 addClassElement(factory.createSetAccessorDeclaration( 171 modifiers, 172 createName(declarationName), 173 createDummyParameters(1, [parameterName], [createTypeNode(typeNode)], 1, /*inJs*/ false), 174 createBody(body, quotePreference, ambient))); 175 } 176 } 177 break; 178 } 179 case SyntaxKind.MethodSignature: 180 case SyntaxKind.MethodDeclaration: 181 // The signature for the implementation appears as an entry in `signatures` iff 182 // there is only one signature. 183 // If there are overloads and an implementation signature, it appears as an 184 // extra declaration that isn't a signature for `type`. 185 // If there is more than one overload but no implementation signature 186 // (eg: an abstract method or interface declaration), there is a 1-1 187 // correspondence of declarations and signatures. 188 Debug.assertIsDefined(declarations); 189 const signatures = type.isUnion() ? flatMap(type.types, t => t.getCallSignatures()) : type.getCallSignatures(); 190 if (!some(signatures)) { 191 break; 192 } 193 194 if (declarations.length === 1) { 195 Debug.assert(signatures.length === 1, "One declaration implies one signature"); 196 const signature = signatures[0]; 197 outputMethod(quotePreference, signature, modifiers, createName(declarationName), createBody(body, quotePreference, ambient)); 198 break; 199 } 200 201 for (const signature of signatures) { 202 // Ensure nodes are fresh so they can have different positions when going through formatting. 203 outputMethod(quotePreference, signature, modifiers, createName(declarationName)); 204 } 205 206 if (!ambient) { 207 if (declarations.length > signatures.length) { 208 const signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration)!; 209 outputMethod(quotePreference, signature, modifiers, createName(declarationName), createBody(body, quotePreference)); 210 } 211 else { 212 Debug.assert(declarations.length === signatures.length, "Declarations and signatures should match count"); 213 addClassElement(createMethodImplementingSignatures(checker, context, enclosingDeclaration, signatures, createName(declarationName), optional && !!(preserveOptional & PreserveOptionalFlags.Method), modifiers, quotePreference, body)); 214 } 215 } 216 break; 217 } 218 219 function outputMethod(quotePreference: QuotePreference, signature: Signature, modifiers: NodeArray<Modifier> | undefined, name: PropertyName, body?: Block): void { 220 const method = createSignatureDeclarationFromSignature(SyntaxKind.MethodDeclaration, context, quotePreference, signature, body, name, modifiers, optional && !!(preserveOptional & PreserveOptionalFlags.Method), enclosingDeclaration, importAdder) as MethodDeclaration; 221 if (method) addClassElement(method); 222 } 223 224 function createName(node: PropertyName) { 225 return getSynthesizedDeepClone(node, /*includeTrivia*/ false); 226 } 227 228 function createBody(block: Block | undefined, quotePreference: QuotePreference, ambient?: boolean) { 229 return ambient ? undefined : 230 getSynthesizedDeepClone(block, /*includeTrivia*/ false) || createStubbedMethodBody(quotePreference); 231 } 232 233 function createTypeNode(typeNode: TypeNode | undefined) { 234 return getSynthesizedDeepClone(typeNode, /*includeTrivia*/ false); 235 } 236} 237 238/** @internal */ 239export function createSignatureDeclarationFromSignature( 240 kind: 241 | SyntaxKind.MethodDeclaration 242 | SyntaxKind.FunctionExpression 243 | SyntaxKind.ArrowFunction 244 | SyntaxKind.FunctionDeclaration, 245 context: TypeConstructionContext, 246 quotePreference: QuotePreference, 247 signature: Signature, 248 body: Block | undefined, 249 name: PropertyName | undefined, 250 modifiers: NodeArray<Modifier> | undefined, 251 optional: boolean | undefined, 252 enclosingDeclaration: Node | undefined, 253 importAdder: ImportAdder | undefined 254) { 255 const program = context.program; 256 const checker = program.getTypeChecker(); 257 const scriptTarget = getEmitScriptTarget(program.getCompilerOptions()); 258 const flags = 259 NodeBuilderFlags.NoTruncation 260 | NodeBuilderFlags.SuppressAnyReturnType 261 | NodeBuilderFlags.AllowEmptyTuple 262 | (quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : NodeBuilderFlags.None); 263 const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, kind, enclosingDeclaration, flags, getNoopSymbolTrackerWithResolver(context)) as ArrowFunction | FunctionExpression | MethodDeclaration | FunctionDeclaration; 264 if (!signatureDeclaration) { 265 return undefined; 266 } 267 268 let typeParameters = signatureDeclaration.typeParameters; 269 let parameters = signatureDeclaration.parameters; 270 let type = signatureDeclaration.type; 271 if (importAdder) { 272 if (typeParameters) { 273 const newTypeParameters = sameMap(typeParameters, typeParameterDecl => { 274 let constraint = typeParameterDecl.constraint; 275 let defaultType = typeParameterDecl.default; 276 if (constraint) { 277 const importableReference = tryGetAutoImportableReferenceFromTypeNode(constraint, scriptTarget); 278 if (importableReference) { 279 constraint = importableReference.typeNode; 280 importSymbols(importAdder, importableReference.symbols); 281 } 282 } 283 if (defaultType) { 284 const importableReference = tryGetAutoImportableReferenceFromTypeNode(defaultType, scriptTarget); 285 if (importableReference) { 286 defaultType = importableReference.typeNode; 287 importSymbols(importAdder, importableReference.symbols); 288 } 289 } 290 return factory.updateTypeParameterDeclaration( 291 typeParameterDecl, 292 typeParameterDecl.modifiers, 293 typeParameterDecl.name, 294 constraint, 295 defaultType 296 ); 297 }); 298 if (typeParameters !== newTypeParameters) { 299 typeParameters = setTextRange(factory.createNodeArray(newTypeParameters, typeParameters.hasTrailingComma), typeParameters); 300 } 301 } 302 const newParameters = sameMap(parameters, parameterDecl => { 303 const importableReference = tryGetAutoImportableReferenceFromTypeNode(parameterDecl.type, scriptTarget); 304 let type = parameterDecl.type; 305 if (importableReference) { 306 type = importableReference.typeNode; 307 importSymbols(importAdder, importableReference.symbols); 308 } 309 return factory.updateParameterDeclaration( 310 parameterDecl, 311 parameterDecl.modifiers, 312 parameterDecl.dotDotDotToken, 313 parameterDecl.name, 314 parameterDecl.questionToken, 315 type, 316 parameterDecl.initializer 317 ); 318 }); 319 if (parameters !== newParameters) { 320 parameters = setTextRange(factory.createNodeArray(newParameters, parameters.hasTrailingComma), parameters); 321 } 322 if (type) { 323 const importableReference = tryGetAutoImportableReferenceFromTypeNode(type, scriptTarget); 324 if (importableReference) { 325 type = importableReference.typeNode; 326 importSymbols(importAdder, importableReference.symbols); 327 } 328 } 329 } 330 331 const questionToken = optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; 332 const asteriskToken = signatureDeclaration.asteriskToken; 333 if (isFunctionExpression(signatureDeclaration)) { 334 return factory.updateFunctionExpression(signatureDeclaration, modifiers, signatureDeclaration.asteriskToken, tryCast(name, isIdentifier), typeParameters, parameters, type, body ?? signatureDeclaration.body); 335 } 336 if (isArrowFunction(signatureDeclaration)) { 337 return factory.updateArrowFunction(signatureDeclaration, modifiers, typeParameters, parameters, type, signatureDeclaration.equalsGreaterThanToken, body ?? signatureDeclaration.body); 338 } 339 if (isMethodDeclaration(signatureDeclaration)) { 340 return factory.updateMethodDeclaration(signatureDeclaration, modifiers, asteriskToken, name ?? factory.createIdentifier(""), questionToken, typeParameters, parameters, type, body); 341 } 342 if (isFunctionDeclaration(signatureDeclaration)) { 343 return factory.updateFunctionDeclaration(signatureDeclaration, modifiers, signatureDeclaration.asteriskToken, tryCast(name, isIdentifier), typeParameters, parameters, type, body ?? signatureDeclaration.body); 344 } 345 return undefined; 346} 347 348/** @internal */ 349export function createSignatureDeclarationFromCallExpression( 350 kind: SyntaxKind.MethodDeclaration | SyntaxKind.FunctionDeclaration | SyntaxKind.MethodSignature, 351 context: CodeFixContextBase, 352 importAdder: ImportAdder, 353 call: CallExpression, 354 name: Identifier | string, 355 modifierFlags: ModifierFlags, 356 contextNode: Node 357): MethodDeclaration | FunctionDeclaration | MethodSignature { 358 const quotePreference = getQuotePreference(context.sourceFile, context.preferences); 359 const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions()); 360 const tracker = getNoopSymbolTrackerWithResolver(context); 361 const checker = context.program.getTypeChecker(); 362 const isJs = isInJSFile(contextNode); 363 const { typeArguments, arguments: args, parent } = call; 364 365 const contextualType = isJs ? undefined : checker.getContextualType(call); 366 const names = map(args, arg => 367 isIdentifier(arg) ? arg.text : isPropertyAccessExpression(arg) && isIdentifier(arg.name) ? arg.name.text : undefined); 368 const instanceTypes = isJs ? [] : map(args, arg => checker.getTypeAtLocation(arg)); 369 const { argumentTypeNodes, argumentTypeParameters } = getArgumentTypesAndTypeParameters( 370 checker, importAdder, instanceTypes, contextNode, scriptTarget, /*flags*/ undefined, tracker 371 ); 372 373 const modifiers = modifierFlags 374 ? factory.createNodeArray(factory.createModifiersFromModifierFlags(modifierFlags)) 375 : undefined; 376 const asteriskToken = isYieldExpression(parent) 377 ? factory.createToken(SyntaxKind.AsteriskToken) 378 : undefined; 379 const typeParameters = isJs ? undefined : createTypeParametersForArguments(checker, argumentTypeParameters, typeArguments); 380 const parameters = createDummyParameters(args.length, names, argumentTypeNodes, /*minArgumentCount*/ undefined, isJs); 381 const type = isJs || contextualType === undefined 382 ? undefined 383 : checker.typeToTypeNode(contextualType, contextNode, /*flags*/ undefined, tracker); 384 385 switch (kind) { 386 case SyntaxKind.MethodDeclaration: 387 return factory.createMethodDeclaration( 388 modifiers, 389 asteriskToken, 390 name, 391 /*questionToken*/ undefined, 392 typeParameters, 393 parameters, 394 type, 395 createStubbedMethodBody(quotePreference) 396 ); 397 case SyntaxKind.MethodSignature: 398 return factory.createMethodSignature( 399 modifiers, 400 name, 401 /*questionToken*/ undefined, 402 typeParameters, 403 parameters, 404 type === undefined ? factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword) : type 405 ); 406 case SyntaxKind.FunctionDeclaration: 407 return factory.createFunctionDeclaration( 408 modifiers, 409 asteriskToken, 410 name, 411 typeParameters, 412 parameters, 413 type, 414 createStubbedBody(Diagnostics.Function_not_implemented.message, quotePreference) 415 ); 416 default: 417 Debug.fail("Unexpected kind"); 418 } 419} 420 421/** @internal */ 422export interface ArgumentTypeParameterAndConstraint { 423 argumentType: Type; 424 constraint?: TypeNode; 425} 426 427function createTypeParametersForArguments(checker: TypeChecker, argumentTypeParameters: [string, ArgumentTypeParameterAndConstraint | undefined][], typeArguments: NodeArray<TypeNode> | undefined) { 428 const usedNames = new Set(argumentTypeParameters.map(pair => pair[0])); 429 const constraintsByName = new Map(argumentTypeParameters); 430 431 if (typeArguments) { 432 const typeArgumentsWithNewTypes = typeArguments.filter(typeArgument => !argumentTypeParameters.some(pair => checker.getTypeAtLocation(typeArgument) === pair[1]?.argumentType)); 433 const targetSize = usedNames.size + typeArgumentsWithNewTypes.length; 434 for (let i = 0; usedNames.size < targetSize; i += 1) { 435 usedNames.add(createTypeParameterName(i)); 436 } 437 } 438 439 return map( 440 arrayFrom(usedNames.values()), 441 usedName => factory.createTypeParameterDeclaration(/*modifiers*/ undefined, usedName, constraintsByName.get(usedName)?.constraint), 442 ); 443} 444 445function createTypeParameterName(index: number) { 446 return CharacterCodes.T + index <= CharacterCodes.Z 447 ? String.fromCharCode(CharacterCodes.T + index) 448 : `T${index}`; 449} 450 451/** @internal */ 452export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, type: Type, contextNode: Node | undefined, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined { 453 let typeNode = checker.typeToTypeNode(type, contextNode, flags, tracker); 454 if (typeNode && isImportTypeNode(typeNode)) { 455 const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); 456 if (importableReference) { 457 importSymbols(importAdder, importableReference.symbols); 458 typeNode = importableReference.typeNode; 459 } 460 } 461 462 // Ensure nodes are fresh so they can have different positions when going through formatting. 463 return getSynthesizedDeepClone(typeNode); 464} 465 466function typeContainsTypeParameter(type: Type) { 467 if (type.isUnionOrIntersection()) { 468 return type.types.some(typeContainsTypeParameter); 469 } 470 471 return type.flags & TypeFlags.TypeParameter; 472} 473 474/** @internal */ 475export function getArgumentTypesAndTypeParameters(checker: TypeChecker, importAdder: ImportAdder, instanceTypes: Type[], contextNode: Node | undefined, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, tracker?: SymbolTracker) { 476 // Types to be used as the types of the parameters in the new function 477 // E.g. from this source: 478 // added("", 0) 479 // The value will look like: 480 // [{ typeName: { text: "string" } }, { typeName: { text: "number" }] 481 // And in the output function will generate: 482 // function added(a: string, b: number) { ... } 483 const argumentTypeNodes: TypeNode[] = []; 484 485 // Names of type parameters provided as arguments to the call 486 // E.g. from this source: 487 // added<T, U>(value); 488 // The value will look like: 489 // [ 490 // ["T", { argumentType: { typeName: { text: "T" } } } ], 491 // ["U", { argumentType: { typeName: { text: "U" } } } ], 492 // ] 493 // And in the output function will generate: 494 // function added<T, U>() { ... } 495 const argumentTypeParameters = new Map<string, ArgumentTypeParameterAndConstraint | undefined>(); 496 497 for (let i = 0; i < instanceTypes.length; i += 1) { 498 const instanceType = instanceTypes[i]; 499 500 // If the instance type contains a deep reference to an existing type parameter, 501 // instead of copying the full union or intersection, create a new type parameter 502 // E.g. from this source: 503 // function existing<T, U>(value: T | U & string) { 504 // added/*1*/(value); 505 // We don't want to output this: 506 // function added<T>(value: T | U & string) { ... } 507 // We instead want to output: 508 // function added<T>(value: T) { ... } 509 if (instanceType.isUnionOrIntersection() && instanceType.types.some(typeContainsTypeParameter)) { 510 const synthesizedTypeParameterName = createTypeParameterName(i); 511 argumentTypeNodes.push(factory.createTypeReferenceNode(synthesizedTypeParameterName)); 512 argumentTypeParameters.set(synthesizedTypeParameterName, undefined); 513 continue; 514 } 515 516 // Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {" 517 const widenedInstanceType = checker.getBaseTypeOfLiteralType(instanceType); 518 const argumentTypeNode = typeToAutoImportableTypeNode(checker, importAdder, widenedInstanceType, contextNode, scriptTarget, flags, tracker); 519 if (!argumentTypeNode) { 520 continue; 521 } 522 523 argumentTypeNodes.push(argumentTypeNode); 524 const argumentTypeParameter = getFirstTypeParameterName(instanceType); 525 526 // If the instance type is a type parameter with a constraint (other than an anonymous object), 527 // remember that constraint for when we create the new type parameter 528 // E.g. from this source: 529 // function existing<T extends string>(value: T) { 530 // added/*1*/(value); 531 // We don't want to output this: 532 // function added<T>(value: T) { ... } 533 // We instead want to output: 534 // function added<T extends string>(value: T) { ... } 535 const instanceTypeConstraint = instanceType.isTypeParameter() && instanceType.constraint && !isAnonymousObjectConstraintType(instanceType.constraint) 536 ? typeToAutoImportableTypeNode(checker, importAdder, instanceType.constraint, contextNode, scriptTarget, flags, tracker) 537 : undefined; 538 539 if (argumentTypeParameter) { 540 argumentTypeParameters.set(argumentTypeParameter, { argumentType: instanceType, constraint: instanceTypeConstraint }); 541 } 542 } 543 544 return { argumentTypeNodes, argumentTypeParameters: arrayFrom(argumentTypeParameters.entries()) }; 545} 546 547function isAnonymousObjectConstraintType(type: Type) { 548 return (type.flags & TypeFlags.Object) && (type as ObjectType).objectFlags === ObjectFlags.Anonymous; 549} 550 551function getFirstTypeParameterName(type: Type): string | undefined { 552 if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) { 553 for (const subType of (type as UnionType | IntersectionType).types) { 554 const subTypeName = getFirstTypeParameterName(subType); 555 if (subTypeName) { 556 return subTypeName; 557 } 558 } 559 } 560 561 return type.flags & TypeFlags.TypeParameter 562 ? type.getSymbol()?.getName() 563 : undefined; 564} 565 566function createDummyParameters(argCount: number, names: (string | undefined)[] | undefined, types: (TypeNode | undefined)[] | undefined, minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] { 567 const parameters: ParameterDeclaration[] = []; 568 const parameterNameCounts = new Map<string, number>(); 569 for (let i = 0; i < argCount; i++) { 570 const parameterName = names?.[i] || `arg${i}`; 571 const parameterNameCount = parameterNameCounts.get(parameterName); 572 parameterNameCounts.set(parameterName, (parameterNameCount || 0) + 1); 573 574 const newParameter = factory.createParameterDeclaration( 575 /*modifiers*/ undefined, 576 /*dotDotDotToken*/ undefined, 577 /*name*/ parameterName + (parameterNameCount || ""), 578 /*questionToken*/ minArgumentCount !== undefined && i >= minArgumentCount ? factory.createToken(SyntaxKind.QuestionToken) : undefined, 579 /*type*/ inJs ? undefined : types?.[i] || factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword), 580 /*initializer*/ undefined); 581 parameters.push(newParameter); 582 } 583 return parameters; 584} 585 586function createMethodImplementingSignatures( 587 checker: TypeChecker, 588 context: TypeConstructionContext, 589 enclosingDeclaration: ClassLikeDeclaration, 590 signatures: readonly Signature[], 591 name: PropertyName, 592 optional: boolean, 593 modifiers: readonly Modifier[] | undefined, 594 quotePreference: QuotePreference, 595 body: Block | undefined, 596): MethodDeclaration { 597 /** This is *a* signature with the maximal number of arguments, 598 * such that if there is a "maximal" signature without rest arguments, 599 * this is one of them. 600 */ 601 let maxArgsSignature = signatures[0]; 602 let minArgumentCount = signatures[0].minArgumentCount; 603 let someSigHasRestParameter = false; 604 for (const sig of signatures) { 605 minArgumentCount = Math.min(sig.minArgumentCount, minArgumentCount); 606 if (signatureHasRestParameter(sig)) { 607 someSigHasRestParameter = true; 608 } 609 if (sig.parameters.length >= maxArgsSignature.parameters.length && (!signatureHasRestParameter(sig) || signatureHasRestParameter(maxArgsSignature))) { 610 maxArgsSignature = sig; 611 } 612 } 613 const maxNonRestArgs = maxArgsSignature.parameters.length - (signatureHasRestParameter(maxArgsSignature) ? 1 : 0); 614 const maxArgsParameterSymbolNames = maxArgsSignature.parameters.map(symbol => symbol.name); 615 const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, /* types */ undefined, minArgumentCount, /*inJs*/ false); 616 617 if (someSigHasRestParameter) { 618 const restParameter = factory.createParameterDeclaration( 619 /*modifiers*/ undefined, 620 factory.createToken(SyntaxKind.DotDotDotToken), 621 maxArgsParameterSymbolNames[maxNonRestArgs] || "rest", 622 /*questionToken*/ maxNonRestArgs >= minArgumentCount ? factory.createToken(SyntaxKind.QuestionToken) : undefined, 623 factory.createArrayTypeNode(factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword)), 624 /*initializer*/ undefined); 625 parameters.push(restParameter); 626 } 627 628 return createStubbedMethod( 629 modifiers, 630 name, 631 optional, 632 /*typeParameters*/ undefined, 633 parameters, 634 getReturnTypeFromSignatures(signatures, checker, context, enclosingDeclaration), 635 quotePreference, 636 body); 637} 638 639function getReturnTypeFromSignatures(signatures: readonly Signature[], checker: TypeChecker, context: TypeConstructionContext, enclosingDeclaration: ClassLikeDeclaration): TypeNode | undefined { 640 if (length(signatures)) { 641 const type = checker.getUnionType(map(signatures, checker.getReturnTypeOfSignature)); 642 return checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context)); 643 } 644} 645 646function createStubbedMethod( 647 modifiers: readonly Modifier[] | undefined, 648 name: PropertyName, 649 optional: boolean, 650 typeParameters: readonly TypeParameterDeclaration[] | undefined, 651 parameters: readonly ParameterDeclaration[], 652 returnType: TypeNode | undefined, 653 quotePreference: QuotePreference, 654 body: Block | undefined 655): MethodDeclaration { 656 return factory.createMethodDeclaration( 657 modifiers, 658 /*asteriskToken*/ undefined, 659 name, 660 optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, 661 typeParameters, 662 parameters, 663 returnType, 664 body || createStubbedMethodBody(quotePreference)); 665} 666 667function createStubbedMethodBody(quotePreference: QuotePreference) { 668 return createStubbedBody(Diagnostics.Method_not_implemented.message, quotePreference); 669} 670 671/** @internal */ 672export function createStubbedBody(text: string, quotePreference: QuotePreference): Block { 673 return factory.createBlock( 674 [factory.createThrowStatement( 675 factory.createNewExpression( 676 factory.createIdentifier("Error"), 677 /*typeArguments*/ undefined, 678 // TODO Handle auto quote preference. 679 [factory.createStringLiteral(text, /*isSingleQuote*/ quotePreference === QuotePreference.Single)]))], 680 /*multiline*/ true); 681} 682 683/** @internal */ 684export function setJsonCompilerOptionValues( 685 changeTracker: textChanges.ChangeTracker, 686 configFile: TsConfigSourceFile, 687 options: [string, Expression][] 688) { 689 const tsconfigObjectLiteral = getTsConfigObjectLiteralExpression(configFile); 690 if (!tsconfigObjectLiteral) return undefined; 691 692 const compilerOptionsProperty = findJsonProperty(tsconfigObjectLiteral, "compilerOptions"); 693 if (compilerOptionsProperty === undefined) { 694 changeTracker.insertNodeAtObjectStart(configFile, tsconfigObjectLiteral, createJsonPropertyAssignment( 695 "compilerOptions", 696 factory.createObjectLiteralExpression(options.map(([optionName, optionValue]) => createJsonPropertyAssignment(optionName, optionValue)), /*multiLine*/ true))); 697 return; 698 } 699 700 const compilerOptions = compilerOptionsProperty.initializer; 701 if (!isObjectLiteralExpression(compilerOptions)) { 702 return; 703 } 704 705 for (const [optionName, optionValue] of options) { 706 const optionProperty = findJsonProperty(compilerOptions, optionName); 707 if (optionProperty === undefined) { 708 changeTracker.insertNodeAtObjectStart(configFile, compilerOptions, createJsonPropertyAssignment(optionName, optionValue)); 709 } 710 else { 711 changeTracker.replaceNode(configFile, optionProperty.initializer, optionValue); 712 } 713 } 714} 715 716/** @internal */ 717export function setJsonCompilerOptionValue( 718 changeTracker: textChanges.ChangeTracker, 719 configFile: TsConfigSourceFile, 720 optionName: string, 721 optionValue: Expression, 722) { 723 setJsonCompilerOptionValues(changeTracker, configFile, [[optionName, optionValue]]); 724} 725 726/** @internal */ 727export function createJsonPropertyAssignment(name: string, initializer: Expression) { 728 return factory.createPropertyAssignment(factory.createStringLiteral(name), initializer); 729} 730 731/** @internal */ 732export function findJsonProperty(obj: ObjectLiteralExpression, name: string): PropertyAssignment | undefined { 733 return find(obj.properties, (p): p is PropertyAssignment => isPropertyAssignment(p) && !!p.name && isStringLiteral(p.name) && p.name.text === name); 734} 735 736/** 737 * Given a type node containing 'import("./a").SomeType<import("./b").OtherType<...>>', 738 * returns an equivalent type reference node with any nested ImportTypeNodes also replaced 739 * with type references, and a list of symbols that must be imported to use the type reference. 740 * 741 * @internal 742 */ 743export function tryGetAutoImportableReferenceFromTypeNode(importTypeNode: TypeNode | undefined, scriptTarget: ScriptTarget) { 744 let symbols: Symbol[] | undefined; 745 const typeNode = visitNode(importTypeNode, visit); 746 if (symbols && typeNode) { 747 return { typeNode, symbols }; 748 } 749 750 function visit(node: TypeNode): TypeNode; 751 function visit(node: Node): Node { 752 if (isLiteralImportTypeNode(node) && node.qualifier) { 753 // Symbol for the left-most thing after the dot 754 const firstIdentifier = getFirstIdentifier(node.qualifier); 755 const name = getNameForExportedSymbol(firstIdentifier.symbol, scriptTarget); 756 const qualifier = name !== firstIdentifier.text 757 ? replaceFirstIdentifierOfEntityName(node.qualifier, factory.createIdentifier(name)) 758 : node.qualifier; 759 760 symbols = append(symbols, firstIdentifier.symbol); 761 const typeArguments = node.typeArguments?.map(visit); 762 return factory.createTypeReferenceNode(qualifier, typeArguments); 763 } 764 return visitEachChild(node, visit, nullTransformationContext); 765 } 766} 767 768function replaceFirstIdentifierOfEntityName(name: EntityName, newIdentifier: Identifier): EntityName { 769 if (name.kind === SyntaxKind.Identifier) { 770 return newIdentifier; 771 } 772 return factory.createQualifiedName(replaceFirstIdentifierOfEntityName(name.left, newIdentifier), name.right); 773} 774 775/** @internal */ 776export function importSymbols(importAdder: ImportAdder, symbols: readonly Symbol[]) { 777 symbols.forEach(s => importAdder.addImportFromExportedSymbol(s, /*isValidTypeOnlyUseSite*/ true)); 778} 779 780/** @internal */ 781export function findAncestorMatchingSpan(sourceFile: SourceFile, span: TextSpan): Node { 782 const end = textSpanEnd(span); 783 let token = getTokenAtPosition(sourceFile, span.start); 784 while (token.end < end) { 785 token = token.parent; 786 } 787 return token; 788} 789