@@ -4,13 +4,14 @@ namespace ts.codefix {
4
4
* Finds members of the resolved type that are missing in the class pointed to by class decl
5
5
* and generates source code for the missing members.
6
6
* @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.
7
8
* @returns Empty string iff there are no member insertions.
8
9
*/
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 {
10
11
const classMembers = classDeclaration . symbol . members ! ;
11
12
for ( const symbol of possiblyMissingSymbols ) {
12
13
if ( ! classMembers . has ( symbol . escapedName ) ) {
13
- addNewNodeForMemberSymbol ( symbol , classDeclaration , context , preferences , out ) ;
14
+ addNewNodeForMemberSymbol ( symbol , classDeclaration , context , preferences , importAdder , addClassElement ) ;
14
15
}
15
16
}
16
17
}
@@ -19,7 +20,7 @@ namespace ts.codefix {
19
20
return {
20
21
directoryExists : context . host . directoryExists ? d => context . host . directoryExists ! ( d ) : undefined ,
21
22
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 ,
23
24
readFile : context . host . readFile ? f => context . host . readFile ! ( f ) : undefined ,
24
25
useCaseSensitiveFileNames : context . host . useCaseSensitiveFileNames ? ( ) => context . host . useCaseSensitiveFileNames ! ( ) : undefined ,
25
26
getSourceFiles : ( ) => context . program . getSourceFiles ( ) ,
@@ -36,19 +37,19 @@ namespace ts.codefix {
36
37
37
38
export interface TypeConstructionContext {
38
39
program : Program ;
39
- host : ModuleSpecifierResolutionHost ;
40
+ host : LanguageServiceHost ;
40
41
}
41
42
42
43
/**
43
44
* @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`.
44
45
*/
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 {
46
47
const declarations = symbol . getDeclarations ( ) ;
47
48
if ( ! ( declarations && declarations . length ) ) {
48
49
return undefined ;
49
50
}
50
51
const checker = context . program . getTypeChecker ( ) ;
51
-
52
+ const scriptTarget = getEmitScriptTarget ( context . program . getCompilerOptions ( ) ) ;
52
53
const declaration = declarations [ 0 ] ;
53
54
const name = getSynthesizedDeepClone ( getNameOfDeclaration ( declaration ) , /*includeTrivia*/ false ) as PropertyName ;
54
55
const visibilityModifier = createVisibilityModifier ( getModifierFlags ( declaration ) ) ;
@@ -61,8 +62,15 @@ namespace ts.codefix {
61
62
case SyntaxKind . PropertySignature :
62
63
case SyntaxKind . PropertyDeclaration :
63
64
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 (
66
74
/*decorators*/ undefined ,
67
75
modifiers ,
68
76
name ,
@@ -72,14 +80,21 @@ namespace ts.codefix {
72
80
break ;
73
81
case SyntaxKind . GetAccessor :
74
82
case SyntaxKind . SetAccessor : {
83
+ let typeNode = checker . typeToTypeNode ( type , enclosingDeclaration , /*flags*/ undefined , getNoopSymbolTrackerWithResolver ( context ) ) ;
75
84
const allAccessors = getAllAccessorDeclarations ( declarations , declaration as AccessorDeclaration ) ;
76
- const typeNode = checker . typeToTypeNode ( type , enclosingDeclaration , /*flags*/ undefined , getNoopSymbolTrackerWithResolver ( context ) ) ;
77
85
const orderedAccessors = allAccessors . secondAccessor
78
86
? [ allAccessors . firstAccessor , allAccessors . secondAccessor ]
79
87
: [ 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
+ }
80
95
for ( const accessor of orderedAccessors ) {
81
96
if ( isGetAccessorDeclaration ( accessor ) ) {
82
- out ( createGetAccessor (
97
+ addClassElement ( createGetAccessor (
83
98
/*decorators*/ undefined ,
84
99
modifiers ,
85
100
name ,
@@ -91,7 +106,7 @@ namespace ts.codefix {
91
106
Debug . assertNode ( accessor , isSetAccessorDeclaration , "The counterpart to a getter should be a setter" ) ;
92
107
const parameter = getSetAccessorValueParameter ( accessor ) ;
93
108
const parameterName = parameter && isIdentifier ( parameter . name ) ? idText ( parameter . name ) : undefined ;
94
- out ( createSetAccessor (
109
+ addClassElement ( createSetAccessor (
95
110
/*decorators*/ undefined ,
96
111
modifiers ,
97
112
name ,
@@ -134,15 +149,15 @@ namespace ts.codefix {
134
149
}
135
150
else {
136
151
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 ) ) ;
138
153
}
139
154
}
140
155
break ;
141
156
}
142
157
143
158
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 ) ;
146
161
}
147
162
}
148
163
@@ -154,13 +169,53 @@ namespace ts.codefix {
154
169
name : PropertyName ,
155
170
optional : boolean ,
156
171
body : Block | undefined ,
172
+ importAdder : ImportAdder | undefined ,
157
173
) : MethodDeclaration | undefined {
158
174
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 ) ) ;
160
178
if ( ! signatureDeclaration ) {
161
179
return undefined ;
162
180
}
163
181
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
+
164
219
signatureDeclaration . decorators = undefined ;
165
220
signatureDeclaration . modifiers = modifiers ;
166
221
signatureDeclaration . name = name ;
@@ -359,4 +414,51 @@ namespace ts.codefix {
359
414
export function findJsonProperty ( obj : ObjectLiteralExpression , name : string ) : PropertyAssignment | undefined {
360
415
return find ( obj . properties , ( p ) : p is PropertyAssignment => isPropertyAssignment ( p ) && ! ! p . name && isStringLiteral ( p . name ) && p . name . text === name ) ;
361
416
}
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
+ }
362
464
}
0 commit comments