• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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