Merge pull request #22226 from markusjohnsson/issue_22180

Infer parameter names and types when applying Delcare Method codefix
This commit is contained in:
Mohamed Hegazy 2018-06-19 09:47:19 -07:00 committed by GitHub
commit 392b7eaca4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 139 additions and 19 deletions

View File

@ -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) {

View File

@ -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));

View File

@ -11625,7 +11625,7 @@ declare namespace ts.codefix {
* @returns Empty string iff there are no member insertions.
*/
function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: ReadonlyArray<Symbol>, 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 {
}

View File

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

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

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

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

View File

@ -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<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.");
}
constructor() {
@ -60,7 +60,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.");
}
constructor() {

View File

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

View File

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