Skip to content

Commit a8e1ad4

Browse files
authored
fix(37781): import missing argument types for a new method (microsoft#37857)
1 parent 5f46d42 commit a8e1ad4

6 files changed

+139
-16
lines changed

src/services/codefixes/fixAddMissingMember.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ namespace ts.codefix {
2121
return [createCodeFixAction(fixName, changes, [Diagnostics.Add_missing_enum_member_0, token.text], fixId, Diagnostics.Add_all_missing_members)];
2222
}
2323
const { parentDeclaration, declSourceFile, inJs, makeStatic, token, call } = info;
24-
const methodCodeAction = call && getActionForMethodDeclaration(context, declSourceFile, parentDeclaration, token, call, makeStatic, inJs, context.preferences);
24+
const methodCodeAction = call && getActionForMethodDeclaration(context, declSourceFile, parentDeclaration, token, call, makeStatic, inJs);
2525
const addMember = inJs && !isInterfaceDeclaration(parentDeclaration) ?
2626
singleElementArray(getActionsForAddMissingMemberInJavascriptFile(context, declSourceFile, parentDeclaration, token, makeStatic)) :
2727
getActionsForAddMissingMemberInTypeScriptFile(context, declSourceFile, parentDeclaration, token, makeStatic);
2828
return concatenate(singleElementArray(methodCodeAction), addMember);
2929
},
3030
fixIds: [fixId],
3131
getAllCodeActions: context => {
32-
const { program, preferences } = context;
32+
const { program } = context;
3333
const checker = program.getTypeChecker();
3434
const seen = createMap<true>();
3535

@@ -66,7 +66,7 @@ namespace ts.codefix {
6666

6767
// Always prefer to add a method declaration if possible.
6868
if (call && !isPrivateIdentifier(token)) {
69-
addMethodDeclaration(context, changes, declSourceFile, parentDeclaration, token, call, makeStatic, inJs, preferences);
69+
addMethodDeclaration(context, changes, declSourceFile, parentDeclaration, token, call, makeStatic, inJs);
7070
}
7171
else {
7272
if (inJs && !isInterfaceDeclaration(parentDeclaration)) {
@@ -304,12 +304,11 @@ namespace ts.codefix {
304304
token: Identifier | PrivateIdentifier,
305305
callExpression: CallExpression,
306306
makeStatic: boolean,
307-
inJs: boolean,
308-
preferences: UserPreferences,
307+
inJs: boolean
309308
): CodeFixAction | undefined {
310309
// Private methods are not implemented yet.
311310
if (isPrivateIdentifier(token)) { return undefined; }
312-
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(context, t, declSourceFile, classDeclaration, token, callExpression, makeStatic, inJs, preferences));
311+
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(context, t, declSourceFile, classDeclaration, token, callExpression, makeStatic, inJs));
313312
return createCodeFixAction(fixName, changes, [makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0, token.text], fixId, Diagnostics.Add_all_missing_members);
314313
}
315314

@@ -321,18 +320,18 @@ namespace ts.codefix {
321320
token: Identifier,
322321
callExpression: CallExpression,
323322
makeStatic: boolean,
324-
inJs: boolean,
325-
preferences: UserPreferences,
323+
inJs: boolean
326324
): void {
327-
const methodDeclaration = createMethodFromCallExpression(context, callExpression, token.text, inJs, makeStatic, preferences, typeDecl);
325+
const importAdder = createImportAdder(declSourceFile, context.program, context.preferences, context.host);
326+
const methodDeclaration = createMethodFromCallExpression(context, callExpression, token.text, inJs, makeStatic, typeDecl, importAdder);
328327
const containingMethodDeclaration = getAncestor(callExpression, SyntaxKind.MethodDeclaration);
329-
330328
if (containingMethodDeclaration && containingMethodDeclaration.parent === typeDecl) {
331329
changeTracker.insertNodeAfter(declSourceFile, containingMethodDeclaration, methodDeclaration);
332330
}
333331
else {
334332
changeTracker.insertNodeAtClassStart(declSourceFile, typeDecl, methodDeclaration);
335333
}
334+
importAdder.writeFixes(changeTracker);
336335
}
337336

338337
function addEnumMemberDeclaration(changes: textChanges.ChangeTracker, checker: TypeChecker, token: Identifier, enumDeclaration: EnumDeclaration) {

src/services/codefixes/helpers.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,16 +218,26 @@ namespace ts.codefix {
218218
methodName: string,
219219
inJs: boolean,
220220
makeStatic: boolean,
221-
preferences: UserPreferences,
222221
contextNode: Node,
222+
importAdder: ImportAdder
223223
): MethodDeclaration {
224224
const body = !isInterfaceDeclaration(contextNode);
225225
const { typeArguments, arguments: args, parent } = call;
226+
const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions());
226227
const checker = context.program.getTypeChecker();
227228
const tracker = getNoopSymbolTrackerWithResolver(context);
228-
const types = map(args, arg =>
229-
// Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {"
230-
checker.typeToTypeNode(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(arg)), contextNode, /*flags*/ undefined, tracker));
229+
const types = map(args, arg => {
230+
const type = checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(arg));
231+
const typeNode = checker.typeToTypeNode(type, contextNode, /*flags*/ undefined, tracker);
232+
if (typeNode?.kind === SyntaxKind.ImportType) {
233+
const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
234+
if (importableReference) {
235+
importSymbols(importAdder, importableReference.symbols);
236+
return importableReference.typeReference;
237+
}
238+
}
239+
return typeNode;
240+
});
231241
const names = map(args, arg =>
232242
isIdentifier(arg) ? arg.text : isPropertyAccessExpression(arg) && isIdentifier(arg.name) ? arg.name.text : undefined);
233243
const contextualType = checker.getContextualType(call);
@@ -242,7 +252,7 @@ namespace ts.codefix {
242252
createTypeParameterDeclaration(CharacterCodes.T + typeArguments!.length - 1 <= CharacterCodes.Z ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`)),
243253
/*parameters*/ createDummyParameters(args.length, names, types, /*minArgumentCount*/ undefined, inJs),
244254
/*type*/ returnType,
245-
body ? createStubbedMethodBody(preferences) : undefined);
255+
body ? createStubbedMethodBody(context.preferences) : undefined);
246256
}
247257

248258
function createDummyParameters(argCount: number, names: (string | undefined)[] | undefined, types: (TypeNode | undefined)[] | undefined, minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] {

tests/cases/fourslash/codeFixUndeclaredAcrossFiles3.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
verify.getAndApplyCodeFix(/*errorCode*/ undefined, 0);
2121

2222
verify.rangeIs(`
23-
m0(arg0: import("./f2").D) {
23+
m0(arg0: D) {
2424
throw new Error("Method not implemented.");
2525
}
2626
`);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: a.ts
4+
////export interface A {
5+
//// x: number;
6+
////}
7+
////export function create(fn: (args: A) => void) {}
8+
9+
// @Filename: b.ts
10+
////import { create } from "./a";
11+
////class B {
12+
//// bar() {
13+
//// create(args => this.foo(args));
14+
//// }
15+
////}
16+
17+
goTo.file("b.ts");
18+
verify.codeFix({
19+
description: [ts.Diagnostics.Declare_method_0.message, "foo"],
20+
index: 0,
21+
newFileContent:
22+
`import { create, A } from "./a";
23+
class B {
24+
bar() {
25+
create(args => this.foo(args));
26+
}
27+
foo(args: A): void {
28+
throw new Error("Method not implemented.");
29+
}
30+
}`
31+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: a.ts
4+
////export interface A {
5+
//// x: number;
6+
////}
7+
8+
// @Filename: b.ts
9+
////import { A } from "./a";
10+
////export interface B<T> {
11+
//// payload: T;
12+
////}
13+
////export function create(fn: (args: B<A>) => void) {}
14+
15+
// @Filename: c.ts
16+
////import { create } from "./b";
17+
////class C {
18+
//// bar() {
19+
//// create(args => this.foo(args));
20+
//// }
21+
////}
22+
23+
goTo.file("c.ts");
24+
verify.codeFix({
25+
description: [ts.Diagnostics.Declare_method_0.message, "foo"],
26+
index: 0,
27+
newFileContent:
28+
`import { create, B } from "./b";
29+
import { A } from "./a";
30+
class C {
31+
bar() {
32+
create(args => this.foo(args));
33+
}
34+
foo(args: B<A>): void {
35+
throw new Error("Method not implemented.");
36+
}
37+
}`
38+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: a.ts
4+
////export interface A {
5+
//// x: number;
6+
////}
7+
8+
// @Filename: b.ts
9+
////export interface B<T> {
10+
//// payload: T;
11+
////}
12+
13+
// @Filename: c.ts
14+
////import { A } from "./a";
15+
////import { B } from "./b";
16+
////export interface C<T> {
17+
//// payload: T;
18+
////}
19+
////export function create(fn: (args: C<B<A>>) => void) {}
20+
21+
// @Filename: d.ts
22+
////import { create } from "./c";
23+
////class D {
24+
//// bar() {
25+
//// create(args => this.foo(args));
26+
//// }
27+
////}
28+
29+
goTo.file("d.ts");
30+
verify.codeFix({
31+
description: [ts.Diagnostics.Declare_method_0.message, "foo"],
32+
index: 0,
33+
newFileContent:
34+
`import { create, C } from "./c";
35+
import { B } from "./b";
36+
import { A } from "./a";
37+
class D {
38+
bar() {
39+
create(args => this.foo(args));
40+
}
41+
foo(args: C<B<A>>): void {
42+
throw new Error("Method not implemented.");
43+
}
44+
}`
45+
});

0 commit comments

Comments
 (0)