Skip to content

Commit b9fb173

Browse files
authored
Merge pull request #18919 from amcasey/ExtractLocalRefinements
Improve Extract Constant's handling of expression statements
2 parents 301c90c + 7b1147f commit b9fb173

File tree

6 files changed

+107
-13
lines changed

6 files changed

+107
-13
lines changed

src/harness/unittests/extractConstants.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ namespace ts {
3333
testExtractConstant("extractConstant_ExpressionStatementExpression",
3434
`[#|"hello"|];`);
3535

36+
testExtractConstant("extractConstant_ExpressionStatementInNestedScope", `
37+
let i = 0;
38+
function F() {
39+
[#|i++|];
40+
}
41+
`);
42+
43+
testExtractConstant("extractConstant_ExpressionStatementConsumesLocal", `
44+
function F() {
45+
let i = 0;
46+
[#|i++|];
47+
}
48+
`);
49+
3650
testExtractConstant("extractConstant_BlockScopes_NoDependencies",
3751
`for (let i = 0; i < 10; i++) {
3852
for (let j = 0; j < 10; j++) {

src/services/refactors/extractSymbol.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -911,12 +911,13 @@ namespace ts.refactor.extractSymbol {
911911
const localReference = createIdentifier(localNameText);
912912
changeTracker.replaceRange(context.file, { pos: node.getStart(), end: node.end }, localReference);
913913
}
914-
else if (node.parent.kind === SyntaxKind.ExpressionStatement) {
915-
// If the parent is an expression statement, replace the statement with the declaration.
914+
else if (node.parent.kind === SyntaxKind.ExpressionStatement && scope === findAncestor(node, isScope)) {
915+
// If the parent is an expression statement and the target scope is the immediately enclosing one,
916+
// replace the statement with the declaration.
916917
const newVariableStatement = createVariableStatement(
917918
/*modifiers*/ undefined,
918919
createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const));
919-
changeTracker.replaceNodeWithNodes(context.file, node.parent, [newVariableStatement], { nodeSeparator: context.newLineCharacter });
920+
changeTracker.replaceRange(context.file, { pos: node.parent.getStart(), end: node.parent.end }, newVariableStatement);
920921
}
921922
else {
922923
const newVariableStatement = createVariableStatement(
@@ -940,8 +941,14 @@ namespace ts.refactor.extractSymbol {
940941
}
941942

942943
// Consume
943-
const localReference = createIdentifier(localNameText);
944-
changeTracker.replaceRange(context.file, { pos: node.getStart(), end: node.end }, localReference);
944+
if (node.parent.kind === SyntaxKind.ExpressionStatement) {
945+
// If the parent is an expression statement, delete it.
946+
changeTracker.deleteRange(context.file, { pos: node.parent.getStart(), end: node.parent.end });
947+
}
948+
else {
949+
const localReference = createIdentifier(localNameText);
950+
changeTracker.replaceRange(context.file, { pos: node.getStart(), end: node.end }, localReference);
951+
}
945952
}
946953
}
947954

@@ -1344,14 +1351,13 @@ namespace ts.refactor.extractSymbol {
13441351
}
13451352

13461353
for (let i = 0; i < scopes.length; i++) {
1347-
if (!isReadonlyArray(targetRange.range)) {
1348-
const scopeUsages = usagesPerScope[i];
1349-
// Special case: in the innermost scope, all usages are available.
1350-
// (The computed value reflects the value at the top-level of the scope, but the
1351-
// local will actually be declared at the same level as the extracted expression).
1352-
if (i > 0 && (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0)) {
1353-
constantErrorsPerScope[i].push(createDiagnosticForNode(targetRange.range, Messages.CannotAccessVariablesFromNestedScopes));
1354-
}
1354+
const scopeUsages = usagesPerScope[i];
1355+
// Special case: in the innermost scope, all usages are available.
1356+
// (The computed value reflects the value at the top-level of the scope, but the
1357+
// local will actually be declared at the same level as the extracted expression).
1358+
if (i > 0 && (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0)) {
1359+
const errorNode = isReadonlyArray(targetRange.range) ? targetRange.range[0] : targetRange.range;
1360+
constantErrorsPerScope[i].push(createDiagnosticForNode(errorNode, Messages.CannotAccessVariablesFromNestedScopes));
13551361
}
13561362

13571363
let hasWrite = false;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// ==ORIGINAL==
2+
3+
function F() {
4+
let i = 0;
5+
i++;
6+
}
7+
8+
// ==SCOPE::Extract to constant in enclosing scope==
9+
10+
function F() {
11+
let i = 0;
12+
const /*RENAME*/newLocal = i++;
13+
}
14+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// ==ORIGINAL==
2+
3+
function F() {
4+
let i = 0;
5+
i++;
6+
}
7+
8+
// ==SCOPE::Extract to constant in enclosing scope==
9+
10+
function F() {
11+
let i = 0;
12+
const /*RENAME*/newLocal = i++;
13+
}
14+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// ==ORIGINAL==
2+
3+
let i = 0;
4+
function F() {
5+
i++;
6+
}
7+
8+
// ==SCOPE::Extract to constant in enclosing scope==
9+
10+
let i = 0;
11+
function F() {
12+
const /*RENAME*/newLocal = i++;
13+
}
14+
15+
// ==SCOPE::Extract to constant in global scope==
16+
17+
let i = 0;
18+
const /*RENAME*/newLocal = i++;
19+
20+
function F() {
21+
22+
}
23+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// ==ORIGINAL==
2+
3+
let i = 0;
4+
function F() {
5+
i++;
6+
}
7+
8+
// ==SCOPE::Extract to constant in enclosing scope==
9+
10+
let i = 0;
11+
function F() {
12+
const /*RENAME*/newLocal = i++;
13+
}
14+
15+
// ==SCOPE::Extract to constant in global scope==
16+
17+
let i = 0;
18+
const /*RENAME*/newLocal = i++;
19+
20+
function F() {
21+
22+
}
23+

0 commit comments

Comments
 (0)