Skip to content

Commit 0c75021

Browse files
authored
Process type nodes in codefixes/refactors for auto-imports recursively (#39130)
* Process type nodes for auto-imports recursively * Rename function
1 parent 1814e2a commit 0c75021

File tree

3 files changed

+43
-52
lines changed

3 files changed

+43
-52
lines changed

src/services/codefixes/helpers.ts

Lines changed: 38 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ namespace ts.codefix {
5353
const flags = quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : undefined;
5454
let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, flags, getNoopSymbolTrackerWithResolver(context));
5555
if (importAdder) {
56-
const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
56+
const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget);
5757
if (importableReference) {
58-
typeNode = importableReference.typeReference;
58+
typeNode = importableReference.typeNode;
5959
importSymbols(importAdder, importableReference.symbols);
6060
}
6161
}
@@ -75,9 +75,9 @@ namespace ts.codefix {
7575
? [allAccessors.firstAccessor, allAccessors.secondAccessor]
7676
: [allAccessors.firstAccessor];
7777
if (importAdder) {
78-
const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
78+
const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget);
7979
if (importableReference) {
80-
typeNode = importableReference.typeReference;
80+
typeNode = importableReference.typeNode;
8181
importSymbols(importAdder, importableReference.symbols);
8282
}
8383
}
@@ -175,21 +175,20 @@ namespace ts.codefix {
175175
let type = signatureDeclaration.type;
176176
if (importAdder) {
177177
if (typeParameters) {
178-
const newTypeParameters = sameMap(typeParameters, (typeParameterDecl, i) => {
179-
const typeParameter = signature.typeParameters![i];
178+
const newTypeParameters = sameMap(typeParameters, typeParameterDecl => {
180179
let constraint = typeParameterDecl.constraint;
181180
let defaultType = typeParameterDecl.default;
182181
if (constraint) {
183-
const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(constraint, typeParameter.constraint, scriptTarget);
182+
const importableReference = tryGetAutoImportableReferenceFromTypeNode(constraint, scriptTarget);
184183
if (importableReference) {
185-
constraint = importableReference.typeReference;
184+
constraint = importableReference.typeNode;
186185
importSymbols(importAdder, importableReference.symbols);
187186
}
188187
}
189188
if (defaultType) {
190-
const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(defaultType, typeParameter.default, scriptTarget);
189+
const importableReference = tryGetAutoImportableReferenceFromTypeNode(defaultType, scriptTarget);
191190
if (importableReference) {
192-
defaultType = importableReference.typeReference;
191+
defaultType = importableReference.typeNode;
193192
importSymbols(importAdder, importableReference.symbols);
194193
}
195194
}
@@ -204,12 +203,11 @@ namespace ts.codefix {
204203
typeParameters = setTextRange(factory.createNodeArray(newTypeParameters, typeParameters.hasTrailingComma), typeParameters);
205204
}
206205
}
207-
const newParameters = sameMap(parameters, (parameterDecl, i) => {
208-
const parameter = signature.parameters[i];
209-
const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(parameterDecl.type, checker.getTypeAtLocation(parameter.valueDeclaration), scriptTarget);
206+
const newParameters = sameMap(parameters, parameterDecl => {
207+
const importableReference = tryGetAutoImportableReferenceFromTypeNode(parameterDecl.type, scriptTarget);
210208
let type = parameterDecl.type;
211209
if (importableReference) {
212-
type = importableReference.typeReference;
210+
type = importableReference.typeNode;
213211
importSymbols(importAdder, importableReference.symbols);
214212
}
215213
return factory.updateParameterDeclaration(
@@ -227,9 +225,9 @@ namespace ts.codefix {
227225
parameters = setTextRange(factory.createNodeArray(newParameters, parameters.hasTrailingComma), parameters);
228226
}
229227
if (type) {
230-
const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(type, signature.resolvedReturnType, scriptTarget);
228+
const importableReference = tryGetAutoImportableReferenceFromTypeNode(type, scriptTarget);
231229
if (importableReference) {
232-
type = importableReference.typeReference;
230+
type = importableReference.typeNode;
233231
importSymbols(importAdder, importableReference.symbols);
234232
}
235233
}
@@ -286,10 +284,10 @@ namespace ts.codefix {
286284
export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, type: Type, contextNode: Node, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined {
287285
const typeNode = checker.typeToTypeNode(type, contextNode, flags, tracker);
288286
if (typeNode && isImportTypeNode(typeNode)) {
289-
const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
287+
const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget);
290288
if (importableReference) {
291289
importSymbols(importAdder, importableReference.symbols);
292-
return importableReference.typeReference;
290+
return importableReference.typeNode;
293291
}
294292
}
295293
return typeNode;
@@ -454,38 +452,32 @@ namespace ts.codefix {
454452
}
455453

456454
/**
457-
* Given an ImportTypeNode 'import("./a").SomeType<import("./b").OtherType<...>>',
455+
* Given a type node containing 'import("./a").SomeType<import("./b").OtherType<...>>',
458456
* returns an equivalent type reference node with any nested ImportTypeNodes also replaced
459457
* with type references, and a list of symbols that must be imported to use the type reference.
460458
*/
461-
export function tryGetAutoImportableReferenceFromImportTypeNode(importTypeNode: TypeNode | undefined, type: Type | undefined, scriptTarget: ScriptTarget) {
462-
if (importTypeNode && isLiteralImportTypeNode(importTypeNode) && importTypeNode.qualifier && (!type || type.symbol)) {
463-
// Symbol for the left-most thing after the dot
464-
const firstIdentifier = getFirstIdentifier(importTypeNode.qualifier);
465-
const name = getNameForExportedSymbol(firstIdentifier.symbol, scriptTarget);
466-
const qualifier = name !== firstIdentifier.text
467-
? replaceFirstIdentifierOfEntityName(importTypeNode.qualifier, factory.createIdentifier(name))
468-
: importTypeNode.qualifier;
469-
470-
const symbols = [firstIdentifier.symbol];
471-
const typeArguments: TypeNode[] = [];
472-
if (importTypeNode.typeArguments) {
473-
importTypeNode.typeArguments.forEach(arg => {
474-
const ref = tryGetAutoImportableReferenceFromImportTypeNode(arg, /*undefined*/ type, scriptTarget);
475-
if (ref) {
476-
symbols.push(...ref.symbols);
477-
typeArguments.push(ref.typeReference);
478-
}
479-
else {
480-
typeArguments.push(arg);
481-
}
482-
});
483-
}
459+
export function tryGetAutoImportableReferenceFromTypeNode(importTypeNode: TypeNode | undefined, scriptTarget: ScriptTarget) {
460+
let symbols: Symbol[] | undefined;
461+
const typeNode = visitNode(importTypeNode, visit);
462+
if (symbols && typeNode) {
463+
return { typeNode, symbols };
464+
}
484465

485-
return {
486-
symbols,
487-
typeReference: factory.createTypeReferenceNode(qualifier, typeArguments)
488-
};
466+
function visit(node: TypeNode): TypeNode;
467+
function visit(node: Node): Node {
468+
if (isLiteralImportTypeNode(node) && node.qualifier) {
469+
// Symbol for the left-most thing after the dot
470+
const firstIdentifier = getFirstIdentifier(node.qualifier);
471+
const name = getNameForExportedSymbol(firstIdentifier.symbol, scriptTarget);
472+
const qualifier = name !== firstIdentifier.text
473+
? replaceFirstIdentifierOfEntityName(node.qualifier, factory.createIdentifier(name))
474+
: node.qualifier;
475+
476+
symbols = append(symbols, firstIdentifier.symbol);
477+
const typeArguments = node.typeArguments?.map(visit);
478+
return factory.createTypeReferenceNode(qualifier, typeArguments);
479+
}
480+
return visitEachChild(node, visit, nullTransformationContext);
489481
}
490482
}
491483

src/services/codefixes/inferFromUsage.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ namespace ts.codefix {
310310
const typeTag = isGetAccessorDeclaration(declaration) ? factory.createJSDocReturnTag(/*tagName*/ undefined, typeExpression, "") : factory.createJSDocTypeTag(/*tagName*/ undefined, typeExpression, "");
311311
addJSDocTags(changes, sourceFile, parent, [typeTag]);
312312
}
313-
else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, declaration, type, sourceFile, changes, importAdder, getEmitScriptTarget(program.getCompilerOptions()))) {
313+
else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, declaration, sourceFile, changes, importAdder, getEmitScriptTarget(program.getCompilerOptions()))) {
314314
changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
315315
}
316316
}
@@ -319,14 +319,13 @@ namespace ts.codefix {
319319
function tryReplaceImportTypeNodeWithAutoImport(
320320
typeNode: TypeNode,
321321
declaration: textChanges.TypeAnnotatable,
322-
type: Type,
323322
sourceFile: SourceFile,
324323
changes: textChanges.ChangeTracker,
325324
importAdder: ImportAdder,
326325
scriptTarget: ScriptTarget
327326
): boolean {
328-
const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
329-
if (importableReference && changes.tryInsertTypeAnnotation(sourceFile, declaration, importableReference.typeReference)) {
327+
const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget);
328+
if (importableReference && changes.tryInsertTypeAnnotation(sourceFile, declaration, importableReference.typeNode)) {
330329
forEach(importableReference.symbols, s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true));
331330
return true;
332331
}

tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
////import { B, C, D } from './types2';
1515
////
1616
////export interface Base {
17-
//// a: A;
17+
//// a: Readonly<A> & { kind: "a"; };
1818
//// b<T extends B = B>(p1: C): D<C>;
1919
////}
2020

@@ -32,7 +32,7 @@ import A from './types1';
3232
import { B, C, D } from './types2';
3333
3434
export class C implements Base {
35-
a: A;
35+
a: Readonly<A> & { kind: "a"; };
3636
b<T extends B = B>(p1: C): D<C> {
3737
throw new Error('Method not implemented.');
3838
}

0 commit comments

Comments
 (0)