Skip to content

Commit bada009

Browse files
authored
Merge pull request #18979 from amcasey/DeepClone
Introduce getSynthesizedDeepClone
2 parents 6cf41ae + eb4f067 commit bada009

File tree

3 files changed

+47
-13
lines changed

3 files changed

+47
-13
lines changed

src/compiler/factory.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,15 @@ namespace ts {
4747
* Creates a shallow, memberwise clone of a node with no source map location.
4848
*/
4949
/* @internal */
50-
export function getSynthesizedClone<T extends Node>(node: T | undefined): T {
50+
export function getSynthesizedClone<T extends Node>(node: T | undefined): T | undefined {
5151
// We don't use "clone" from core.ts here, as we need to preserve the prototype chain of
5252
// the original node. We also need to exclude specific properties and only include own-
5353
// properties (to skip members already defined on the shared prototype).
54+
55+
if (node === undefined) {
56+
return undefined;
57+
}
58+
5459
const clone = <T>createSynthesizedNode(node.kind);
5560
clone.flags |= node.flags;
5661
setOriginalNode(clone, node);

src/services/refactors/extractSymbol.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,7 @@ namespace ts.refactor.extractSymbol {
10881088
}
10891089
}
10901090

1091-
function transformFunctionBody(body: Node, writes: ReadonlyArray<UsageEntry>, substitutions: ReadonlyMap<() => Node>, hasReturn: boolean): { body: Block, returnValueProperty: string } {
1091+
function transformFunctionBody(body: Node, writes: ReadonlyArray<UsageEntry>, substitutions: ReadonlyMap<Node>, hasReturn: boolean): { body: Block, returnValueProperty: string } {
10921092
if (isBlock(body) && !writes && substitutions.size === 0) {
10931093
// already block, no writes to propagate back, no substitutions - can use node as is
10941094
return { body: createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined };
@@ -1136,21 +1136,21 @@ namespace ts.refactor.extractSymbol {
11361136
const oldIgnoreReturns = ignoreReturns;
11371137
ignoreReturns = ignoreReturns || isFunctionLikeDeclaration(node) || isClassLike(node);
11381138
const substitution = substitutions.get(getNodeId(node).toString());
1139-
const result = substitution ? substitution() : visitEachChild(node, visitor, nullTransformationContext);
1139+
const result = substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext);
11401140
ignoreReturns = oldIgnoreReturns;
11411141
return result;
11421142
}
11431143
}
11441144
}
11451145

1146-
function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyMap<() => Node>): Expression {
1146+
function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyMap<Node>): Expression {
11471147
return substitutions.size
11481148
? visitor(initializer) as Expression
11491149
: initializer;
11501150

11511151
function visitor(node: Node): VisitResult<Node> {
11521152
const substitution = substitutions.get(getNodeId(node).toString());
1153-
return substitution ? substitution() : visitEachChild(node, visitor, nullTransformationContext);
1153+
return substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext);
11541154
}
11551155
}
11561156

@@ -1279,7 +1279,7 @@ namespace ts.refactor.extractSymbol {
12791279
interface ScopeUsages {
12801280
readonly usages: Map<UsageEntry>;
12811281
readonly typeParameterUsages: Map<TypeParameter>; // Key is type ID
1282-
readonly substitutions: Map<() => Node>;
1282+
readonly substitutions: Map<Node>;
12831283
}
12841284

12851285
interface ReadsAndWrites {
@@ -1298,7 +1298,7 @@ namespace ts.refactor.extractSymbol {
12981298

12991299
const allTypeParameterUsages = createMap<TypeParameter>(); // Key is type ID
13001300
const usagesPerScope: ScopeUsages[] = [];
1301-
const substitutionsPerScope: Map<() => Node>[] = [];
1301+
const substitutionsPerScope: Map<Node>[] = [];
13021302
const functionErrorsPerScope: Diagnostic[][] = [];
13031303
const constantErrorsPerScope: Diagnostic[][] = [];
13041304
const visibleDeclarationsInExtractedRange: Symbol[] = [];
@@ -1322,8 +1322,8 @@ namespace ts.refactor.extractSymbol {
13221322

13231323
// initialize results
13241324
for (const scope of scopes) {
1325-
usagesPerScope.push({ usages: createMap<UsageEntry>(), typeParameterUsages: createMap<TypeParameter>(), substitutions: createMap<() => Expression>() });
1326-
substitutionsPerScope.push(createMap<() => Expression>());
1325+
usagesPerScope.push({ usages: createMap<UsageEntry>(), typeParameterUsages: createMap<TypeParameter>(), substitutions: createMap<Expression>() });
1326+
substitutionsPerScope.push(createMap<Expression>());
13271327

13281328
functionErrorsPerScope.push(
13291329
isFunctionLikeDeclaration(scope) && scope.kind !== SyntaxKind.FunctionDeclaration
@@ -1622,20 +1622,20 @@ namespace ts.refactor.extractSymbol {
16221622
}
16231623
}
16241624

1625-
function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol, scopeDecl: Node, isTypeNode: boolean): () => (PropertyAccessExpression | EntityName) {
1625+
function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol, scopeDecl: Node, isTypeNode: boolean): PropertyAccessExpression | EntityName {
16261626
if (!symbol) {
16271627
return undefined;
16281628
}
16291629
if (symbol.getDeclarations().some(d => d.parent === scopeDecl)) {
1630-
return () => createIdentifier(symbol.name);
1630+
return createIdentifier(symbol.name);
16311631
}
16321632
const prefix = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.parent, scopeDecl, isTypeNode);
16331633
if (prefix === undefined) {
16341634
return undefined;
16351635
}
16361636
return isTypeNode
1637-
? () => createQualifiedName(<EntityName>prefix(), createIdentifier(symbol.name))
1638-
: () => createPropertyAccess(<Expression>prefix(), symbol.name);
1637+
? createQualifiedName(<EntityName>prefix, createIdentifier(symbol.name))
1638+
: createPropertyAccess(<Expression>prefix, symbol.name);
16391639
}
16401640
}
16411641

src/services/utilities.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,4 +1334,33 @@ namespace ts {
13341334
}
13351335
return position;
13361336
}
1337+
1338+
/**
1339+
* Creates a deep, memberwise clone of a node with no source map location.
1340+
*
1341+
* WARNING: This is an expensive operation and is only intended to be used in refactorings
1342+
* and code fixes (because those are triggered by explicit user actions).
1343+
*/
1344+
export function getSynthesizedDeepClone<T extends Node>(node: T | undefined): T | undefined {
1345+
if (node === undefined) {
1346+
return undefined;
1347+
}
1348+
1349+
const visited = visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext);
1350+
if (visited === node) {
1351+
// This only happens for leaf nodes - internal nodes always see their children change.
1352+
const clone = getSynthesizedClone(node);
1353+
clone.pos = node.pos;
1354+
clone.end = node.end;
1355+
return clone;
1356+
}
1357+
1358+
// PERF: As an optimization, rather than calling getSynthesizedClone, we'll update
1359+
// the new node created by visitEachChild with the extra changes getSynthesizedClone
1360+
// would have made.
1361+
1362+
visited.parent = undefined;
1363+
1364+
return visited;
1365+
}
13371366
}

0 commit comments

Comments
 (0)