diff --git a/src/harness/unittests/extractConstants.ts b/src/harness/unittests/extractConstants.ts index c5ddc18fea4..09f8db34f31 100644 --- a/src/harness/unittests/extractConstants.ts +++ b/src/harness/unittests/extractConstants.ts @@ -230,6 +230,30 @@ function f(): void { } testExtractConstantFailed("extractConstant_Never", ` function f(): never { } [#|f();|]`); + + testExtractConstant("extractConstant_This_Constructor", ` +class C { + constructor() { + [#|this.m2()|]; + } + m2() { return 1; } +}`); + + testExtractConstant("extractConstant_This_Method", ` +class C { + m1() { + [#|this.m2()|]; + } + m2() { return 1; } +}`); + + testExtractConstant("extractConstant_This_Property", ` +namespace N { // Force this test to be TS-only + class C { + x = 1; + y = [#|this.x|]; + } +}`); }); function testExtractConstant(caption: string, text: string) { diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index a03bb843600..9dd148e5420 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -476,7 +476,10 @@ namespace ts.refactor.extractSymbol { // 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 const containingClass = getContainingClass(current); if (containingClass) { - return [containingClass]; + const containingFunction = findAncestor(current, isFunctionLikeDeclaration); + return containingFunction + ? [containingFunction, containingClass] + : [containingClass]; } } diff --git a/tests/baselines/reference/extractConstant/extractConstant_This_Constructor.js b/tests/baselines/reference/extractConstant/extractConstant_This_Constructor.js new file mode 100644 index 00000000000..cf45ab2cd3f --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_This_Constructor.js @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +class C { + constructor() { + /*[#|*/this.m2()/*|]*/; + } + m2() { return 1; } +} +// ==SCOPE::Extract to constant in enclosing scope== + +class C { + constructor() { + const /*RENAME*/newLocal = this.m2(); + } + m2() { return 1; } +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_This_Constructor.ts b/tests/baselines/reference/extractConstant/extractConstant_This_Constructor.ts new file mode 100644 index 00000000000..d36d1a6fa21 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_This_Constructor.ts @@ -0,0 +1,26 @@ +// ==ORIGINAL== + +class C { + constructor() { + /*[#|*/this.m2()/*|]*/; + } + m2() { return 1; } +} +// ==SCOPE::Extract to constant in enclosing scope== + +class C { + constructor() { + const /*RENAME*/newLocal = this.m2(); + } + m2() { return 1; } +} +// ==SCOPE::Extract to readonly field in class 'C'== + +class C { + private readonly newProperty = this.m2(); + + constructor() { + this./*RENAME*/newProperty; + } + m2() { return 1; } +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_This_Method.js b/tests/baselines/reference/extractConstant/extractConstant_This_Method.js new file mode 100644 index 00000000000..fd703868e9f --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_This_Method.js @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +class C { + m1() { + /*[#|*/this.m2()/*|]*/; + } + m2() { return 1; } +} +// ==SCOPE::Extract to constant in enclosing scope== + +class C { + m1() { + const /*RENAME*/newLocal = this.m2(); + } + m2() { return 1; } +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_This_Method.ts b/tests/baselines/reference/extractConstant/extractConstant_This_Method.ts new file mode 100644 index 00000000000..0dbaa4372d4 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_This_Method.ts @@ -0,0 +1,26 @@ +// ==ORIGINAL== + +class C { + m1() { + /*[#|*/this.m2()/*|]*/; + } + m2() { return 1; } +} +// ==SCOPE::Extract to constant in enclosing scope== + +class C { + m1() { + const /*RENAME*/newLocal = this.m2(); + } + m2() { return 1; } +} +// ==SCOPE::Extract to readonly field in class 'C'== + +class C { + private readonly newProperty = this.m2(); + + m1() { + this./*RENAME*/newProperty; + } + m2() { return 1; } +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_This_Property.ts b/tests/baselines/reference/extractConstant/extractConstant_This_Property.ts new file mode 100644 index 00000000000..04b3b50da1b --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_This_Property.ts @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +namespace N { // Force this test to be TS-only + class C { + x = 1; + y = /*[#|*/this.x/*|]*/; + } +} +// ==SCOPE::Extract to readonly field in class 'C'== + +namespace N { // Force this test to be TS-only + class C { + x = 1; + private readonly newProperty = this.x; + + y = this./*RENAME*/newProperty; + } +} \ No newline at end of file diff --git a/tests/cases/fourslash/extract-method20.ts b/tests/cases/fourslash/extract-method20.ts index 75927f0fd5c..bd137c55d19 100644 --- a/tests/cases/fourslash/extract-method20.ts +++ b/tests/cases/fourslash/extract-method20.ts @@ -10,5 +10,6 @@ //// } goTo.select('a', 'b') -verify.refactorAvailable('Extract Symbol', 'function_scope_0'); -verify.not.refactorAvailable('Extract Symbol', 'function_scope_1'); +verify.not.refactorAvailable('Extract Symbol', 'function_scope_0'); +verify.refactorAvailable('Extract Symbol', 'function_scope_1'); +verify.not.refactorAvailable('Extract Symbol', 'function_scope_2');