From 55f863a09d4389d5509c9f93b6235408dadd361e Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 17 Jun 2020 17:09:27 -0700 Subject: [PATCH 1/2] Process type nodes for auto-imports recursively --- src/services/codefixes/helpers.ts | 82 +++++++++---------- src/services/codefixes/inferFromUsage.ts | 7 +- ...deFixClassImplementInterfaceAutoImports.ts | 4 +- 3 files changed, 42 insertions(+), 51 deletions(-) diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 405a5db924bd6..c683b4997c6be 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -52,9 +52,9 @@ namespace ts.codefix { const flags = preferences.quotePreference === "single" ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : undefined; let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, flags, getNoopSymbolTrackerWithResolver(context)); if (importAdder) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, scriptTarget); if (importableReference) { - typeNode = importableReference.typeReference; + typeNode = importableReference.typeNode; importSymbols(importAdder, importableReference.symbols); } } @@ -74,9 +74,9 @@ namespace ts.codefix { ? [allAccessors.firstAccessor, allAccessors.secondAccessor] : [allAccessors.firstAccessor]; if (importAdder) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, scriptTarget); if (importableReference) { - typeNode = importableReference.typeReference; + typeNode = importableReference.typeNode; importSymbols(importAdder, importableReference.symbols); } } @@ -172,21 +172,20 @@ namespace ts.codefix { let type = signatureDeclaration.type; if (importAdder) { if (typeParameters) { - const newTypeParameters = sameMap(typeParameters, (typeParameterDecl, i) => { - const typeParameter = signature.typeParameters![i]; + const newTypeParameters = sameMap(typeParameters, typeParameterDecl => { let constraint = typeParameterDecl.constraint; let defaultType = typeParameterDecl.default; if (constraint) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(constraint, typeParameter.constraint, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(constraint, scriptTarget); if (importableReference) { - constraint = importableReference.typeReference; + constraint = importableReference.typeNode; importSymbols(importAdder, importableReference.symbols); } } if (defaultType) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(defaultType, typeParameter.default, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(defaultType, scriptTarget); if (importableReference) { - defaultType = importableReference.typeReference; + defaultType = importableReference.typeNode; importSymbols(importAdder, importableReference.symbols); } } @@ -201,12 +200,11 @@ namespace ts.codefix { typeParameters = setTextRange(factory.createNodeArray(newTypeParameters, typeParameters.hasTrailingComma), typeParameters); } } - const newParameters = sameMap(parameters, (parameterDecl, i) => { - const parameter = signature.parameters[i]; - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(parameterDecl.type, checker.getTypeAtLocation(parameter.valueDeclaration), scriptTarget); + const newParameters = sameMap(parameters, parameterDecl => { + const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(parameterDecl.type, scriptTarget); let type = parameterDecl.type; if (importableReference) { - type = importableReference.typeReference; + type = importableReference.typeNode; importSymbols(importAdder, importableReference.symbols); } return factory.updateParameterDeclaration( @@ -224,9 +222,9 @@ namespace ts.codefix { parameters = setTextRange(factory.createNodeArray(newParameters, parameters.hasTrailingComma), parameters); } if (type) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(type, signature.resolvedReturnType, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(type, scriptTarget); if (importableReference) { - type = importableReference.typeReference; + type = importableReference.typeNode; importSymbols(importAdder, importableReference.symbols); } } @@ -282,10 +280,10 @@ namespace ts.codefix { export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, type: Type, contextNode: Node, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined { const typeNode = checker.typeToTypeNode(type, contextNode, flags, tracker); if (typeNode && isImportTypeNode(typeNode)) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, scriptTarget); if (importableReference) { importSymbols(importAdder, importableReference.symbols); - return importableReference.typeReference; + return importableReference.typeNode; } } return typeNode; @@ -454,34 +452,28 @@ namespace ts.codefix { * returns an equivalent type reference node with any nested ImportTypeNodes also replaced * with type references, and a list of symbols that must be imported to use the type reference. */ - export function tryGetAutoImportableReferenceFromImportTypeNode(importTypeNode: TypeNode | undefined, type: Type | undefined, scriptTarget: ScriptTarget) { - if (importTypeNode && isLiteralImportTypeNode(importTypeNode) && importTypeNode.qualifier && (!type || type.symbol)) { - // Symbol for the left-most thing after the dot - const firstIdentifier = getFirstIdentifier(importTypeNode.qualifier); - const name = getNameForExportedSymbol(firstIdentifier.symbol, scriptTarget); - const qualifier = name !== firstIdentifier.text - ? replaceFirstIdentifierOfEntityName(importTypeNode.qualifier, factory.createIdentifier(name)) - : importTypeNode.qualifier; - - const symbols = [firstIdentifier.symbol]; - const typeArguments: TypeNode[] = []; - if (importTypeNode.typeArguments) { - importTypeNode.typeArguments.forEach(arg => { - const ref = tryGetAutoImportableReferenceFromImportTypeNode(arg, /*undefined*/ type, scriptTarget); - if (ref) { - symbols.push(...ref.symbols); - typeArguments.push(ref.typeReference); - } - else { - typeArguments.push(arg); - } - }); - } + export function tryGetAutoImportableReferenceFromImportTypeNode(importTypeNode: TypeNode | undefined, scriptTarget: ScriptTarget) { + let symbols: Symbol[] | undefined; + const typeNode = visitNode(importTypeNode, visit); + if (symbols) { + return { typeNode, symbols }; + } - return { - symbols, - typeReference: factory.createTypeReferenceNode(qualifier, typeArguments) - }; + function visit(node: TypeNode): TypeNode; + function visit(node: Node): Node { + if (isLiteralImportTypeNode(node) && node.qualifier) { + // Symbol for the left-most thing after the dot + const firstIdentifier = getFirstIdentifier(node.qualifier); + const name = getNameForExportedSymbol(firstIdentifier.symbol, scriptTarget); + const qualifier = name !== firstIdentifier.text + ? replaceFirstIdentifierOfEntityName(node.qualifier, factory.createIdentifier(name)) + : node.qualifier; + + symbols = append(symbols, firstIdentifier.symbol); + const typeArguments = node.typeArguments?.map(visit); + return factory.createTypeReferenceNode(qualifier, typeArguments); + } + return visitEachChild(node, visit, nullTransformationContext); } } diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index bd5782d7a0fe5..ffdf9da15d03d 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -310,7 +310,7 @@ namespace ts.codefix { const typeTag = isGetAccessorDeclaration(declaration) ? factory.createJSDocReturnTag(/*tagName*/ undefined, typeExpression, "") : factory.createJSDocTypeTag(/*tagName*/ undefined, typeExpression, ""); addJSDocTags(changes, sourceFile, parent, [typeTag]); } - else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, declaration, type, sourceFile, changes, importAdder, getEmitScriptTarget(program.getCompilerOptions()))) { + else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, declaration, sourceFile, changes, importAdder, getEmitScriptTarget(program.getCompilerOptions()))) { changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode); } } @@ -319,14 +319,13 @@ namespace ts.codefix { function tryReplaceImportTypeNodeWithAutoImport( typeNode: TypeNode, declaration: textChanges.TypeAnnotatable, - type: Type, sourceFile: SourceFile, changes: textChanges.ChangeTracker, importAdder: ImportAdder, scriptTarget: ScriptTarget ): boolean { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget); - if (importableReference && changes.tryInsertTypeAnnotation(sourceFile, declaration, importableReference.typeReference)) { + const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, scriptTarget); + if (importableReference && changes.tryInsertTypeAnnotation(sourceFile, declaration, importableReference.typeNode)) { forEach(importableReference.symbols, s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true)); return true; } diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports.ts index 3a08ffa5e5a2a..828a72f9477f8 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports.ts @@ -14,7 +14,7 @@ ////import { B, C, D } from './types2'; //// ////export interface Base { -//// a: A; +//// a: Readonly & { kind: "a"; }; //// b(p1: C): D; ////} @@ -32,7 +32,7 @@ import A from './types1'; import { B, C, D } from './types2'; export class C implements Base { - a: A; + a: Readonly & { kind: "a"; }; b(p1: C): D { throw new Error("Method not implemented."); } From b34f95f5af0bbe65d1ec02a29b5bb735105c6ffc Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 17 Jun 2020 17:17:48 -0700 Subject: [PATCH 2/2] Rename function --- src/services/codefixes/helpers.ts | 20 ++++++++++---------- src/services/codefixes/inferFromUsage.ts | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index c683b4997c6be..935d0e2a4e002 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -52,7 +52,7 @@ namespace ts.codefix { const flags = preferences.quotePreference === "single" ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : undefined; let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, flags, getNoopSymbolTrackerWithResolver(context)); if (importAdder) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); if (importableReference) { typeNode = importableReference.typeNode; importSymbols(importAdder, importableReference.symbols); @@ -74,7 +74,7 @@ namespace ts.codefix { ? [allAccessors.firstAccessor, allAccessors.secondAccessor] : [allAccessors.firstAccessor]; if (importAdder) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); if (importableReference) { typeNode = importableReference.typeNode; importSymbols(importAdder, importableReference.symbols); @@ -176,14 +176,14 @@ namespace ts.codefix { let constraint = typeParameterDecl.constraint; let defaultType = typeParameterDecl.default; if (constraint) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(constraint, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromTypeNode(constraint, scriptTarget); if (importableReference) { constraint = importableReference.typeNode; importSymbols(importAdder, importableReference.symbols); } } if (defaultType) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(defaultType, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromTypeNode(defaultType, scriptTarget); if (importableReference) { defaultType = importableReference.typeNode; importSymbols(importAdder, importableReference.symbols); @@ -201,7 +201,7 @@ namespace ts.codefix { } } const newParameters = sameMap(parameters, parameterDecl => { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(parameterDecl.type, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromTypeNode(parameterDecl.type, scriptTarget); let type = parameterDecl.type; if (importableReference) { type = importableReference.typeNode; @@ -222,7 +222,7 @@ namespace ts.codefix { parameters = setTextRange(factory.createNodeArray(newParameters, parameters.hasTrailingComma), parameters); } if (type) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(type, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromTypeNode(type, scriptTarget); if (importableReference) { type = importableReference.typeNode; importSymbols(importAdder, importableReference.symbols); @@ -280,7 +280,7 @@ namespace ts.codefix { export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, type: Type, contextNode: Node, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined { const typeNode = checker.typeToTypeNode(type, contextNode, flags, tracker); if (typeNode && isImportTypeNode(typeNode)) { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); if (importableReference) { importSymbols(importAdder, importableReference.symbols); return importableReference.typeNode; @@ -448,14 +448,14 @@ namespace ts.codefix { } /** - * Given an ImportTypeNode 'import("./a").SomeType>', + * Given a type node containing 'import("./a").SomeType>', * returns an equivalent type reference node with any nested ImportTypeNodes also replaced * with type references, and a list of symbols that must be imported to use the type reference. */ - export function tryGetAutoImportableReferenceFromImportTypeNode(importTypeNode: TypeNode | undefined, scriptTarget: ScriptTarget) { + export function tryGetAutoImportableReferenceFromTypeNode(importTypeNode: TypeNode | undefined, scriptTarget: ScriptTarget) { let symbols: Symbol[] | undefined; const typeNode = visitNode(importTypeNode, visit); - if (symbols) { + if (symbols && typeNode) { return { typeNode, symbols }; } diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index ffdf9da15d03d..220a4268802b9 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -324,7 +324,7 @@ namespace ts.codefix { importAdder: ImportAdder, scriptTarget: ScriptTarget ): boolean { - const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, scriptTarget); + const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); if (importableReference && changes.tryInsertTypeAnnotation(sourceFile, declaration, importableReference.typeNode)) { forEach(importableReference.symbols, s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true)); return true;