Skip to content

Commit 9338f6a

Browse files
committed
feat(51870): add a quick fix to add an additional parameters
1 parent 93e6b9d commit 9338f6a

20 files changed

+503
-2
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7784,6 +7784,18 @@
77847784
"category": "Message",
77857785
"code": 95187
77867786
},
7787+
"Add missing parameter to '{0}'": {
7788+
"category": "Message",
7789+
"code": 95188
7790+
},
7791+
"Add missing parameters to '{0}'": {
7792+
"category": "Message",
7793+
"code": 95189
7794+
},
7795+
"Add all missing parameters": {
7796+
"category": "Message",
7797+
"code": 95190
7798+
},
77877799

77887800
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
77897801
"category": "Error",

src/services/_namespaces/ts.codefix.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export * from "../codefixes/fixSpelling";
3131
export * from "../codefixes/returnValueCorrect";
3232
export * from "../codefixes/fixAddMissingMember";
3333
export * from "../codefixes/fixAddMissingNewOperator";
34+
export * from "../codefixes/fixAddMissingParam";
3435
export * from "../codefixes/fixCannotFindModule";
3536
export * from "../codefixes/fixClassDoesntImplementInheritedAbstractMember";
3637
export * from "../codefixes/fixClassSuperMustPrecedeThisAccess";
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import {
2+
append,
3+
ArrowFunction,
4+
declarationNameToString,
5+
DiagnosticOrDiagnosticAndArguments,
6+
Diagnostics,
7+
factory,
8+
find,
9+
findAncestor,
10+
first,
11+
forEach,
12+
FunctionDeclaration,
13+
FunctionExpression,
14+
FunctionLikeDeclaration,
15+
getNameOfAccessExpression,
16+
getNameOfDeclaration,
17+
getTokenAtPosition,
18+
isAccessExpression,
19+
isCallExpression,
20+
isIdentifier,
21+
isParameter,
22+
isPropertyDeclaration,
23+
isSourceFileFromLibrary,
24+
isVariableDeclaration,
25+
last,
26+
lastOrUndefined,
27+
length,
28+
map,
29+
MethodDeclaration,
30+
Node,
31+
NodeBuilderFlags,
32+
ParameterDeclaration,
33+
Program,
34+
SignatureKind,
35+
sort,
36+
SourceFile,
37+
SyntaxKind,
38+
textChanges,
39+
Type,
40+
TypeChecker,
41+
} from "../_namespaces/ts";
42+
import {
43+
codeFixAll,
44+
createCodeFixAction,
45+
registerCodeFix,
46+
} from "../_namespaces/ts.codefix";
47+
48+
const fixId = "addMissingParam";
49+
const errorCodes = [Diagnostics.Expected_0_arguments_but_got_1.code];
50+
51+
registerCodeFix({
52+
errorCodes,
53+
fixIds: [fixId],
54+
getCodeActions(context) {
55+
const info = getInfo(context.sourceFile, context.program, context.span.start);
56+
if (info === undefined) return undefined;
57+
58+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, info));
59+
return [createCodeFixAction(fixId, changes, info.description, fixId, Diagnostics.Add_all_missing_parameters)];
60+
},
61+
getAllCodeActions: context =>
62+
codeFixAll(context, errorCodes, (changes, diag) => {
63+
const info = getInfo(context.sourceFile, context.program, diag.start);
64+
if (info) {
65+
doChange(changes, context.sourceFile, info);
66+
}
67+
}),
68+
});
69+
70+
type ConvertableSignatureDeclaration =
71+
| FunctionDeclaration
72+
| FunctionExpression
73+
| ArrowFunction
74+
| MethodDeclaration;
75+
76+
interface SignatureInfo {
77+
readonly description: DiagnosticOrDiagnosticAndArguments;
78+
readonly newParameters: ParameterInfo[];
79+
readonly declarations: ConvertableSignatureDeclaration[];
80+
readonly overload: ConvertableSignatureDeclaration | undefined;
81+
}
82+
83+
interface ParameterInfo {
84+
readonly pos: number;
85+
readonly declaration: ParameterDeclaration;
86+
}
87+
88+
function getInfo(sourceFile: SourceFile, program: Program, pos: number): SignatureInfo | undefined {
89+
const token = getTokenAtPosition(sourceFile, pos);
90+
const callExpression = findAncestor(token, isCallExpression);
91+
if (callExpression === undefined || length(callExpression.arguments) === 0) return undefined;
92+
93+
const checker = program.getTypeChecker();
94+
const type = checker.getTypeAtLocation(callExpression.expression);
95+
const signatures = checker.getSignaturesOfType(type, SignatureKind.Call);
96+
const signature = lastOrUndefined(sort(signatures, (a, b) => length(a.parameters) - length(b.parameters)));
97+
if (
98+
signature &&
99+
signature.declaration &&
100+
isConvertableSignatureDeclaration(signature.declaration)
101+
) {
102+
const declaration = (signature.declaration.body === undefined ? find(signature.declaration.symbol.declarations, d => isConvertableSignatureDeclaration(d) && !!d.body) : signature.declaration) as ConvertableSignatureDeclaration;
103+
if (declaration === undefined) {
104+
return undefined;
105+
}
106+
107+
isSourceFileFromLibrary(program, declaration.getSourceFile());
108+
109+
const overload = signature.declaration.body === undefined ? signature.declaration : undefined;
110+
if (overload && length(overload.parameters) !== length(declaration.parameters)) return undefined;
111+
112+
const name = tryGetName(declaration);
113+
if (name === undefined) return undefined;
114+
115+
const declarations: ConvertableSignatureDeclaration[] = append([declaration], overload);
116+
const newParameters: ParameterInfo[] = [];
117+
const parametersLength = length(declaration.parameters);
118+
const argumentsLength = length(callExpression.arguments);
119+
for (let i = 0, pos = 0, paramIndex = 0; i < argumentsLength; i++) {
120+
const arg = callExpression.arguments[i];
121+
const expr = isAccessExpression(arg) ? getNameOfAccessExpression(arg) : arg;
122+
const type = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(arg)));
123+
const parameter = pos < parametersLength ? declaration.parameters[pos] : undefined;
124+
if (
125+
parameter &&
126+
checker.isTypeAssignableTo(type, checker.getTypeAtLocation(parameter))
127+
) {
128+
pos++;
129+
}
130+
else {
131+
const newParameter = {
132+
pos: i,
133+
declaration: factory.createParameterDeclaration(
134+
/*modifiers*/ undefined,
135+
/*dotDotDotToken*/ undefined,
136+
expr && isIdentifier(expr) ? expr.text : `p${paramIndex++}`,
137+
/*questionToken*/ undefined,
138+
typeToTypeNode(checker, type, declaration),
139+
/*initializer*/ undefined,
140+
),
141+
};
142+
append(newParameters, newParameter);
143+
}
144+
}
145+
return {
146+
declarations,
147+
newParameters,
148+
overload,
149+
description: [
150+
length(newParameters) > 1 ? Diagnostics.Add_missing_parameters_to_0 : Diagnostics.Add_missing_parameter_to_0,
151+
declarationNameToString(name),
152+
],
153+
};
154+
}
155+
return undefined;
156+
}
157+
158+
function tryGetName(node: FunctionLikeDeclaration) {
159+
const name = getNameOfDeclaration(node);
160+
if (name) {
161+
return name;
162+
}
163+
164+
if (
165+
isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ||
166+
isPropertyDeclaration(node.parent) ||
167+
isParameter(node.parent)
168+
) {
169+
return node.parent.name;
170+
}
171+
}
172+
173+
function typeToTypeNode(checker: TypeChecker, type: Type, enclosingDeclaration: Node) {
174+
const typeNode = checker.typeToTypeNode(checker.getWidenedType(type), enclosingDeclaration, NodeBuilderFlags.NoTruncation);
175+
return typeNode ? typeNode : factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword);
176+
}
177+
178+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { declarations, newParameters }: SignatureInfo) {
179+
forEach(declarations, declaration => {
180+
if (length(declaration.parameters)) {
181+
changes.replaceNodeRangeWithNodes(
182+
sourceFile,
183+
first(declaration.parameters),
184+
last(declaration.parameters),
185+
updateParameters(declaration, newParameters),
186+
{
187+
joiner: ", ",
188+
indentation: 0,
189+
leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll,
190+
trailingTriviaOption: textChanges.TrailingTriviaOption.Include,
191+
},
192+
);
193+
}
194+
else {
195+
forEach(updateParameters(declaration, newParameters), (parameter, index) => {
196+
if (length(declaration.parameters) === 0 && index === 0) {
197+
changes.insertNodeAt(sourceFile, declaration.parameters.end, parameter);
198+
}
199+
else {
200+
changes.insertNodeAtEndOfList(sourceFile, declaration.parameters, parameter);
201+
}
202+
});
203+
}
204+
});
205+
}
206+
207+
function isConvertableSignatureDeclaration(node: Node): node is ConvertableSignatureDeclaration {
208+
switch (node.kind) {
209+
case SyntaxKind.FunctionDeclaration:
210+
case SyntaxKind.FunctionExpression:
211+
case SyntaxKind.MethodDeclaration:
212+
case SyntaxKind.ArrowFunction:
213+
return true;
214+
default:
215+
return false;
216+
}
217+
}
218+
219+
function updateParameters(node: ConvertableSignatureDeclaration, newParameters: readonly ParameterInfo[]) {
220+
const parameters = map(node.parameters, p =>
221+
factory.createParameterDeclaration(
222+
p.modifiers,
223+
p.dotDotDotToken,
224+
p.name,
225+
p.questionToken,
226+
p.type,
227+
p.initializer,
228+
));
229+
for (const { pos, declaration } of newParameters) {
230+
const prev = pos > 0 ? parameters[pos - 1] : undefined;
231+
parameters.splice(
232+
pos,
233+
0,
234+
factory.updateParameterDeclaration(
235+
declaration,
236+
declaration.modifiers,
237+
declaration.dotDotDotToken,
238+
declaration.name,
239+
prev && prev.questionToken ? factory.createToken(SyntaxKind.QuestionToken) : declaration.questionToken,
240+
declaration.type,
241+
declaration.initializer,
242+
),
243+
);
244+
}
245+
return parameters;
246+
}

