Infer parameter names and types when applying Delcare Method codefix (#22180)

This commit is contained in:
Markus Johnsson
2018-02-28 08:55:07 +01:00
parent c1128d6957
commit 627c95e3f5
7 changed files with 87 additions and 21 deletions

View File

@@ -31,7 +31,7 @@ namespace ts.codefix {
// Always prefer to add a method declaration if possible.
if (call) {
addMethodDeclaration(changes, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs);
addMethodDeclaration(context, changes, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs);
}
else {
if (inJs) {
@@ -181,14 +181,14 @@ namespace ts.codefix {
return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, fixId: undefined };
}
function getActionForMethodDeclaration(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFixAction | undefined {
function getActionForMethodDeclaration(context: CodeFixContextBase, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFixAction | undefined {
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]);
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs));
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(context, t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs));
return { description, changes, fixId };
}
function addMethodDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean) {
const methodDeclaration = createMethodFromCallExpression(callExpression, token.text, inJs, makeStatic);
function addMethodDeclaration(context: CodeFixContextBase, changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean) {
const methodDeclaration = createMethodFromCallExpression(context, callExpression, token.text, inJs, makeStatic);
changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration);
}
}

View File

@@ -107,7 +107,18 @@ namespace ts.codefix {
return nodes && createNodeArray(nodes.map(getSynthesizedDeepClone));
}
export function createMethodFromCallExpression({ typeArguments, arguments: args }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean): MethodDeclaration {
export function createMethodFromCallExpression(context: CodeFixContextBase, { typeArguments, arguments: args }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean): MethodDeclaration {
const checker = context.program.getTypeChecker();
const types = map(args,
(arg) => {
let type = checker.getTypeAtLocation(arg);
// 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,
@@ -116,12 +127,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());
}
function createDummyParameters(argCount: number, names: string[] | undefined, minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] {
function createDummyParameters(argCount: number, names: string[] | undefined, types: TypeNode[], minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] {
const parameters: ParameterDeclaration[] = [];
for (let i = 0; i < argCount; i++) {
const newParameter = createParameter(
@@ -130,7 +141,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);
}
@@ -157,7 +168,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));

View File

@@ -0,0 +1,24 @@
/// <reference path='fourslash.ts' />
////class C {
//// z: boolean = true;
//// method() {
//// const x = 0;
//// this.y(x, "a", this.z);
//// }
////}
verify.codeFixAll({
fixId: "addMissingMember",
newFileContent:
`class C {
y(x: number, arg1: string, z: boolean): any {
throw new Error("Method not implemented.");
}
z: boolean = true;
method() {
const x = 0;
this.y(x, "a", this.z);
}
}`,
});

View File

@@ -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: {};
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.");
}
`);

View File

@@ -0,0 +1,26 @@
/// <reference path='fourslash.ts' />
// @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.");
}
`);

View File

@@ -13,7 +13,7 @@ verify.codeFix({
description: "Declare static method 'm1'",
index: 0,
newRangeContent: `
static m1(arg0: any, arg1: any, arg2: any): any {
static m1(arg0: number, arg1: number, arg2: number): any {
throw new Error("Method not implemented.");
}
`,
@@ -23,10 +23,10 @@ verify.codeFix({
description: "Declare static method 'm2'",
index: 0,
newRangeContent: `
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.");
}
`,
@@ -37,10 +37,10 @@ verify.codeFix({
index: 0,
newRangeContent: `
static prop1: number;
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.");
}
`,
@@ -52,10 +52,10 @@ verify.codeFix({
newRangeContent: `
static prop2: string;
static prop1: number;
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.");
}
`,

View File

@@ -14,7 +14,7 @@ verify.codeFix({
description: "Declare method 'foo1'",
index: 0,
newRangeContent: `
foo1(arg0: any, arg1: any, arg2: any): any {
foo1(arg0: number, arg1: number, arg2: number): any {
throw new Error("Method not implemented.");
}
`,
@@ -27,7 +27,7 @@ verify.codeFix({
foo2<T, U, V, W, X, Y, Z>(): 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.");
}
`
@@ -43,7 +43,7 @@ verify.codeFix({
foo2<T, U, V, W, X, Y, Z>(): 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.");
}
`