diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 53492211042..7dbb43b94c7 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -32,7 +32,7 @@ namespace ts.codefix { // Always prefer to add a method declaration if possible. if (call) { - addMethodDeclaration(changes, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs, preferences); + addMethodDeclaration(context, changes, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs, preferences); } else { if (inJs) { @@ -184,11 +184,12 @@ namespace ts.codefix { inJs: boolean, preferences: UserPreferences, ): CodeFixAction | undefined { - const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs, preferences)); + const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(context, t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs, preferences)); return createCodeFixAction(fixName, changes, [makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0, token.text], fixId, Diagnostics.Add_all_missing_members); } function addMethodDeclaration( + context: CodeFixContextBase, changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, @@ -198,7 +199,7 @@ namespace ts.codefix { inJs: boolean, preferences: UserPreferences, ): void { - const methodDeclaration = createMethodFromCallExpression(callExpression, token.text, inJs, makeStatic, preferences); + const methodDeclaration = createMethodFromCallExpression(context, callExpression, token.text, inJs, makeStatic, preferences); const containingMethodDeclaration = getAncestor(callExpression, SyntaxKind.MethodDeclaration); if (containingMethodDeclaration && containingMethodDeclaration.parent === classDeclaration) { diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 58e0d2bebbf..5f26336a117 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -111,12 +111,27 @@ namespace ts.codefix { } export function createMethodFromCallExpression( + context: CodeFixContextBase, { typeArguments, arguments: args, parent: parent }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean, preferences: UserPreferences, ): MethodDeclaration { + const checker = context.program.getTypeChecker(); + const types = map(args, + arg => { + let type = checker.getTypeAtLocation(arg); + if (type === undefined) { + return undefined; + } + // Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {" + type = checker.getBaseTypeOfLiteralType(type); + return checker.typeToTypeNode(type); + }); + const names = map(args, arg => + isIdentifier(arg) ? arg.text : + isPropertyAccessExpression(arg) ? arg.name.text : undefined); return createMethod( /*decorators*/ undefined, /*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined, @@ -125,12 +140,12 @@ namespace ts.codefix { /*questionToken*/ undefined, /*typeParameters*/ inJs ? undefined : map(typeArguments, (_, i) => createTypeParameterDeclaration(CharacterCodes.T + typeArguments!.length - 1 <= CharacterCodes.Z ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`)), - /*parameters*/ createDummyParameters(args.length, /*names*/ undefined, /*minArgumentCount*/ undefined, inJs), + /*parameters*/ createDummyParameters(args.length, names, types, /*minArgumentCount*/ undefined, inJs), /*type*/ inJs ? undefined : createKeywordTypeNode(SyntaxKind.AnyKeyword), createStubbedMethodBody(preferences)); } - function createDummyParameters(argCount: number, names: string[] | undefined, minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] { + function createDummyParameters(argCount: number, names: (string | undefined)[] | undefined, types: (TypeNode | undefined)[] | undefined, minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] { const parameters: ParameterDeclaration[] = []; for (let i = 0; i < argCount; i++) { const newParameter = createParameter( @@ -139,7 +154,7 @@ namespace ts.codefix { /*dotDotDotToken*/ undefined, /*name*/ names && names[i] || `arg${i}`, /*questionToken*/ minArgumentCount !== undefined && i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined, - /*type*/ inJs ? undefined : createKeywordTypeNode(SyntaxKind.AnyKeyword), + /*type*/ inJs ? undefined : types && types[i] || createKeywordTypeNode(SyntaxKind.AnyKeyword), /*initializer*/ undefined); parameters.push(newParameter); } @@ -172,7 +187,7 @@ namespace ts.codefix { const maxNonRestArgs = maxArgsSignature.parameters.length - (maxArgsSignature.hasRestParameter ? 1 : 0); const maxArgsParameterSymbolNames = maxArgsSignature.parameters.map(symbol => symbol.name); - const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, minArgumentCount, /*inJs*/ false); + const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, /* types */ undefined, minArgumentCount, /*inJs*/ false); if (someSigHasRestParameter) { const anyArrayType = createArrayTypeNode(createKeywordTypeNode(SyntaxKind.AnyKeyword)); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index a9fe9a32556..eaac2305bc7 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -11625,7 +11625,7 @@ declare namespace ts.codefix { * @returns Empty string iff there are no member insertions. */ function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: ReadonlyArray, checker: TypeChecker, preferences: UserPreferences, out: (node: ClassElement) => void): void; - function createMethodFromCallExpression({ typeArguments, arguments: args, parent: parent }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean, preferences: UserPreferences): MethodDeclaration; + function createMethodFromCallExpression(context: CodeFixContextBase, { typeArguments, arguments: args, parent: parent }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean, preferences: UserPreferences): MethodDeclaration; } declare namespace ts.codefix { } diff --git a/tests/cases/fourslash/codeFixAddMissingMember9.ts b/tests/cases/fourslash/codeFixAddMissingMember9.ts new file mode 100644 index 00000000000..b583cd28e9a --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember9.ts @@ -0,0 +1,25 @@ +/// + +////class C { +//// z: boolean = true; +//// method() { +//// const x = 0; +//// this.y(x, "a", this.z); +//// } +////} + +verify.codeFixAll({ + fixId: "addMissingMember", + fixAllDescription: "Add all missing members", + newFileContent: +`class C { + z: boolean = true; + method() { + const x = 0; + this.y(x, "a", this.z); + } + y(x: number, arg1: string, z: boolean): any { + throw new Error("Method not implemented."); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixUndeclaredAcrossFiles1.ts b/tests/cases/fourslash/codeFixUndeclaredAcrossFiles1.ts index 29dcc2a5048..0b2669e41f8 100644 --- a/tests/cases/fourslash/codeFixUndeclaredAcrossFiles1.ts +++ b/tests/cases/fourslash/codeFixUndeclaredAcrossFiles1.ts @@ -10,6 +10,7 @@ //// let c = new X.C; //// c.m1(); //// c.y = {}; +//// c.m2(c); // @Filename: f1.ts //// export class C {[| @@ -21,14 +22,18 @@ verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); verify.rangeIs(` + m2(c: C): any { + throw new Error("Method not implemented."); + } y: { [x: string]: any; }; m1(): any { throw new Error("Method not implemented."); } static x: any; - static m0(arg0: any, arg1: any, arg2: any): any { + static m0(arg0: number, arg1: string, arg2: undefined[]): any { throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixUndeclaredAcrossFiles3.ts b/tests/cases/fourslash/codeFixUndeclaredAcrossFiles3.ts new file mode 100644 index 00000000000..2b07493f923 --- /dev/null +++ b/tests/cases/fourslash/codeFixUndeclaredAcrossFiles3.ts @@ -0,0 +1,26 @@ +/// + +// @allowJs: true +// @checkJs: true + +// @Filename: f3.ts +//// import { C } from "./f1"; +//// import { D } from "./f2"; +//// const c = new C(); +//// c.m0(new D()); + +// @Filename: f2.ts +//// export class D { } + +// @Filename: f1.ts +//// export class C {[| +//// |]x: number; +//// } + +verify.getAndApplyCodeFix(/*errorCode*/ undefined, 0); + +verify.rangeIs(` + m0(arg0: D): any { + throw new Error("Method not implemented."); + } +`); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixUndeclaredInStaticMethod.ts b/tests/cases/fourslash/codeFixUndeclaredInStaticMethod.ts index 9faa15745fc..a4ac2488350 100644 --- a/tests/cases/fourslash/codeFixUndeclaredInStaticMethod.ts +++ b/tests/cases/fourslash/codeFixUndeclaredInStaticMethod.ts @@ -20,7 +20,7 @@ verify.codeFix({ this.prop1 = 10; A.prop2 = "asdf"; } - static m1(arg0: any, arg1: any, arg2: any): any { + static m1(arg0: number, arg1: number, arg2: number): any { throw new Error("Method not implemented."); } }`, @@ -37,10 +37,10 @@ verify.codeFix({ this.prop1 = 10; A.prop2 = "asdf"; } - static m2(arg0: any, arg1: any): any { + static m2(arg0: number, arg1: number): any { throw new Error("Method not implemented."); } - static m1(arg0: any, arg1: any, arg2: any): any { + static m1(arg0: number, arg1: number, arg2: number): any { throw new Error("Method not implemented."); } }`, @@ -58,10 +58,10 @@ verify.codeFix({ this.prop1 = 10; A.prop2 = "asdf"; } - static m2(arg0: any, arg1: any): any { + static m2(arg0: number, arg1: number): any { throw new Error("Method not implemented."); } - static m1(arg0: any, arg1: any, arg2: any): any { + static m1(arg0: number, arg1: number, arg2: number): any { throw new Error("Method not implemented."); } }`, @@ -80,10 +80,10 @@ verify.codeFix({ this.prop1 = 10; A.prop2 = "asdf"; } - static m2(arg0: any, arg1: any): any { + static m2(arg0: number, arg1: number): any { throw new Error("Method not implemented."); } - static m1(arg0: any, arg1: any, arg2: any): any { + static m1(arg0: number, arg1: number, arg2: number): any { throw new Error("Method not implemented."); } }`, diff --git a/tests/cases/fourslash/codeFixUndeclaredMethod.ts b/tests/cases/fourslash/codeFixUndeclaredMethod.ts index 41e27dcf207..bf276379b0a 100644 --- a/tests/cases/fourslash/codeFixUndeclaredMethod.ts +++ b/tests/cases/fourslash/codeFixUndeclaredMethod.ts @@ -15,7 +15,7 @@ verify.codeFix({ index: 0, newFileContent: `class A { - foo1(arg0: any, arg1: any, arg2: any): any { + foo1(arg0: number, arg1: number, arg2: number): any { throw new Error("Method not implemented."); } constructor() { @@ -36,7 +36,7 @@ verify.codeFix({ foo2(): any { throw new Error("Method not implemented."); } - foo1(arg0: any, arg1: any, arg2: any): any { + foo1(arg0: number, arg1: number, arg2: number): any { throw new Error("Method not implemented."); } constructor() { @@ -60,7 +60,7 @@ verify.codeFix({ foo2(): any { throw new Error("Method not implemented."); } - foo1(arg0: any, arg1: any, arg2: any): any { + foo1(arg0: number, arg1: number, arg2: number): any { throw new Error("Method not implemented."); } constructor() { diff --git a/tests/cases/fourslash/codeFixUndeclaredMethodFunctionArgs.ts b/tests/cases/fourslash/codeFixUndeclaredMethodFunctionArgs.ts new file mode 100644 index 00000000000..1c06cfc3afd --- /dev/null +++ b/tests/cases/fourslash/codeFixUndeclaredMethodFunctionArgs.ts @@ -0,0 +1,31 @@ +/// + +//// class A {[| +//// |]constructor() { +//// this.foo1(() => 1, () => "2", () => false); +//// this.foo2((a: number) => a, (b: string) => b, (c: boolean) => c); +//// } +//// } + +verify.codeFix({ + description: "Declare method 'foo1'", + index: 0, + newRangeContent: ` + foo1(arg0: () => number, arg1: () => string, arg2: () => boolean): any { + throw new Error("Method not implemented."); + } + `, +}); + +verify.codeFix({ + description: "Declare method 'foo2'", + index: 0, + newRangeContent: ` + foo2(arg0: (a: number) => number, arg1: (b: string) => string, arg2: (c: boolean) => boolean): any { + throw new Error("Method not implemented."); + } + foo1(arg0: () => number, arg1: () => string, arg2: () => boolean): any { + throw new Error("Method not implemented."); + } + ` +}); diff --git a/tests/cases/fourslash/codeFixUndeclaredMethodObjectLiteralArgs.ts b/tests/cases/fourslash/codeFixUndeclaredMethodObjectLiteralArgs.ts new file mode 100644 index 00000000000..bd434cf9d9d --- /dev/null +++ b/tests/cases/fourslash/codeFixUndeclaredMethodObjectLiteralArgs.ts @@ -0,0 +1,17 @@ +/// + +//// class A {[| +//// |]constructor() { +//// this.foo1(null, {}, { a: 1, b: "2"}); +//// } +//// } + +verify.codeFix({ + description: "Declare method 'foo1'", + index: 0, + newRangeContent: ` + foo1(arg0: null, arg1: {}, arg2: { a: number; b: string; }): any { + throw new Error("Method not implemented."); + } + ` +});