Skip to content

Commit b244cd4

Browse files
authored
Merge pull request microsoft#18864 from amcasey/GH18857
Handle repeated substitution of the same symbol during extraction
2 parents d620129 + 3b4cbeb commit b244cd4

File tree

5 files changed

+64
-23
lines changed

5 files changed

+64
-23
lines changed

src/harness/unittests/extractConstants.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,11 @@ namespace ts {
8484
let x = [#|t + 1|];
8585
}`);
8686

87-
// TODO (18857): handle repeated substitution
88-
// testExtractConstant("extractConstant_RepeatedSubstitution",
89-
// `namespace X {
90-
// export const j = 10;
91-
// export const y = [#|j * j|];
92-
// }`);
87+
testExtractConstant("extractConstant_RepeatedSubstitution",
88+
`namespace X {
89+
export const j = 10;
90+
export const y = [#|j * j|];
91+
}`);
9392

9493
testExtractConstant("extractConstant_VariableList_const",
9594
`const a = 1, b = [#|a + 1|];`);

src/harness/unittests/extractFunctions.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -355,12 +355,11 @@ function parsePrimaryExpression(): any {
355355
[#|function G() { }|]
356356
}`);
357357

358-
// TODO (18857): handle repeated substitution
359-
// testExtractFunction("extractFunction_RepeatedSubstitution",
360-
// `namespace X {
361-
// export const j = 10;
362-
// export const y = [#|j * j|];
363-
// }`);
358+
testExtractFunction("extractFunction_RepeatedSubstitution",
359+
`namespace X {
360+
export const j = 10;
361+
export const y = [#|j * j|];
362+
}`);
364363
});
365364

366365
function testExtractFunction(caption: string, text: string) {

src/services/refactors/extractSymbol.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@ namespace ts.refactor.extractSymbol {
10481048
}
10491049
}
10501050

1051-
function transformFunctionBody(body: Node, writes: ReadonlyArray<UsageEntry>, substitutions: ReadonlyMap<Node>, hasReturn: boolean): { body: Block, returnValueProperty: string } {
1051+
function transformFunctionBody(body: Node, writes: ReadonlyArray<UsageEntry>, substitutions: ReadonlyMap<() => Node>, hasReturn: boolean): { body: Block, returnValueProperty: string } {
10521052
if (isBlock(body) && !writes && substitutions.size === 0) {
10531053
// already block, no writes to propagate back, no substitutions - can use node as is
10541054
return { body: createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined };
@@ -1096,21 +1096,21 @@ namespace ts.refactor.extractSymbol {
10961096
const oldIgnoreReturns = ignoreReturns;
10971097
ignoreReturns = ignoreReturns || isFunctionLikeDeclaration(node) || isClassLike(node);
10981098
const substitution = substitutions.get(getNodeId(node).toString());
1099-
const result = substitution || visitEachChild(node, visitor, nullTransformationContext);
1099+
const result = substitution ? substitution() : visitEachChild(node, visitor, nullTransformationContext);
11001100
ignoreReturns = oldIgnoreReturns;
11011101
return result;
11021102
}
11031103
}
11041104
}
11051105

1106-
function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyMap<Node>): Expression {
1106+
function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyMap<() => Node>): Expression {
11071107
return substitutions.size
11081108
? visitor(initializer) as Expression
11091109
: initializer;
11101110

11111111
function visitor(node: Node): VisitResult<Node> {
11121112
const substitution = substitutions.get(getNodeId(node).toString());
1113-
return substitution || visitEachChild(node, visitor, nullTransformationContext);
1113+
return substitution ? substitution() : visitEachChild(node, visitor, nullTransformationContext);
11141114
}
11151115
}
11161116

@@ -1239,7 +1239,7 @@ namespace ts.refactor.extractSymbol {
12391239
interface ScopeUsages {
12401240
readonly usages: Map<UsageEntry>;
12411241
readonly typeParameterUsages: Map<TypeParameter>; // Key is type ID
1242-
readonly substitutions: Map<Node>;
1242+
readonly substitutions: Map<() => Node>;
12431243
}
12441244

12451245
interface ReadsAndWrites {
@@ -1258,7 +1258,7 @@ namespace ts.refactor.extractSymbol {
12581258

12591259
const allTypeParameterUsages = createMap<TypeParameter>(); // Key is type ID
12601260
const usagesPerScope: ScopeUsages[] = [];
1261-
const substitutionsPerScope: Map<Node>[] = [];
1261+
const substitutionsPerScope: Map<() => Node>[] = [];
12621262
const functionErrorsPerScope: Diagnostic[][] = [];
12631263
const constantErrorsPerScope: Diagnostic[][] = [];
12641264
const visibleDeclarationsInExtractedRange: Symbol[] = [];
@@ -1270,8 +1270,8 @@ namespace ts.refactor.extractSymbol {
12701270

12711271
// initialize results
12721272
for (const scope of scopes) {
1273-
usagesPerScope.push({ usages: createMap<UsageEntry>(), typeParameterUsages: createMap<TypeParameter>(), substitutions: createMap<Expression>() });
1274-
substitutionsPerScope.push(createMap<Expression>());
1273+
usagesPerScope.push({ usages: createMap<UsageEntry>(), typeParameterUsages: createMap<TypeParameter>(), substitutions: createMap<() => Expression>() });
1274+
substitutionsPerScope.push(createMap<() => Expression>());
12751275

12761276
functionErrorsPerScope.push(
12771277
isFunctionLikeDeclaration(scope) && scope.kind !== SyntaxKind.FunctionDeclaration
@@ -1567,18 +1567,20 @@ namespace ts.refactor.extractSymbol {
15671567
}
15681568
}
15691569

1570-
function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol, scopeDecl: Node, isTypeNode: boolean): PropertyAccessExpression | EntityName {
1570+
function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol, scopeDecl: Node, isTypeNode: boolean): () => (PropertyAccessExpression | EntityName) {
15711571
if (!symbol) {
15721572
return undefined;
15731573
}
15741574
if (symbol.getDeclarations().some(d => d.parent === scopeDecl)) {
1575-
return createIdentifier(symbol.name);
1575+
return () => createIdentifier(symbol.name);
15761576
}
15771577
const prefix = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.parent, scopeDecl, isTypeNode);
15781578
if (prefix === undefined) {
15791579
return undefined;
15801580
}
1581-
return isTypeNode ? createQualifiedName(<EntityName>prefix, createIdentifier(symbol.name)) : createPropertyAccess(<Expression>prefix, symbol.name);
1581+
return isTypeNode
1582+
? () => createQualifiedName(<EntityName>prefix(), createIdentifier(symbol.name))
1583+
: () => createPropertyAccess(<Expression>prefix(), symbol.name);
15821584
}
15831585
}
15841586

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// ==ORIGINAL==
2+
namespace X {
3+
export const j = 10;
4+
export const y = j * j;
5+
}
6+
// ==SCOPE::Extract to constant in enclosing scope==
7+
namespace X {
8+
export const j = 10;
9+
const newLocal = j * j;
10+
11+
export const y = /*RENAME*/newLocal;
12+
}
13+
// ==SCOPE::Extract to constant in global scope==
14+
const newLocal = X.j * X.j;
15+
16+
namespace X {
17+
export const j = 10;
18+
export const y = /*RENAME*/newLocal;
19+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// ==ORIGINAL==
2+
namespace X {
3+
export const j = 10;
4+
export const y = j * j;
5+
}
6+
// ==SCOPE::Extract to function in namespace 'X'==
7+
namespace X {
8+
export const j = 10;
9+
export const y = /*RENAME*/newFunction();
10+
11+
function newFunction() {
12+
return j * j;
13+
}
14+
}
15+
// ==SCOPE::Extract to function in global scope==
16+
namespace X {
17+
export const j = 10;
18+
export const y = /*RENAME*/newFunction();
19+
}
20+
function newFunction() {
21+
return X.j * X.j;
22+
}

0 commit comments

Comments
 (0)