Skip to content

Commit

Permalink
feat(51870): add a quick fix to add an additional parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk committed Nov 15, 2023
1 parent ca7a3af commit 1811cf5
Show file tree
Hide file tree
Showing 16 changed files with 351 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/compiler/diagnosticMessages.json
Expand Up @@ -7744,6 +7744,18 @@
"category": "Message",
"code": 95187
},
"Add missing parameter to '{0}'": {
"category": "Message",
"code": 95188
},
"Add missing parameters to '{0}'": {
"category": "Message",
"code": 95189
},
"Add all missing parameters": {
"category": "Message",
"code": 95190
},

"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down
1 change: 1 addition & 0 deletions src/services/_namespaces/ts.codefix.ts
Expand Up @@ -31,6 +31,7 @@ export * from "../codefixes/fixSpelling";
export * from "../codefixes/returnValueCorrect";
export * from "../codefixes/fixAddMissingMember";
export * from "../codefixes/fixAddMissingNewOperator";
export * from "../codefixes/fixAddMissingParam";
export * from "../codefixes/fixCannotFindModule";
export * from "../codefixes/fixClassDoesntImplementInheritedAbstractMember";
export * from "../codefixes/fixClassSuperMustPrecedeThisAccess";
Expand Down
150 changes: 150 additions & 0 deletions src/services/codefixes/fixAddMissingParam.ts
@@ -0,0 +1,150 @@
import {
append,
declarationNameToString,
Diagnostics,
factory,
findAncestor,
firstOrUndefined,
forEach,
FunctionLikeDeclaration,
getNameOfAccessExpression,
getNameOfDeclaration,
getTokenAtPosition,
isAccessExpression,
isCallExpression,
isFunctionLikeDeclaration,
isIdentifier,
isParameter,
isPropertyDeclaration,
isVariableDeclaration,
length,
Node,
NodeBuilderFlags,
SignatureDeclaration,
SignatureKind,
SourceFile,
SyntaxKind,
textChanges,
TypeChecker,
TypeNode,
} from "../_namespaces/ts";
import {
codeFixAll,
createCodeFixAction,
registerCodeFix,
} from "../_namespaces/ts.codefix";

const fixId = "addMissingParam";
const errorCodes = [Diagnostics.Expected_0_arguments_but_got_1.code];

registerCodeFix({
errorCodes,
fixIds: [fixId],
getCodeActions(context) {
const checker = context.program.getTypeChecker();
const info = getInfo(context.sourceFile, checker, context.span.start);
if (info === undefined) {
return undefined;
}
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, info));
return [createCodeFixAction(fixId, changes, [length(info.parameters) === 1 ? Diagnostics.Add_missing_parameter_to_0 : Diagnostics.Add_missing_parameters_to_0, info.name], fixId, Diagnostics.Add_all_missing_parameters)];
},
getAllCodeActions: context =>
codeFixAll(context, errorCodes, (changes, diag) => {
const checker = context.program.getTypeChecker();
const info = getInfo(context.sourceFile, checker, diag.start);
if (info) {
doChange(changes, context.sourceFile, info);
}
}),
});

interface Parameter {
readonly name: string;
readonly type: TypeNode;
}

interface Info {
readonly signature: SignatureDeclaration;
readonly name: string;
readonly parameters: Parameter[];
}

function getInfo(sourceFile: SourceFile, checker: TypeChecker, pos: number): Info | undefined {
const token = getTokenAtPosition(sourceFile, pos);
const callExpression = findAncestor(token, isCallExpression);
if (callExpression === undefined) {
return undefined;
}

const type = checker.getTypeAtLocation(callExpression.expression);
const signature = firstOrUndefined(checker.getSignaturesOfType(type, SignatureKind.Call));
if (signature && signature.declaration && isFunctionLikeDeclaration(signature.declaration)) {
const name = tryGetName(signature.declaration);
if (name === undefined) {
return undefined;
}

const parameters: Parameter[] = [];
const parametersLength = length(signature.declaration.parameters);
for (let i = parametersLength, index = 0; i < length(callExpression.arguments); i++) {
const arg = callExpression.arguments[i];
const expr = isAccessExpression(arg) ? getNameOfAccessExpression(arg) : arg;
append(parameters, {
name: expr && isIdentifier(expr) ? expr.text : `param${index++}`,
type: getArgumentType(checker, expr, signature.declaration),
});
}

return {
signature: signature.declaration,
name: declarationNameToString(name),
parameters,
};
}
return undefined;
}

function tryGetName(node: FunctionLikeDeclaration) {
const name = getNameOfDeclaration(node);
if (name) {
return name;
}

if (
isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ||
isPropertyDeclaration(node.parent) ||
isParameter(node.parent)
) {
return node.parent.name;
}
}

function getArgumentType(checker: TypeChecker, node: Node, enclosingDeclaration: Node) {
if (node) {
const typeNode = checker.typeToTypeNode(checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(node))), enclosingDeclaration, NodeBuilderFlags.NoTruncation);
if (typeNode) {
return typeNode;
}
}
return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword);
}

