@@ -51,18 +51,14 @@ namespace ts.refactor.convertParamsToDestructuredObject {
51
51
changes : textChanges . ChangeTracker ,
52
52
functionDeclaration : ValidFunctionDeclaration ,
53
53
groupedReferences : GroupedReferences ) : void {
54
- const newParamDeclaration = map ( createNewParameters ( functionDeclaration , program , host ) , param => getSynthesizedDeepClone ( param ) ) ;
55
- changes . replaceNodeRangeWithNodes (
56
- sourceFile ,
57
- first ( functionDeclaration . parameters ) ,
58
- last ( functionDeclaration . parameters ) ,
59
- newParamDeclaration ,
60
- { joiner : ", " ,
61
- // indentation is set to 0 because otherwise the object parameter will be indented if there is a `this` parameter
62
- indentation : 0 ,
63
- leadingTriviaOption : textChanges . LeadingTriviaOption . IncludeAll ,
64
- trailingTriviaOption : textChanges . TrailingTriviaOption . Include
65
- } ) ;
54
+ const signature = groupedReferences . signature ;
55
+ const newFunctionDeclarationParams = map ( createNewParameters ( functionDeclaration , program , host ) , param => getSynthesizedDeepClone ( param ) ) ;
56
+
57
+ if ( signature ) {
58
+ const newSignatureParams = map ( createNewParameters ( signature , program , host ) , param => getSynthesizedDeepClone ( param ) ) ;
59
+ replaceParameters ( signature , newSignatureParams ) ;
60
+ }
61
+ replaceParameters ( functionDeclaration , newFunctionDeclarationParams ) ;
66
62
67
63
const functionCalls = sortAndDeduplicate ( groupedReferences . functionCalls , /*comparer*/ ( a , b ) => compareValues ( a . pos , b . pos ) ) ;
68
64
for ( const call of functionCalls ) {
@@ -76,6 +72,21 @@ namespace ts.refactor.convertParamsToDestructuredObject {
76
72
{ leadingTriviaOption : textChanges . LeadingTriviaOption . IncludeAll , trailingTriviaOption : textChanges . TrailingTriviaOption . Include } ) ;
77
73
}
78
74
}
75
+
76
+ function replaceParameters ( declarationOrSignature : ValidFunctionDeclaration | ValidMethodSignature , parameterDeclarations : ParameterDeclaration [ ] ) {
77
+ changes . replaceNodeRangeWithNodes (
78
+ sourceFile ,
79
+ first ( declarationOrSignature . parameters ) ,
80
+ last ( declarationOrSignature . parameters ) ,
81
+ parameterDeclarations ,
82
+ {
83
+ joiner : ", " ,
84
+ // indentation is set to 0 because otherwise the object parameter will be indented if there is a `this` parameter
85
+ indentation : 0 ,
86
+ leadingTriviaOption : textChanges . LeadingTriviaOption . IncludeAll ,
87
+ trailingTriviaOption : textChanges . TrailingTriviaOption . Include
88
+ } ) ;
89
+ }
79
90
}
80
91
81
92
function getGroupedReferences ( functionDeclaration : ValidFunctionDeclaration , program : Program , cancellationToken : CancellationToken ) : GroupedReferences {
@@ -99,13 +110,41 @@ namespace ts.refactor.convertParamsToDestructuredObject {
99
110
const functionSymbols = map ( functionNames , getSymbolTargetAtLocation ) ;
100
111
const classSymbols = map ( classNames , getSymbolTargetAtLocation ) ;
101
112
const isConstructor = isConstructorDeclaration ( functionDeclaration ) ;
113
+ const contextualSymbols = map ( functionNames , name => getSymbolForContextualType ( name , checker ) ) ;
102
114
103
115
for ( const entry of referenceEntries ) {
104
- if ( entry . kind !== FindAllReferences . EntryKind . Node ) {
116
+ if ( entry . kind === FindAllReferences . EntryKind . Span ) {
105
117
groupedReferences . valid = false ;
106
118
continue ;
107
119
}
108
120
121
+ /* Declarations in object literals may be implementations of method signatures which have a different symbol from the declaration
122
+ For example:
123
+ interface IFoo { m(a: number): void }
124
+ const foo: IFoo = { m(a: number): void {} }
125
+ In these cases we get the symbol for the signature from the contextual type.
126
+ */
127
+ if ( contains ( contextualSymbols , getSymbolTargetAtLocation ( entry . node ) ) ) {
128
+ if ( isValidMethodSignature ( entry . node . parent ) ) {
129
+ groupedReferences . signature = entry . node . parent ;
130
+ continue ;
131
+ }
132
+ const call = entryToFunctionCall ( entry ) ;
133
+ if ( call ) {
134
+ groupedReferences . functionCalls . push ( call ) ;
135
+ continue ;
136
+ }
137
+ }
138
+
139
+ const contextualSymbol = getSymbolForContextualType ( entry . node , checker ) ;
140
+ if ( contextualSymbol && contains ( contextualSymbols , contextualSymbol ) ) {
141
+ const decl = entryToDeclaration ( entry ) ;
142
+ if ( decl ) {
143
+ groupedReferences . declarations . push ( decl ) ;
144
+ continue ;
145
+ }
146
+ }
147
+
109
148
/* We compare symbols because in some cases find all references wil return a reference that may or may not be to the refactored function.
110
149
Example from the refactorConvertParamsToDestructuredObject_methodCallUnion.ts test:
111
150
class A { foo(a: number, b: number) { return a + b; } }
@@ -175,6 +214,20 @@ namespace ts.refactor.convertParamsToDestructuredObject {
175
214
}
176
215
}
177
216
217
+ /**
218
+ * Gets the symbol for the contextual type of the node if it is not a union or intersection.
219
+ */
220
+ function getSymbolForContextualType ( node : Node , checker : TypeChecker ) : Symbol | undefined {
221
+ const element = getContainingObjectLiteralElement ( node ) ;
222
+ if ( element ) {
223
+ const contextualType = checker . getContextualTypeForObjectLiteralElement ( < ObjectLiteralElementLike > element ) ;
224
+ const symbol = contextualType ?. getSymbol ( ) ;
225
+ if ( symbol && ! ( getCheckFlags ( symbol ) & CheckFlags . Synthetic ) ) {
226
+ return symbol ;
227
+ }
228
+ }
229
+ }
230
+
178
231
function entryToImportOrExport ( entry : FindAllReferences . NodeEntry ) : Node | undefined {
179
232
const node = entry . node ;
180
233
@@ -292,6 +345,10 @@ namespace ts.refactor.convertParamsToDestructuredObject {
292
345
return false ;
293
346
}
294
347
348
+ function isValidMethodSignature ( node : Node ) : node is ValidMethodSignature {
349
+ return isMethodSignature ( node ) && ( isInterfaceDeclaration ( node . parent ) || isTypeLiteralNode ( node . parent ) ) ;
350
+ }
351
+
295
352
function isValidFunctionDeclaration (
296
353
functionDeclaration : FunctionLikeDeclaration ,
297
354
checker : TypeChecker ) : functionDeclaration is ValidFunctionDeclaration {
@@ -300,6 +357,11 @@ namespace ts.refactor.convertParamsToDestructuredObject {
300
357
case SyntaxKind . FunctionDeclaration :
301
358
return hasNameOrDefault ( functionDeclaration ) && isSingleImplementation ( functionDeclaration , checker ) ;
302
359
case SyntaxKind . MethodDeclaration :
360
+ if ( isObjectLiteralExpression ( functionDeclaration . parent ) ) {
361
+ const contextualSymbol = getSymbolForContextualType ( functionDeclaration . name , checker ) ;
362
+ // don't offer the refactor when there are multiple signatures since we won't know which ones the user wants to change
363
+ return contextualSymbol ?. declarations . length === 1 && isSingleImplementation ( functionDeclaration , checker ) ;
364
+ }
303
365
return isSingleImplementation ( functionDeclaration , checker ) ;
304
366
case SyntaxKind . Constructor :
305
367
if ( isClassDeclaration ( functionDeclaration . parent ) ) {
@@ -398,7 +460,7 @@ namespace ts.refactor.convertParamsToDestructuredObject {
398
460
return objectLiteral ;
399
461
}
400
462
401
- function createNewParameters ( functionDeclaration : ValidFunctionDeclaration , program : Program , host : LanguageServiceHost ) : NodeArray < ParameterDeclaration > {
463
+ function createNewParameters ( functionDeclaration : ValidFunctionDeclaration | ValidMethodSignature , program : Program , host : LanguageServiceHost ) : NodeArray < ParameterDeclaration > {
402
464
const checker = program . getTypeChecker ( ) ;
403
465
const refactorableParameters = getRefactorableParameters ( functionDeclaration . parameters ) ;
404
466
const bindingElements = map ( refactorableParameters , createBindingElementFromParameterDeclaration ) ;
@@ -584,6 +646,10 @@ namespace ts.refactor.convertParamsToDestructuredObject {
584
646
parameters : NodeArray < ValidParameterDeclaration > ;
585
647
}
586
648
649
+ interface ValidMethodSignature extends MethodSignature {
650
+ parameters : NodeArray < ValidParameterDeclaration > ;
651
+ }
652
+
587
653
type ValidFunctionDeclaration = ValidConstructor | ValidFunction | ValidMethod | ValidArrowFunction | ValidFunctionExpression ;
588
654
589
655
interface ValidParameterDeclaration extends ParameterDeclaration {
@@ -595,6 +661,7 @@ namespace ts.refactor.convertParamsToDestructuredObject {
595
661
interface GroupedReferences {
596
662
functionCalls : ( CallExpression | NewExpression ) [ ] ;
597
663
declarations : Node [ ] ;
664
+ signature ?: ValidMethodSignature ;
598
665
classReferences ?: ClassReferences ;
599
666
valid : boolean ;
600
667
}
0 commit comments