@@ -217,6 +217,7 @@ namespace ts.refactor.extractSymbol {
217217 export const cannotAccessVariablesFromNestedScopes = createMessage ( "Cannot access variables from nested scopes" ) ;
218218 export const cannotExtractToJSClass = createMessage ( "Cannot extract constant to a class scope in JS" ) ;
219219 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" ) ;
220221 }
221222
222223 enum RangeFacts {
@@ -225,10 +226,11 @@ namespace ts.refactor.extractSymbol {
225226 IsGenerator = 1 << 1 ,
226227 IsAsyncFunction = 1 << 2 ,
227228 UsesThis = 1 << 3 ,
229+ UsesThisInFunction = 1 << 4 ,
228230 /**
229231 * The range is in a function which needs the 'static' modifier in a class
230232 */
231- InStaticRegion = 1 << 4
233+ InStaticRegion = 1 << 5 ,
232234 }
233235
234236 /**
@@ -242,6 +244,10 @@ namespace ts.refactor.extractSymbol {
242244 * Used to ensure we don't turn something used outside the range free (or worse, resolve to a different entity).
243245 */
244246 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 ;
245251 }
246252
247253 /**
@@ -294,6 +300,8 @@ namespace ts.refactor.extractSymbol {
294300 // about what things need to be done as part of the extraction.
295301 let rangeFacts = RangeFacts . None ;
296302
303+ let thisNode : Node | undefined ;
304+
297305 if ( ! start || ! end ) {
298306 // cannot find either start or end node
299307 return { errors : [ createFileDiagnostic ( sourceFile , span . start , length , Messages . cannotExtractRange ) ] } ;
@@ -336,7 +344,7 @@ namespace ts.refactor.extractSymbol {
336344 return { errors : [ createFileDiagnostic ( sourceFile , span . start , length , Messages . cannotExtractRange ) ] } ;
337345 }
338346
339- return { targetRange : { range : statements , facts : rangeFacts , declarations } } ;
347+ return { targetRange : { range : statements , facts : rangeFacts , declarations, thisNode } } ;
340348 }
341349
342350 if ( isReturnStatement ( start ) && ! start . expression ) {
@@ -351,7 +359,7 @@ namespace ts.refactor.extractSymbol {
351359 if ( errors ) {
352360 return { errors } ;
353361 }
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
355363
356364 /**
357365 * Attempt to refine the extraction node (generally, by shrinking it) to produce better results.
@@ -453,6 +461,17 @@ namespace ts.refactor.extractSymbol {
453461
454462 visit ( nodeToCheck ) ;
455463
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+
456475 return errors ;
457476
458477 function visit ( node : Node ) {
@@ -494,13 +513,15 @@ namespace ts.refactor.extractSymbol {
494513 }
495514 else {
496515 rangeFacts |= RangeFacts . UsesThis ;
516+ thisNode = node ;
497517 }
498518 break ;
499519 case SyntaxKind . ArrowFunction :
500520 // check if arrow function uses this
501521 forEachChild ( node , function check ( n ) {
502522 if ( isThis ( n ) ) {
503523 rangeFacts |= RangeFacts . UsesThis ;
524+ thisNode = node ;
504525 }
505526 else if ( isClassLike ( n ) || ( isFunctionLike ( n ) && ! isArrowFunction ( n ) ) ) {
506527 return false ;
@@ -559,6 +580,7 @@ namespace ts.refactor.extractSymbol {
559580 case SyntaxKind . ThisType :
560581 case SyntaxKind . ThisKeyword :
561582 rangeFacts |= RangeFacts . UsesThis ;
583+ thisNode = node ;
562584 break ;
563585 case SyntaxKind . LabeledStatement : {
564586 const label = ( node as LabeledStatement ) . label ;
@@ -650,7 +672,7 @@ namespace ts.refactor.extractSymbol {
650672 */
651673 function collectEnclosingScopes ( range : TargetRange ) : Scope [ ] {
652674 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 ) ) {
654676 // 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
655677 const containingClass = getContainingClass ( current ) ;
656678 if ( containingClass ) {
@@ -904,6 +926,8 @@ namespace ts.refactor.extractSymbol {
904926
905927 let newFunction : MethodDeclaration | FunctionDeclaration ;
906928
929+ const callThis = ! ! ( range . facts & RangeFacts . UsesThisInFunction ) ;
930+
907931 if ( isClassLike ( scope ) ) {
908932 // always create private method in TypeScript files
909933 const modifiers : Modifier [ ] = isJS ? [ ] : [ factory . createModifier ( SyntaxKind . PrivateKeyword ) ] ;
@@ -926,6 +950,23 @@ namespace ts.refactor.extractSymbol {
926950 ) ;
927951 }
928952 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+ }
929970 newFunction = factory . createFunctionDeclaration (
930971 /*decorators*/ undefined ,
931972 range . facts & RangeFacts . IsAsyncFunction ? [ factory . createToken ( SyntaxKind . AsyncKeyword ) ] : undefined ,
@@ -953,8 +994,15 @@ namespace ts.refactor.extractSymbol {
953994 // replace range with function call
954995 const called = getCalledExpression ( scope , range , functionNameText ) ;
955996
997+ if ( callThis ) {
998+ callArguments . unshift ( factory . createIdentifier ( "this" ) ) ;
999+ }
1000+
9561001 let call : Expression = factory . createCallExpression (
957- called ,
1002+ callThis ? factory . createPropertyAccessExpression (
1003+ called ,
1004+ "call"
1005+ ) : called ,
9581006 callTypeArguments , // Note that no attempt is made to take advantage of type argument inference
9591007 callArguments ) ;
9601008 if ( range . facts & RangeFacts . IsGenerator ) {
@@ -1710,6 +1758,10 @@ namespace ts.refactor.extractSymbol {
17101758 constantErrorsPerScope [ i ] . push ( createDiagnosticForNode ( errorNode , Messages . cannotAccessVariablesFromNestedScopes ) ) ;
17111759 }
17121760
1761+ if ( targetRange . facts & RangeFacts . UsesThisInFunction && isClassLike ( scopes [ i ] ) ) {
1762+ functionErrorsPerScope [ i ] . push ( createDiagnosticForNode ( targetRange . thisNode ! , Messages . cannotExtractFunctionsContainingThisToMethod ) ) ;
1763+ }
1764+
17131765 let hasWrite = false ;
17141766 let readonlyClassPropertyWrite : Declaration | undefined ;
17151767 usagesPerScope [ i ] . usages . forEach ( value => {
0 commit comments