function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { signature, parameters }: Info) {
forEach(parameters, ({ name, type }, index) => {
const param = factory.createParameterDeclaration(
/*modifiers*/ undefined,
/*dotDotDotToken*/ undefined,
name,
/*questionToken*/ undefined,
type,
/*initializer*/ undefined,
);
if (length(signature.parameters) === 0 && index === 0) {
changes.insertNodeAt(sourceFile, signature.parameters.end, param);
}
else {
changes.insertNodeAtEndOfList(sourceFile, signature.parameters, param);
}
});
}
12 changes: 12 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam1.ts
@@ -0,0 +1,12 @@
/// <reference path="fourslash.ts" />

////[|function f() {}|]
////
////const a = 1;
////f(a);

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "f"],
index: 0,
newRangeContent: "function f(a: number) {}"
});
13 changes: 13 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam10.ts
@@ -0,0 +1,13 @@
/// <reference path="fourslash.ts" />

////[|function f() {}|]
////
////const a = 1;
////const b = "";
////f(a, b, true);

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameters_to_0.message, "f"],
index: 0,
newRangeContent: "function f(a: number, b: string, param0: boolean) {}"
});
14 changes: 14 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam11.ts
@@ -0,0 +1,14 @@
/// <reference path="fourslash.ts" />

////class C {
//// [|private p = () => {}|]
//// m(a: boolean) {
//// this.p(a);
//// }
////}

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "p"],
index: 0,
newRangeContent: "private p = (a: boolean) => {}"
});
11 changes: 11 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam12.ts
@@ -0,0 +1,11 @@
/// <reference path="fourslash.ts" />

////function f([|cb = () => {}|]) {
//// cb("");
////}

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "cb"],
index: 0,
newRangeContent: "cb = (param0: string) => {}"
});
13 changes: 13 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam2.ts
@@ -0,0 +1,13 @@
/// <reference path="fourslash.ts" />

////[|function f(a: number) {}|]
////
////const a = 1;
////const b = 1;
////f(a, b);

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "f"],
index: 0,
newRangeContent: "function f(a: number, b: number) {}"
});
16 changes: 16 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam3.ts
@@ -0,0 +1,16 @@
/// <reference path="fourslash.ts" />

////class C {
//// private a = 1;
////
//// [|m1() {}|]
//// m2() {
//// this.m1(this.a);
//// }
////}

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "m1"],
index: 0,
newRangeContent: "m1(a: number) {}"
});
11 changes: 11 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam4.ts
@@ -0,0 +1,11 @@
/// <reference path="fourslash.ts" />

////[|function f() {}|]
////
////f("");

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "f"],
index: 0,
newRangeContent: "function f(param0: string) {}"
});
11 changes: 11 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam5.ts
@@ -0,0 +1,11 @@
/// <reference path="fourslash.ts" />

////[|const f = function () {}|]
////
////f("");

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "f"],
index: 0,
newRangeContent: "const f = function (param0: string) {}"
});
11 changes: 11 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam6.ts
@@ -0,0 +1,11 @@
/// <reference path="fourslash.ts" />

////[|const f = () => {}|]
////
////f("");

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "f"],
index: 0,
newRangeContent: "const f = (param0: string) => {}"
});
14 changes: 14 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam7.ts
@@ -0,0 +1,14 @@
/// <reference path="fourslash.ts" />

////class C {
//// [|m1() {}|]
//// m2(a: boolean) {
//// this.m1(a);
//// }
////}

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "m1"],
index: 0,
newRangeContent: "m1(a: boolean) {}"
});
14 changes: 14 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam8.ts
@@ -0,0 +1,14 @@
/// <reference path="fourslash.ts" />

////[|function f(a: number) {}|]
////
////const a = 1;
////const b = 1;
////const c = 1;
////f(a, b, c);

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameters_to_0.message, "f"],
index: 0,
newRangeContent: "function f(a: number, b: number, c: number) {}"
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam9.ts
@@ -0,0 +1,10 @@
/// <reference path="fourslash.ts" />

////[|function f() {}|]
////f("", { x: 1 }, [ "" ], true);

verify.codeFix({
description: [ts.Diagnostics.Add_missing_parameters_to_0.message, "f"],
index: 0,
newRangeContent: "function f(param0: string, param1: { x: number; }, param2: string[], param3: boolean) {}"
});
38 changes: 38 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingParam_all.ts
@@ -0,0 +1,38 @@
/// <reference path='fourslash.ts' />

////[|function f1() {}|]
////
////const a = 1;
////const b = "";
////f1(a, b, true);
////
////function f2() {}
////f2("", { x: 1 }, [ "" ], true);
////
////class C {
//// [|m1() {}|]
//// m2(a: boolean) {
//// this.m1(a);
//// }
////}

verify.codeFixAll({
fixId: "addMissingParam",
fixAllDescription: ts.Diagnostics.Add_all_missing_parameters.message,
newFileContent:
`function f1(a: number, b: string, param0: boolean) {}
const a = 1;
const b = "";
f1(a, b, true);
function f2(param0: string, param1: { x: number; }, param2: string[], param3: boolean) {}
f2("", { x: 1 }, [ "" ], true);
class C {
m1(a: boolean) {}
m2(a: boolean) {
this.m1(a);
}
}`
});

0 comments on commit 1811cf5

Please sign in to comment.