@@ -4,13 +4,14 @@ namespace ts.codefix {
44 * Finds members of the resolved type that are missing in the class pointed to by class decl
55 * and generates source code for the missing members.
66 * @param possiblyMissingSymbols The collection of symbols to filter and then get insertions for.
7+ * @param importAdder If provided, type annotations will use identifier type references instead of ImportTypeNodes, and the missing imports will be added to the importAdder.
78 * @returns Empty string iff there are no member insertions.
89 */
9- export function createMissingMemberNodes ( classDeclaration : ClassLikeDeclaration , possiblyMissingSymbols : readonly Symbol [ ] , context : TypeConstructionContext , preferences : UserPreferences , out : ( node : ClassElement ) => void ) : void {
10+ export function createMissingMemberNodes ( classDeclaration : ClassLikeDeclaration , possiblyMissingSymbols : readonly Symbol [ ] , context : TypeConstructionContext , preferences : UserPreferences , importAdder : ImportAdder | undefined , addClassElement : ( node : ClassElement ) => void ) : void {
1011 const classMembers = classDeclaration . symbol . members ! ;
1112 for ( const symbol of possiblyMissingSymbols ) {
1213 if ( ! classMembers . has ( symbol . escapedName ) ) {
13- addNewNodeForMemberSymbol ( symbol , classDeclaration , context , preferences , out ) ;
14+ addNewNodeForMemberSymbol ( symbol , classDeclaration , context , preferences , importAdder , addClassElement ) ;
1415 }
1516 }
1617 }
@@ -19,7 +20,7 @@ namespace ts.codefix {
1920 return {
2021 directoryExists : context . host . directoryExists ? d => context . host . directoryExists ! ( d ) : undefined ,
2122 fileExists : context . host . fileExists ? f => context . host . fileExists ! ( f ) : undefined ,
22- getCurrentDirectory : context . host . getCurrentDirectory ? ( ) => context . host . getCurrentDirectory ! ( ) : undefined ,
23+ getCurrentDirectory : context . host . getCurrentDirectory ? ( ) => context . host . getCurrentDirectory ( ) : undefined ,
2324 readFile : context . host . readFile ? f => context . host . readFile ! ( f ) : undefined ,
2425 useCaseSensitiveFileNames : context . host . useCaseSensitiveFileNames ? ( ) => context . host . useCaseSensitiveFileNames ! ( ) : undefined ,
2526 getSourceFiles : ( ) => context . program . getSourceFiles ( ) ,
@@ -36,19 +37,19 @@ namespace ts.codefix {
3637
3738 export interface TypeConstructionContext {
3839 program : Program ;
39- host : ModuleSpecifierResolutionHost ;
40+ host : LanguageServiceHost ;
4041 }
4142
4243 /**
4344 * @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`.
4445 */
45- function addNewNodeForMemberSymbol ( symbol : Symbol , enclosingDeclaration : ClassLikeDeclaration , context : TypeConstructionContext , preferences : UserPreferences , out : ( node : Node ) => void ) : void {
46+ function addNewNodeForMemberSymbol ( symbol : Symbol , enclosingDeclaration : ClassLikeDeclaration , context : TypeConstructionContext , preferences : UserPreferences , importAdder : ImportAdder | undefined , addClassElement : ( node : Node ) => void ) : void {
4647 const declarations = symbol . getDeclarations ( ) ;
4748 if ( ! ( declarations && declarations . length ) ) {
4849 return undefined ;
4950 }
5051 const checker = context . program . getTypeChecker ( ) ;
51-
52+ const scriptTarget = getEmitScriptTarget ( context . program . getCompilerOptions ( ) ) ;
5253 const declaration = declarations [ 0 ] ;
5354 const name = getSynthesizedDeepClone ( getNameOfDeclaration ( declaration ) , /*includeTrivia*/ false ) as PropertyName ;
5455 const visibilityModifier = createVisibilityModifier ( getModifierFlags ( declaration ) ) ;
@@ -61,8 +62,15 @@ namespace ts.codefix {
6162 case SyntaxKind . PropertySignature :
6263 case SyntaxKind . PropertyDeclaration :
6364 const flags = preferences . quotePreference === "single" ? NodeBuilderFlags . UseSingleQuotesForStringLiteralType : undefined ;
64- const typeNode = checker . typeToTypeNode ( type , enclosingDeclaration , flags , getNoopSymbolTrackerWithResolver ( context ) ) ;
65- out ( createProperty (
65+ let typeNode = checker . typeToTypeNode ( type , enclosingDeclaration , flags , getNoopSymbolTrackerWithResolver ( context ) ) ;
66+ if ( importAdder ) {
67+ const importableReference = tryGetAutoImportableReferenceFromImportTypeNode ( typeNode , type , scriptTarget ) ;
68+ if ( importableReference ) {
69+ typeNode = importableReference . typeReference ;
70+ importSymbols ( importAdder , importableReference . symbols ) ;
71+ }
72+ }
73+ addClassElement ( createProperty (
6674 /*decorators*/ undefined ,
6775 modifiers ,
6876 name ,
@@ -72,14 +80,21 @@ namespace ts.codefix {
7280 break ;
7381 case SyntaxKind . GetAccessor :
7482 case SyntaxKind . SetAccessor : {
83+ let typeNode = checker . typeToTypeNode ( type , enclosingDeclaration , /*flags*/ undefined , getNoopSymbolTrackerWithResolver ( context ) ) ;
7584 const allAccessors = getAllAccessorDeclarations ( declarations , declaration as AccessorDeclaration ) ;
76- const typeNode = checker . typeToTypeNode ( type , enclosingDeclaration , /*flags*/ undefined , getNoopSymbolTrackerWithResolver ( context ) ) ;
7785 const orderedAccessors = allAccessors . secondAccessor
7886 ? [ allAccessors . firstAccessor , allAccessors . secondAccessor ]
7987 : [ allAccessors . firstAccessor ] ;
88+ if ( importAdder ) {
89+ const importableReference = tryGetAutoImportableReferenceFromImportTypeNode ( typeNode , type , scriptTarget ) ;
90+ if ( importableReference ) {
91+ typeNode = importableReference . typeReference ;
92+ importSymbols ( importAdder , importableReference . symbols ) ;
93+ }
94+ }
8095 for ( const accessor of orderedAccessors ) {
8196 if ( isGetAccessorDeclaration ( accessor ) ) {
82- out ( createGetAccessor (
97+ addClassElement ( createGetAccessor (
8398 /*decorators*/ undefined ,
8499 modifiers ,
85100 name ,
@@ -91,7 +106,7 @@ namespace ts.codefix {
91106 Debug . assertNode ( accessor , isSetAccessorDeclaration , "The counterpart to a getter should be a setter" ) ;
92107 const parameter = getSetAccessorValueParameter ( accessor ) ;
93108 const parameterName = parameter && isIdentifier ( parameter . name ) ? idText ( parameter . name ) : undefined ;
94- out ( createSetAccessor (
109+ addClassElement ( createSetAccessor (
95110 /*decorators*/ undefined ,
96111 modifiers ,
97112 name ,
@@ -134,15 +149,15 @@ namespace ts.codefix {
134149 }
135150 else {
136151 Debug . assert ( declarations . length === signatures . length , "Declarations and signatures should match count" ) ;
137- out ( createMethodImplementingSignatures ( signatures , name , optional , modifiers , preferences ) ) ;
152+ addClassElement ( createMethodImplementingSignatures ( signatures , name , optional , modifiers , preferences ) ) ;
138153 }
139154 }
140155 break ;
141156 }
142157
143158 function outputMethod ( signature : Signature , modifiers : NodeArray < Modifier > | undefined , name : PropertyName , body ?: Block ) : void {
144- const method = signatureToMethodDeclaration ( context , signature , enclosingDeclaration , modifiers , name , optional , body ) ;
145- if ( method ) out ( method ) ;
159+ const method = signatureToMethodDeclaration ( context , signature , enclosingDeclaration , modifiers , name , optional , body , importAdder ) ;
160+ if ( method ) addClassElement ( method ) ;
146161 }
147162 }
148163
@@ -154,13 +169,53 @@ namespace ts.codefix {
154169 name : PropertyName ,
155170 optional : boolean ,
156171 body : Block | undefined ,
172+ importAdder : ImportAdder | undefined ,
157173 ) : MethodDeclaration | undefined {
158174 const program = context . program ;
159- const signatureDeclaration = < MethodDeclaration > program . getTypeChecker ( ) . signatureToSignatureDeclaration ( signature , SyntaxKind . MethodDeclaration , enclosingDeclaration , NodeBuilderFlags . NoTruncation | NodeBuilderFlags . SuppressAnyReturnType , getNoopSymbolTrackerWithResolver ( context ) ) ;
175+ const checker = program . getTypeChecker ( ) ;
176+ const scriptTarget = getEmitScriptTarget ( program . getCompilerOptions ( ) ) ;
177+ const signatureDeclaration = < MethodDeclaration > checker . signatureToSignatureDeclaration ( signature , SyntaxKind . MethodDeclaration , enclosingDeclaration , NodeBuilderFlags . NoTruncation | NodeBuilderFlags . SuppressAnyReturnType , getNoopSymbolTrackerWithResolver ( context ) ) ;
160178 if ( ! signatureDeclaration ) {
161179 return undefined ;
162180 }
163181
182+ if ( importAdder ) {
183+ if ( signatureDeclaration . typeParameters ) {
184+ forEach ( signatureDeclaration . typeParameters , ( typeParameterDecl , i ) => {
185+ const typeParameter = signature . typeParameters ! [ i ] ;
186+ if ( typeParameterDecl . constraint ) {
187+ const importableReference = tryGetAutoImportableReferenceFromImportTypeNode ( typeParameterDecl . constraint , typeParameter . constraint , scriptTarget ) ;
188+ if ( importableReference ) {
189+ typeParameterDecl . constraint = importableReference . typeReference ;
190+ importSymbols ( importAdder , importableReference . symbols ) ;
191+ }
192+ }
193+ if ( typeParameterDecl . default ) {
194+ const importableReference = tryGetAutoImportableReferenceFromImportTypeNode ( typeParameterDecl . default , typeParameter . default , scriptTarget ) ;
195+ if ( importableReference ) {
196+ typeParameterDecl . default = importableReference . typeReference ;
197+ importSymbols ( importAdder , importableReference . symbols ) ;
198+ }
199+ }
200+ } ) ;
201+ }
202+ forEach ( signatureDeclaration . parameters , ( parameterDecl , i ) => {
203+ const parameter = signature . parameters [ i ] ;
204+ const importableReference = tryGetAutoImportableReferenceFromImportTypeNode ( parameterDecl . type , checker . getTypeAtLocation ( parameter . valueDeclaration ) , scriptTarget ) ;
205+ if ( importableReference ) {
206+ parameterDecl . type = importableReference . typeReference ;
207+ importSymbols ( importAdder , importableReference . symbols ) ;
208+ }
209+ } ) ;
210+ if ( signatureDeclaration . type ) {
211+ const importableReference = tryGetAutoImportableReferenceFromImportTypeNode ( signatureDeclaration . type , signature . resolvedReturnType , scriptTarget ) ;
212+ if ( importableReference ) {
213+ signatureDeclaration . type = importableReference . typeReference ;
214+ importSymbols ( importAdder , importableReference . symbols ) ;
215+ }
216+ }
217+ }
218+
164219 signatureDeclaration . decorators = undefined ;
165220 signatureDeclaration . modifiers = modifiers ;
166221 signatureDeclaration . name = name ;
@@ -359,4 +414,51 @@ namespace ts.codefix {
359414 export function findJsonProperty ( obj : ObjectLiteralExpression , name : string ) : PropertyAssignment | undefined {
360415 return find ( obj . properties , ( p ) : p is PropertyAssignment => isPropertyAssignment ( p ) && ! ! p . name && isStringLiteral ( p . name ) && p . name . text === name ) ;
361416 }
417+
418+ /**
419+ * Given an ImportTypeNode 'import("./a").SomeType<import("./b").OtherType<...>>',
420+ * returns an equivalent type reference node with any nested ImportTypeNodes also replaced
421+ * with type references, and a list of symbols that must be imported to use the type reference.
422+ */
423+ export function tryGetAutoImportableReferenceFromImportTypeNode ( importTypeNode : TypeNode | undefined , type : Type | undefined , scriptTarget : ScriptTarget ) {
424+ if ( importTypeNode && isLiteralImportTypeNode ( importTypeNode ) && importTypeNode . qualifier && ( ! type || type . symbol ) ) {
425+ // Symbol for the left-most thing after the dot
426+ const firstIdentifier = getFirstIdentifier ( importTypeNode . qualifier ) ;
427+ const name = getNameForExportedSymbol ( firstIdentifier . symbol , scriptTarget ) ;
428+ const qualifier = name !== firstIdentifier . text
429+ ? replaceFirstIdentifierOfEntityName ( importTypeNode . qualifier , createIdentifier ( name ) )
430+ : importTypeNode . qualifier ;
431+
432+ const symbols = [ firstIdentifier . symbol ] ;
433+ const typeArguments : TypeNode [ ] = [ ] ;
434+ if ( importTypeNode . typeArguments ) {
435+ importTypeNode . typeArguments . forEach ( arg => {
436+ const ref = tryGetAutoImportableReferenceFromImportTypeNode ( arg , /*undefined*/ type , scriptTarget ) ;
437+ if ( ref ) {
438+ symbols . push ( ...ref . symbols ) ;
439+ typeArguments . push ( ref . typeReference ) ;
440+ }
441+ else {
442+ typeArguments . push ( arg ) ;
443+ }
444+ } ) ;
445+ }
446+
447+ return {
448+ symbols,
449+ typeReference : createTypeReferenceNode ( qualifier , typeArguments )
450+ } ;
451+ }
452+ }
453+
454+ function replaceFirstIdentifierOfEntityName ( name : EntityName , newIdentifier : Identifier ) : EntityName {
455+ if ( name . kind === SyntaxKind . Identifier ) {
456+ return newIdentifier ;
457+ }
458+ return createQualifiedName ( replaceFirstIdentifierOfEntityName ( name . left , newIdentifier ) , name . right ) ;
459+ }
460+
461+ function importSymbols ( importAdder : ImportAdder , symbols : readonly Symbol [ ] ) {
462+ symbols . forEach ( s => importAdder . addImportFromExportedSymbol ( s , /*usageIsTypeOnly*/ true ) ) ;
463+ }
362464}
0 commit comments