Skip to content

Commit 334125e

Browse files
committed
Merge pull request #18343 from amcasey/InsertionPosition
Improve insertion position of extracted methods (cherry picked from commit 40e4591)
1 parent 12d1ea5 commit 334125e

File tree

10 files changed

+313
-6
lines changed

10 files changed

+313
-6
lines changed

src/compiler/core.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2596,4 +2596,6 @@ namespace ts {
25962596
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) {
25972597
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs;
25982598
}
2599+
2600+
export function assertTypeIsNever(_: never): void {}
25992601
}

src/harness/unittests/extractMethods.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,60 @@ function test(x: number) {
594594
finally {
595595
[#|return 1;|]
596596
}
597+
}`);
598+
// Extraction position - namespace
599+
testExtractMethod("extractMethod23",
600+
`namespace NS {
601+
function M1() { }
602+
function M2() {
603+
[#|return 1;|]
604+
}
605+
function M3() { }
606+
}`);
607+
// Extraction position - function
608+
testExtractMethod("extractMethod24",
609+
`function Outer() {
610+
function M1() { }
611+
function M2() {
612+
[#|return 1;|]
613+
}
614+
function M3() { }
615+
}`);
616+
// Extraction position - file
617+
testExtractMethod("extractMethod25",
618+
`function M1() { }
619+
function M2() {
620+
[#|return 1;|]
621+
}
622+
function M3() { }`);
623+
// Extraction position - class without ctor
624+
testExtractMethod("extractMethod26",
625+
`class C {
626+
M1() { }
627+
M2() {
628+
[#|return 1;|]
629+
}
630+
M3() { }
631+
}`);
632+
// Extraction position - class with ctor in middle
633+
testExtractMethod("extractMethod27",
634+
`class C {
635+
M1() { }
636+
M2() {
637+
[#|return 1;|]
638+
}
639+
constructor() { }
640+
M3() { }
641+
}`);
642+
// Extraction position - class with ctor at end
643+
testExtractMethod("extractMethod28",
644+
`class C {
645+
M1() { }
646+
M2() {
647+
[#|return 1;|]
648+
}
649+
M3() { }
650+
constructor() { }
597651
}`);
598652
});
599653

src/services/refactors/extractMethod.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -669,8 +669,14 @@ namespace ts.refactor.extractMethod {
669669
}
670670

671671
const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
672-
// insert function at the end of the scope
673-
changeTracker.insertNodeBefore(context.file, scope.getLastToken(), newFunction, { prefix: context.newLineCharacter, suffix: context.newLineCharacter });
672+
const minInsertionPos = (isReadonlyArray(range.range) ? lastOrUndefined(range.range) : range.range).end;
673+
const nodeToInsertBefore = getNodeToInsertBefore(minInsertionPos, scope);
674+
if (nodeToInsertBefore) {
675+
changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newFunction, { suffix: context.newLineCharacter + context.newLineCharacter });
676+
}
677+
else {
678+
changeTracker.insertNodeBefore(context.file, scope.getLastToken(), newFunction, { prefix: context.newLineCharacter, suffix: context.newLineCharacter });
679+
}
674680

675681
const newNodes: Node[] = [];
676682
// replace range with function call
@@ -752,6 +758,39 @@ namespace ts.refactor.extractMethod {
752758
const renameFilename = renameRange.getSourceFile().fileName;
753759
const renameLocation = getRenameLocation(edits, renameFilename, functionNameText);
754760
return { renameFilename, renameLocation, edits };
761+
762+
function getStatementsOrClassElements(scope: Scope): ReadonlyArray<Statement> | ReadonlyArray<ClassElement> {
763+
if (isFunctionLike(scope)) {
764+
const body = scope.body;
765+
if (isBlock(body)) {
766+
return body.statements;
767+
}
768+
}
769+
else if (isModuleBlock(scope) || isSourceFile(scope)) {
770+
return scope.statements;
771+
}
772+
else if (isClassLike(scope)) {
773+
return scope.members;
774+
}
775+
else {
776+
assertTypeIsNever(scope);
777+
}
778+
779+
return emptyArray;
780+
}
781+
782+
/**
783+
* If `scope` contains a function after `minPos`, then return the first such function.
784+
* Otherwise, return `undefined`.
785+
*/
786+
function getNodeToInsertBefore(minPos: number, scope: Scope): Node | undefined {
787+
const children = getStatementsOrClassElements(scope);
788+
for (const child of children) {
789+
if (child.pos >= minPos && isFunctionLike(child) && !isConstructorDeclaration(child)) {
790+
return child;
791+
}
792+
}
793+
}
755794
}
756795

757796
function getRenameLocation(edits: ReadonlyArray<FileTextChanges>, renameFilename: string, functionNameText: string): number {
@@ -784,6 +823,7 @@ namespace ts.refactor.extractMethod {
784823
}
785824
}
786825

826+
787827
function transformFunctionBody(body: Node, writes: ReadonlyArray<UsageEntry>, substitutions: ReadonlyMap<Node>, hasReturn: boolean): { body: Block, returnValueProperty: string } {
788828
if (isBlock(body) && !writes && substitutions.size === 0) {
789829
// already block, no writes to propagate back, no substitutions - can use node as is
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// ==ORIGINAL==
2+
namespace NS {
3+
function M1() { }
4+
function M2() {
5+
return 1;
6+
}
7+
function M3() { }
8+
}
9+
// ==SCOPE::function 'M2'==
10+
namespace NS {
11+
function M1() { }
12+
function M2() {
13+
return /*RENAME*/newFunction();
14+
15+
function newFunction() {
16+
return 1;
17+
}
18+
}
19+
function M3() { }
20+
}
21+
// ==SCOPE::namespace 'NS'==
22+
namespace NS {
23+
function M1() { }
24+
function M2() {
25+
return /*RENAME*/newFunction();
26+
}
27+
function newFunction() {
28+
return 1;
29+
}
30+
31+
function M3() { }
32+
}
33+
// ==SCOPE::global scope==
34+
namespace NS {
35+
function M1() { }
36+
function M2() {
37+
return /*RENAME*/newFunction();
38+
}
39+
function M3() { }
40+
}
41+
function newFunction() {
42+
return 1;
43+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// ==ORIGINAL==
2+
function Outer() {
3+
function M1() { }
4+
function M2() {
5+
return 1;
6+
}
7+
function M3() { }
8+
}
9+
// ==SCOPE::function 'M2'==
10+
function Outer() {
11+
function M1() { }
12+
function M2() {
13+
return /*RENAME*/newFunction();
14+
15+
function newFunction() {
16+
return 1;
17+
}
18+
}
19+
function M3() { }
20+
}
21+
// ==SCOPE::function 'Outer'==
22+
function Outer() {
23+
function M1() { }
24+
function M2() {
25+
return /*RENAME*/newFunction();
26+
}
27+
function newFunction() {
28+
return 1;
29+
}
30+
31+
function M3() { }
32+
}
33+
// ==SCOPE::global scope==
34+
function Outer() {
35+
function M1() { }
36+
function M2() {
37+
return /*RENAME*/newFunction();
38+
}
39+
function M3() { }
40+
}
41+
function newFunction() {
42+
return 1;
43+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// ==ORIGINAL==
2+
function M1() { }
3+
function M2() {
4+
return 1;
5+
}
6+
function M3() { }
7+
// ==SCOPE::function 'M2'==
8+
function M1() { }
9+
function M2() {
10+
return /*RENAME*/newFunction();
11+
12+
function newFunction() {
13+
return 1;
14+
}
15+
}
16+
function M3() { }
17+
// ==SCOPE::global scope==
18+
function M1() { }
19+
function M2() {
20+
return /*RENAME*/newFunction();
21+
}
22+
function newFunction() {
23+
return 1;
24+
}
25+
26+
function M3() { }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// ==ORIGINAL==
2+
class C {
3+
M1() { }
4+
M2() {
5+
return 1;
6+
}
7+
M3() { }
8+
}
9+
// ==SCOPE::class 'C'==
10+
class C {
11+
M1() { }
12+
M2() {
13+
return this./*RENAME*/newFunction();
14+
}
15+
private newFunction() {
16+
return 1;
17+
}
18+
19+
M3() { }
20+
}
21+
// ==SCOPE::global scope==
22+
class C {
23+
M1() { }
24+
M2() {
25+
return /*RENAME*/newFunction();
26+
}
27+
M3() { }
28+
}
29+
function newFunction() {
30+
return 1;
31+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// ==ORIGINAL==
2+
class C {
3+
M1() { }
4+
M2() {
5+
return 1;
6+
}
7+
constructor() { }
8+
M3() { }
9+
}
10+
// ==SCOPE::class 'C'==
11+
class C {
12+
M1() { }
13+
M2() {
14+
return this./*RENAME*/newFunction();
15+
}
16+
constructor() { }
17+
private newFunction() {
18+
return 1;
19+
}
20+
21+
M3() { }
22+
}
23+
// ==SCOPE::global scope==
24+
class C {
25+
M1() { }
26+
M2() {
27+
return /*RENAME*/newFunction();
28+
}
29+
constructor() { }
30+
M3() { }
31+
}
32+
function newFunction() {
33+
return 1;
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// ==ORIGINAL==
2+
class C {
3+
M1() { }
4+
M2() {
5+
return 1;
6+
}
7+
M3() { }
8+
constructor() { }
9+
}
10+
// ==SCOPE::class 'C'==
11+
class C {
12+
M1() { }
13+
M2() {
14+
return this./*RENAME*/newFunction();
15+
}
16+
private newFunction() {
17+
return 1;
18+
}
19+
20+
M3() { }
21+
constructor() { }
22+
}
23+
// ==SCOPE::global scope==
24+
class C {
25+
M1() { }
26+
M2() {
27+
return /*RENAME*/newFunction();
28+
}
29+
M3() { }
30+
constructor() { }
31+
}
32+
function newFunction() {
33+
return 1;
34+
}

tests/cases/fourslash/extract-method13.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ edit.applyRefactor({
3737
constructor(q: string = C.newFunction()) {
3838
}
3939
40-
private static newFunction(): string {
41-
return "a" + "b";
42-
}
43-
4440
private static newFunction_1() {
4541
return 1 + 1;
4642
}
43+
44+
private static newFunction(): string {
45+
return "a" + "b";
46+
}
4747
}`
4848
});

0 commit comments

Comments
 (0)