Skip to content

Commit 53ee194

Browse files
TypeScript Bota-tarasyuk
TypeScript Bot
andauthored
Cherry-pick PR #38213 into release-3.9 (#38281)
Component commits: 17f0dfb fix(38177): add auto-import for missing argument type in new functions/methods Co-authored-by: Alexander T <[email protected]>
1 parent 391a159 commit 53ee194

File tree

8 files changed

+294
-14
lines changed

8 files changed

+294
-14
lines changed

src/services/codefixes/helpers.ts

+15-13
Original file line numberDiff line numberDiff line change
@@ -226,18 +226,8 @@ namespace ts.codefix {
226226
const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions());
227227
const checker = context.program.getTypeChecker();
228228
const tracker = getNoopSymbolTrackerWithResolver(context);
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-
});
229+
const types = map(args, arg =>
230+
typeToAutoImportableTypeNode(checker, importAdder, checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(arg)), contextNode, scriptTarget, /*flags*/ undefined, tracker));
241231
const names = map(args, arg =>
242232
isIdentifier(arg) ? arg.text : isPropertyAccessExpression(arg) && isIdentifier(arg.name) ? arg.name.text : undefined);
243233
const contextualType = checker.getContextualType(call);
@@ -255,6 +245,18 @@ namespace ts.codefix {
255245
body ? createStubbedMethodBody(context.preferences) : undefined);
256246
}
257247

