Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,13 +500,11 @@ namespace ts {
case SyntaxKind.ArrowFunction:
return true;
default:
staticAssertNever(node);
assertTypeIsNever(node);
return false;
}
}

function staticAssertNever(_: never): void {}

// Gets the nearest enclosing block scope container that has the provided node
// as a descendant, that is not the provided node.
export function getEnclosingBlockScopeContainer(node: Node): Node {
Expand Down
58 changes: 56 additions & 2 deletions src/harness/unittests/extractMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ namespace ts {
testExtractRange(`
function f() {
while (true) {
[#|
[#|
if (x) {
return;
} |]
Expand All @@ -234,7 +234,7 @@ namespace ts {
testExtractRange(`
function f() {
while (true) {
[#|
[#|
[$|if (x) {
}
return;|]
Expand Down Expand Up @@ -655,6 +655,60 @@ function test(x: number) {
finally {
[#|return 1;|]
}
}`);
// Extraction position - namespace
testExtractMethod("extractMethod23",
`namespace NS {
function M1() { }
function M2() {
[#|return 1;|]
}
function M3() { }
}`);
// Extraction position - function
testExtractMethod("extractMethod24",
`function Outer() {
function M1() { }
function M2() {
[#|return 1;|]
}
function M3() { }
}`);
// Extraction position - file
testExtractMethod("extractMethod25",
`function M1() { }
function M2() {
[#|return 1;|]
}
function M3() { }`);
// Extraction position - class without ctor
testExtractMethod("extractMethod26",
`class C {
M1() { }
M2() {
[#|return 1;|]
}
M3() { }
}`);
// Extraction position - class with ctor in middle
testExtractMethod("extractMethod27",
`class C {
M1() { }
M2() {
[#|return 1;|]
}
constructor() { }
M3() { }
}`);
// Extraction position - class with ctor at end
testExtractMethod("extractMethod28",
`class C {
M1() { }
M2() {
[#|return 1;|]
}
M3() { }
constructor() { }
}`);
});

Expand Down
43 changes: 41 additions & 2 deletions src/services/refactors/extractMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -697,8 +697,14 @@ namespace ts.refactor.extractMethod {
}

const changeTracker = textChanges.ChangeTracker.fromContext(context);
// insert function at the end of the scope
changeTracker.insertNodeBefore(context.file, scope.getLastToken(), newFunction, { prefix: context.newLineCharacter, suffix: context.newLineCharacter });
const minInsertionPos = (isReadonlyArray(range.range) ? lastOrUndefined(range.range) : range.range).end;
const nodeToInsertBefore = getNodeToInsertBefore(minInsertionPos, scope);
if (nodeToInsertBefore) {
changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newFunction, { suffix: context.newLineCharacter + context.newLineCharacter });
}
else {
changeTracker.insertNodeBefore(context.file, scope.getLastToken(), newFunction, { prefix: context.newLineCharacter, suffix: context.newLineCharacter });
}

const newNodes: Node[] = [];
// replace range with function call
Expand Down Expand Up @@ -831,6 +837,39 @@ namespace ts.refactor.extractMethod {
return "__return";
}

function getStatementsOrClassElements(scope: Scope): ReadonlyArray<Statement> | ReadonlyArray<ClassElement> {
if (isFunctionLike(scope)) {
const body = scope.body;
if (isBlock(body)) {
return body.statements;
}
}
else if (isModuleBlock(scope) || isSourceFile(scope)) {
return scope.statements;
}
else if (isClassLike(scope)) {
return scope.members;
}
else {
assertTypeIsNever(scope);
}

return emptyArray;
}

/**
* If `scope` contains a function after `minPos`, then return the first such function.
* Otherwise, return `undefined`.
*/
function getNodeToInsertBefore(minPos: number, scope: Scope): Node | undefined {
const children = getStatementsOrClassElements(scope);
for (const child of children) {
if (child.pos >= minPos && isFunctionLike(child) && !isConstructorDeclaration(child)) {
return child;
}
}
}

function transformFunctionBody(body: Node) {
if (isBlock(body) && !writes && substitutions.size === 0) {
// already block, no writes to propagate back, no substitutions - can use node as is
Expand Down
43 changes: 43 additions & 0 deletions tests/baselines/reference/extractMethod/extractMethod23.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// ==ORIGINAL==
namespace NS {
function M1() { }
function M2() {
return 1;
}
function M3() { }
}
// ==SCOPE::function 'M2'==
namespace NS {
function M1() { }
function M2() {
return newFunction();

function newFunction() {
return 1;
}
}
function M3() { }
}
// ==SCOPE::namespace 'NS'==
namespace NS {
function M1() { }
function M2() {
return newFunction();
}
function newFunction() {
return 1;
}

function M3() { }
}
// ==SCOPE::global scope==
namespace NS {
function M1() { }
function M2() {
return newFunction();
}
function M3() { }
}
function newFunction() {
return 1;
}
43 changes: 43 additions & 0 deletions tests/baselines/reference/extractMethod/extractMethod24.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// ==ORIGINAL==
function Outer() {
function M1() { }
function M2() {
return 1;
}
function M3() { }
}
// ==SCOPE::function 'M2'==
function Outer() {
function M1() { }
function M2() {
return newFunction();

function newFunction() {
return 1;
}
}
function M3() { }
}
// ==SCOPE::function 'Outer'==
function Outer() {
function M1() { }
function M2() {
return newFunction();
}
function newFunction() {
return 1;
}

function M3() { }
}
// ==SCOPE::global scope==
function Outer() {
function M1() { }
function M2() {
return newFunction();
}
function M3() { }
}
function newFunction() {
return 1;
}
26 changes: 26 additions & 0 deletions tests/baselines/reference/extractMethod/extractMethod25.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// ==ORIGINAL==
function M1() { }
function M2() {
return 1;
}
function M3() { }
// ==SCOPE::function 'M2'==
function M1() { }
function M2() {
return newFunction();

function newFunction() {
return 1;
}
}
function M3() { }
// ==SCOPE::global scope==
function M1() { }
function M2() {
return newFunction();
}
function newFunction() {
return 1;
}

function M3() { }
31 changes: 31 additions & 0 deletions tests/baselines/reference/extractMethod/extractMethod26.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// ==ORIGINAL==
class C {
M1() { }
M2() {
return 1;
}
M3() { }
}
// ==SCOPE::class 'C'==
class C {
M1() { }
M2() {
return this.newFunction();
}
private newFunction() {
return 1;
}

M3() { }
}
// ==SCOPE::global scope==
class C {
M1() { }
M2() {
return newFunction();
}
M3() { }
}
function newFunction() {
return 1;
}
34 changes: 34 additions & 0 deletions tests/baselines/reference/extractMethod/extractMethod27.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// ==ORIGINAL==
class C {
M1() { }
M2() {
return 1;
}
constructor() { }
M3() { }
}
// ==SCOPE::class 'C'==
class C {
M1() { }
M2() {
return this.newFunction();
}
constructor() { }
private newFunction() {
return 1;
}

M3() { }
}
// ==SCOPE::global scope==
class C {
M1() { }
M2() {
return newFunction();
}
constructor() { }
M3() { }
}
function newFunction() {
return 1;
}
34 changes: 34 additions & 0 deletions tests/baselines/reference/extractMethod/extractMethod28.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// ==ORIGINAL==
class C {
M1() { }
M2() {
return 1;
}
M3() { }
constructor() { }
}
// ==SCOPE::class 'C'==
class C {
M1() { }
M2() {
return this.newFunction();
}
private newFunction() {
return 1;
}

M3() { }
constructor() { }
}
// ==SCOPE::global scope==
class C {
M1() { }
M2() {
return newFunction();
}
M3() { }
constructor() { }
}
function newFunction() {
return 1;
}
Loading