@@ -217,6 +217,7 @@ namespace ts.refactor.extractSymbol {
217
217
export const cannotAccessVariablesFromNestedScopes = createMessage ( "Cannot access variables from nested scopes" ) ;
218
218
export const cannotExtractToJSClass = createMessage ( "Cannot extract constant to a class scope in JS" ) ;
219
219
export const cannotExtractToExpressionArrowFunction = createMessage ( "Cannot extract constant to an arrow function without a block" ) ;
220
+ export const cannotExtractFunctionsContainingThisToMethod = createMessage ( "Cannot extract functions containing this to method" ) ;
220
221
}
221
222
222
223
enum RangeFacts {
@@ -225,10 +226,11 @@ namespace ts.refactor.extractSymbol {
225
226
IsGenerator = 1 << 1 ,
226
227
IsAsyncFunction = 1 << 2 ,
227
228
UsesThis = 1 << 3 ,
229
+ UsesThisInFunction = 1 << 4 ,
228
230
/**
229
231
* The range is in a function which needs the 'static' modifier in a class
230
232
*/
231
- InStaticRegion = 1 << 4
233
+ InStaticRegion = 1 << 5 ,
232
234
}
233
235
234
236
/**
@@ -242,6 +244,10 @@ namespace ts.refactor.extractSymbol {
242
244
* Used to ensure we don't turn something used outside the range free (or worse, resolve to a different entity).
243
245
*/
244
246
readonly declarations : Symbol [ ] ;
247
+ /**
248
+ * If `this` is referring to a function instead of class, we need to retrieve its type.
249
+ */
250
+ readonly thisNode : Node | undefined ;
245
251
}
246
252
247
253
/**
@@ -294,6 +300,8 @@ namespace ts.refactor.extractSymbol {
294
300
// about what things need to be done as part of the extraction.
295
301
let rangeFacts = RangeFacts . None ;
296
302
303
+ let thisNode : Node | undefined ;
304
+
297
305
if ( ! start || ! end ) {
298
306
// cannot find either start or end node
299
307
return { errors : [ createFileDiagnostic ( sourceFile , span . start , length , Messages . cannotExtractRange ) ] } ;
@@ -336,7 +344,7 @@ namespace ts.refactor.extractSymbol {
336
344
return { errors : [ createFileDiagnostic ( sourceFile , span . start , length , Messages . cannotExtractRange ) ] } ;
337
345
}
338
346
339
- return { targetRange : { range : statements , facts : rangeFacts , declarations } } ;
347
+ return { targetRange : { range : statements , facts : rangeFacts , declarations, thisNode } } ;
340
348
}
341
349
342
350
if ( isReturnStatement ( start ) && ! start . expression ) {
@@ -351,7 +359,7 @@ namespace ts.refactor.extractSymbol {
351
359
if ( errors ) {
352
360
return { errors } ;
353
361
}
354
- return { targetRange : { range : getStatementOrExpressionRange ( node ) ! , facts : rangeFacts , declarations } } ; // TODO: GH#18217
362
+ return { targetRange : { range : getStatementOrExpressionRange ( node ) ! , facts : rangeFacts , declarations, thisNode } } ; // TODO: GH#18217
355
363
356
364
/**
357
365
* Attempt to refine the extraction node (generally, by shrinking it) to produce better results.
@@ -453,6 +461,17 @@ namespace ts.refactor.extractSymbol {
453
461
454
462
visit ( nodeToCheck ) ;
455
463
464
+ if ( rangeFacts & RangeFacts . UsesThis ) {
465
+ const container = getThisContainer ( nodeToCheck , /** includeArrowFunctions */ false ) ;
466
+ if (
467
+ container . kind === SyntaxKind . FunctionDeclaration ||
468
+ ( container . kind === SyntaxKind . MethodDeclaration && container . parent . kind === SyntaxKind . ObjectLiteralExpression ) ||
469
+ container . kind === SyntaxKind . FunctionExpression
470
+ ) {
471
+ rangeFacts |= RangeFacts . UsesThisInFunction ;
472
+ }
473
+ }
474
+
456
475
return errors ;
457
476
458
477
function visit ( node : Node ) {
@@ -494,13 +513,15 @@ namespace ts.refactor.extractSymbol {
494
513
}
495
514
else {
496
515
rangeFacts |= RangeFacts . UsesThis ;
516
+ thisNode = node ;
497
517
}
498
518
break ;
499
519
case SyntaxKind . ArrowFunction :
500
520
// check if arrow function uses this
501
521
forEachChild ( node , function check ( n ) {
502
522
if ( isThis ( n ) ) {
503
523
rangeFacts |= RangeFacts . UsesThis ;
524
+ thisNode = node ;
504
525
}
505
526
else if ( isClassLike ( n ) || ( isFunctionLike ( n ) && ! isArrowFunction ( n ) ) ) {
506
527
return false ;
@@ -559,6 +580,7 @@ namespace ts.refactor.extractSymbol {
559
580
case SyntaxKind . ThisType :
560
581
case SyntaxKind . ThisKeyword :
561
582
rangeFacts |= RangeFacts . UsesThis ;
583
+ thisNode = node ;
562
584
break ;
563
585
case SyntaxKind . LabeledStatement : {
564
586
const label = ( node as LabeledStatement ) . label ;
@@ -650,7 +672,7 @@ namespace ts.refactor.extractSymbol {
650
672
*/
651
673
function collectEnclosingScopes ( range : TargetRange ) : Scope [ ] {
652
674
let current : Node = isReadonlyArray ( range . range ) ? first ( range . range ) : range . range ;
653
- if ( range . facts & RangeFacts . UsesThis ) {
675
+ if ( range . facts & RangeFacts . UsesThis && ! ( range . facts & RangeFacts . UsesThisInFunction ) ) {
654
676
// if range uses this as keyword or as type inside the class then it can only be extracted to a method of the containing class
655
677
const containingClass = getContainingClass ( current ) ;
656
678
if ( containingClass ) {
@@ -904,6 +926,8 @@ namespace ts.refactor.extractSymbol {
904
926
905
927
let newFunction : MethodDeclaration | FunctionDeclaration ;
906
928
929
+ const callThis = ! ! ( range . facts & RangeFacts . UsesThisInFunction ) ;
930
+
907
931
if ( isClassLike ( scope ) ) {
908
932
// always create private method in TypeScript files
909
933
const modifiers : Modifier [ ] = isJS ? [ ] : [ factory . createModifier ( SyntaxKind . PrivateKeyword ) ] ;
@@ -926,6 +950,23 @@ namespace ts.refactor.extractSymbol {
926
950
) ;
927
951
}
928
952
else {
953
+ if ( callThis ) {
954
+ parameters . unshift (
955
+ factory . createParameterDeclaration (
956
+ /*decorators*/ undefined ,
957
+ /*modifiers*/ undefined ,
958
+ /*dotDotDotToken*/ undefined ,
959
+ /*name*/ "this" ,
960
+ /*questionToken*/ undefined ,
961
+ checker . typeToTypeNode (
962
+ checker . getTypeAtLocation ( range . thisNode ! ) ,
963
+ scope ,
964
+ NodeBuilderFlags . NoTruncation
965
+ ) ,
966
+ /*initializer*/ undefined ,
967
+ )
968
+ ) ;
969
+ }
929
970
newFunction = factory . createFunctionDeclaration (
930
971
/*decorators*/ undefined ,
931
972
range . facts & RangeFacts . IsAsyncFunction ? [ factory . createToken ( SyntaxKind . AsyncKeyword ) ] : undefined ,
@@ -953,8 +994,15 @@ namespace ts.refactor.extractSymbol {
953
994
// replace range with function call
954
995
const called = getCalledExpression ( scope , range , functionNameText ) ;
955
996
997
+ if ( callThis ) {
998
+ callArguments . unshift ( factory . createIdentifier ( "this" ) ) ;
999
+ }
1000
+
956
1001
let call : Expression = factory . createCallExpression (
957
- called ,
1002
+ callThis ? factory . createPropertyAccessExpression (
1003
+ called ,
1004
+ "call"
1005
+ ) : called ,
958
1006
callTypeArguments , // Note that no attempt is made to take advantage of type argument inference
959
1007
callArguments ) ;
960
1008
if ( range . facts & RangeFacts . IsGenerator ) {
@@ -1710,6 +1758,10 @@ namespace ts.refactor.extractSymbol {
1710
1758
constantErrorsPerScope [ i ] . push ( createDiagnosticForNode ( errorNode , Messages . cannotAccessVariablesFromNestedScopes ) ) ;
1711
1759
}
1712
1760
1761
+ if ( targetRange . facts & RangeFacts . UsesThisInFunction && isClassLike ( scopes [ i ] ) ) {
1762
+ functionErrorsPerScope [ i ] . push ( createDiagnosticForNode ( targetRange . thisNode ! , Messages . cannotExtractFunctionsContainingThisToMethod ) ) ;
1763
+ }
1764
+
1713
1765
let hasWrite = false ;
1714
1766
let readonlyClassPropertyWrite : Declaration | undefined ;
1715
1767
usagesPerScope [ i ] . usages . forEach ( value => {
0 commit comments