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