mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 03:23:08 -06:00
Merge pull request #22226 from markusjohnsson/issue_22180
Infer parameter names and types when applying Delcare Method codefix
This commit is contained in:
commit
392b7eaca4
@ -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) {
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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 {
|
||||
}
|
||||
|
||||
25
tests/cases/fourslash/codeFixAddMissingMember9.ts
Normal file
25
tests/cases/fourslash/codeFixAddMissingMember9.ts
Normal 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.");
|
||||
}
|
||||
}`,
|
||||
});
|
||||
@ -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.");
|
||||
}
|
||||
`);
|
||||
|
||||
26
tests/cases/fourslash/codeFixUndeclaredAcrossFiles3.ts
Normal file
26
tests/cases/fourslash/codeFixUndeclaredAcrossFiles3.ts
Normal 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.");
|
||||
}
|
||||
`);
|
||||
@ -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.");
|
||||
}
|
||||
}`,
|
||||
|
||||
@ -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() {
|
||||
|
||||
31
tests/cases/fourslash/codeFixUndeclaredMethodFunctionArgs.ts
Normal file
31
tests/cases/fourslash/codeFixUndeclaredMethodFunctionArgs.ts
Normal 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.");
|
||||
}
|
||||
`
|
||||
});
|
||||
@ -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.");
|
||||
}
|
||||
`
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user