248+
export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, type: Type, contextNode: Node, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined {
249+
const typeNode = checker.typeToTypeNode(type, contextNode, flags, tracker);
250+
if (typeNode && isImportTypeNode(typeNode)) {
251+
const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
252+
if (importableReference) {
253+
importSymbols(importAdder, importableReference.symbols);
254+
return importableReference.typeReference;
255+
}
256+
}
257+
return typeNode;
258+
}
259+
258260
function createDummyParameters(argCount: number, names: (string | undefined)[] | undefined, types: (TypeNode | undefined)[] | undefined, minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] {
259261
const parameters: ParameterDeclaration[] = [];
260262
for (let i = 0; i < argCount; i++) {
@@ -456,7 +458,7 @@ namespace ts.codefix {
456458
return createQualifiedName(replaceFirstIdentifierOfEntityName(name.left, newIdentifier), name.right);
457459
}
458460

459-
function importSymbols(importAdder: ImportAdder, symbols: readonly Symbol[]) {
461+
export function importSymbols(importAdder: ImportAdder, symbols: readonly Symbol[]) {
460462
symbols.forEach(s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true));
461463
}
462464
}

src/services/refactors/extractSymbol.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,8 @@ namespace ts.refactor.extractSymbol {
719719
context: RefactorContext): RefactorEditInfo {
720720

721721
const checker = context.program.getTypeChecker();
722+
const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions());
723+
const importAdder = codefix.createImportAdder(context.file, context.program, context.preferences, context.host);
722724

723725
// Make a unique name for the extracted function
724726
const file = scope.getSourceFile();
@@ -737,7 +739,7 @@ namespace ts.refactor.extractSymbol {
737739
let type = checker.getTypeOfSymbolAtLocation(usage.symbol, usage.node);
738740
// Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {"
739741
type = checker.getBaseTypeOfLiteralType(type);
740-
typeNode = checker.typeToTypeNode(type, scope, NodeBuilderFlags.NoTruncation);
742+
typeNode = codefix.typeToAutoImportableTypeNode(checker, importAdder, type, scope, scriptTarget, NodeBuilderFlags.NoTruncation);
741743
}
742744

743745
const paramDecl = createParameter(
@@ -823,6 +825,7 @@ namespace ts.refactor.extractSymbol {
823825
else {
824826
changeTracker.insertNodeAtEndOfScope(context.file, scope, newFunction);
825827
}
828+
importAdder.writeFixes(changeTracker);
826829

827830
const newNodes: Node[] = [];
828831
// replace range with function call
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: /a.ts
4+
////export interface A {
5+
//// x: number;
6+
////}
7+
////export const a: A = { x: 1 };
8+
9+
// @Filename: /b.ts
10+
////import { a } from "./a";
11+
////
12+
////function foo() {
13+
//// const arg = a;
14+
//// /*a*/console.log(arg);/*b*/
15+
////}
16+
17+
goTo.file("/b.ts");
18+
goTo.select("a", "b");
19+
edit.applyRefactor({
20+
refactorName: "Extract Symbol",
21+
actionName: "function_scope_1",
22+
actionDescription: "Extract to function in module scope",
23+
newContent:
24+
`import { a, A } from "./a";
25+
26+
function foo() {
27+
const arg = a;
28+
/*RENAME*/newFunction(arg);
29+
}
30+
31+
function newFunction(arg: A) {
32+
console.log(arg);
33+
}
34+
`
35+
});
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 const b: B<A> = {
14+
//// payload: { x: 1 }
15+
////}
16+
17+
// @Filename: /c.ts
18+
////import { b } from "./b";
19+
////
20+
////function foo() {
21+
//// const prop = b;
22+
//// /*a*/console.log(prop);/*b*/
23+
////}
24+
25+
goTo.file("/c.ts");
26+
goTo.select("a", "b");
27+
edit.applyRefactor({
28+
refactorName: "Extract Symbol",
29+
actionName: "function_scope_1",
30+
actionDescription: "Extract to function in module scope",
31+
newContent:
32+
`import { b, B } from "./b";
33+
import { A } from "./a";
34+
35+
function foo() {
36+
const prop = b;
37+
/*RENAME*/newFunction(prop);
38+
}
39+
40+
function newFunction(prop: B<A>) {
41+
console.log(prop);
42+
}
43+
`
44+
});
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
////
20+
////export const c: C<B<A>> = {
21+
//// payload: {
22+
//// payload: { x: 1 }
23+
//// }
24+
////}
25+
26+
// @Filename: /d.ts
27+
////import { c } from "./c";
28+
////
29+
////function foo() {
30+
//// const prop = c;
31+
//// /*a*/console.log(prop);/*b*/
32+
////}
33+
34+
goTo.file("/c.ts");
35+
goTo.select("a", "b");
36+
edit.applyRefactor({
37+
refactorName: "Extract Symbol",
38+
actionName: "function_scope_1",
39+
actionDescription: "Extract to function in module scope",
40+
newContent:
41+
`import { c, C } from "./c";
42+
import { B } from "./b";
43+
import { A } from "./a";
44+
45+
function foo() {
46+
const prop = c;
47+
/*RENAME*/newFunction(prop);
48+
}
49+
50+
function newFunction(prop: C<B<A>>) {
51+
console.log(prop);
52+
}
53+
`
54+
});
+38
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+
////export const a: A = { x: 1 };
8+
9+
// @Filename: /b.ts
10+
////import { a } from "./a";
11+
////
12+
////class Foo {
13+
//// foo() {
14+
//// const arg = a;
15+
//// /*a*/console.log(arg);/*b*/
16+
//// }
17+
////}
18+
19+
goTo.file("/b.ts");
20+
goTo.select("a", "b");
21+
edit.applyRefactor({
22+
refactorName: "Extract Symbol",
23+
actionName: "function_scope_1",
24+
actionDescription: "Extract to method in class 'Foo'",
25+
newContent:
26+
`import { a, A } from "./a";
27+
28+
class Foo {
29+
foo() {
30+
const arg = a;
31+
this./*RENAME*/newMethod(arg);
32+
}
33+
34+
private newMethod(arg: A) {
35+
console.log(arg);
36+
}
37+
}`
38+
});
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 const b: B<A> = {
14+
//// payload: { x: 1 }
15+
////}
16+
17+
// @Filename: /c.ts
18+
////import { b } from "./b";
19+
////
20+
////class Foo {
21+
//// foo() {
22+
//// const arg = b;
23+
//// /*a*/console.log(arg);/*b*/
24+
//// }
25+
////}
26+
27+
goTo.file("/c.ts");
28+
goTo.select("a", "b");
29+
edit.applyRefactor({
30+
refactorName: "Extract Symbol",
31+
actionName: "function_scope_1",
32+
actionDescription: "Extract to method in class 'Foo'",
33+
newContent:
34+
`import { b, B } from "./b";
35+
import { A } from "./a";
36+
37+
class Foo {
38+
foo() {
39+
const arg = b;
40+
this./*RENAME*/newMethod(arg);
41+
}
42+
43+
private newMethod(arg: B<A>) {
44+
console.log(arg);
45+
}
46+
}`
47+
});
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
////
20+
////export const c: C<B<A>> = {
21+
//// payload: {
22+
//// payload: { x: 1 }
23+
//// }
24+
////}
25+
26+
// @Filename: /d.ts
27+
////import { c } from "./c";
28+
////
29+
////class Foo {
30+
//// foo() {
31+
//// const arg = c;
32+
//// /*a*/console.log(arg);/*b*/
33+
//// }
34+
////}
35+
36+
goTo.file("/d.ts");
37+
goTo.select("a", "b");
38+
edit.applyRefactor({
39+
refactorName: "Extract Symbol",
40+
actionName: "function_scope_1",
41+
actionDescription: "Extract to method in class 'Foo'",
42+
newContent:
43+
`import { c, C } from "./c";
44+
import { B } from "./b";
45+
import { A } from "./a";
46+
47+
class Foo {
48+
foo() {
49+
const arg = c;
50+
this./*RENAME*/newMethod(arg);
51+
}
52+
53+
private newMethod(arg: C<B<A>>) {
54+
console.log(arg);
55+
}
56+
}`
57+
});

0 commit comments

Comments
 (0)