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