tests/cases/fourslash/arityErrorAfterSignatureHelp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ verify.signatureHelp({
1818
kind: "retrigger"
1919
}
2020
})
21-
verify.not.codeFixAvailable() // trigger typecheck
21+
verify.not.codeFixAvailable(); // trigger typecheck
2222
verify.errorExistsBetweenMarkers("1", "2");
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////[|function f() {}|]
4+
////
5+
////const a = 1;
6+
////f(a);
7+
8+
verify.codeFix({
9+
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "f"],
10+
index: 0,
11+
newRangeContent: "function f(a: number) {}"
12+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////[|function f() {}|]
4+
////
5+
////const a = 1;
6+
////const b = "";
7+
////f(a, b, true);
8+
9+
verify.codeFix({
10+
description: [ts.Diagnostics.Add_missing_parameters_to_0.message, "f"],
11+
index: 0,
12+
newRangeContent: "function f(a: number, b: string, p0: boolean) {}"
13+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////class C {
4+
//// [|private p = () => {}|]
5+
//// m(a: boolean) {
6+
//// this.p(a);
7+
//// }
8+
////}
9+
10+
verify.codeFix({
11+
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "p"],
12+
index: 0,
13+
newRangeContent: "private p = (a: boolean) => {}"
14+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function f([|cb = () => {}|]) {
4+
//// cb("");
5+
////}
6+
7+
verify.codeFix({
8+
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "cb"],
9+
index: 0,
10+
newRangeContent: "cb = (p0: string) => {}"
11+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function f(a: string): string;
4+
////function f(a: string, b: number): string;
5+
////function f(a: string, b?: number): string {
6+
//// return "";
7+
////}
8+
////f("", 1, "");
9+
10+
verify.codeFix({
11+
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "f"],
12+
index: 0,
13+
newFileContent:
14+
`function f(a: string): string;
15+
function f(a: string, b: number, p0: string): string;
16+
function f(a: string, b?: number, p0?: string): string {
17+
return "";
18+
}
19+
f("", 1, "");`
20+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function f(a: string): string;
4+
////function f(a: string, b: number): string;
5+
////function f(a: string, b?: number): string {
6+
//// return "";
7+
////}
8+
////f("", "", 1);
9+
10+
verify.codeFix({
11+
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "f"],
12+
index: 0,
13+
newFileContent:
14+
`function f(a: string): string;
15+
function f(a: string, p0: string, b: number): string;
16+
function f(a: string, p0: string, b?: number): string {
17+
return "";
18+
}
19+
f("", "", 1);`
20+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////[|function f(a: number) {}|]
4+
////
5+
////const a = 1;
6+
////const b = 1;
7+
////f(a, b);
8+
9+
verify.codeFix({
10+
description: [ts.Diagnostics.Add_missing_parameter_to_0.message, "f"],
11+
index: 0,
12+
newRangeContent: "function f(a: number, b: number) {}"
13+
});

0 commit comments

Comments
 (0)