Skip to content

Commit 98772e1

Browse files
committed
deep clone enum initializer when reused for quickinfo expansion
1 parent 9e2feca commit 98772e1

5 files changed

Lines changed: 128 additions & 204 deletions

File tree

src/compiler/checker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ import {
374374
getStrictOptionValue,
375375
getSuperContainer,
376376
getSymbolNameForPrivateIdentifier,
377+
getSynthesizedDeepClone,
377378
getTextOfIdentifierOrLiteral,
378379
getTextOfJSDocComment,
379380
getTextOfJsxAttributeName,
@@ -9895,7 +9896,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
98959896
let initializer: Expression | undefined;
98969897
let initializerLength: number;
98979898
if (isExpanding(context) && memberDecl && memberDecl.initializer) {
9898-
initializer = memberDecl.initializer;
9899+
initializer = getSynthesizedDeepClone(memberDecl.initializer);
98999900
initializerLength = memberDecl.initializer.end - memberDecl.initializer.pos;
99009901
}
99019902
else {

src/compiler/utilities.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
__String,
33
AccessExpression,
44
AccessorDeclaration,
5+
addEmitFlags,
56
addRange,
67
affectsDeclarationPathOptionDeclarations,
78
affectsEmitOptionDeclarations,
@@ -511,6 +512,8 @@ import {
511512
ScriptTarget,
512513
semanticDiagnosticsOptionDeclarations,
513514
SetAccessorDeclaration,
515+
setOriginalNode,
516+
setTextRange,
514517
ShorthandPropertyAssignment,
515518
shouldAllowImportingTsExtension,
516519
Signature,
@@ -592,6 +595,7 @@ import {
592595
VariableDeclarationList,
593596
VariableLikeDeclaration,
594597
VariableStatement,
598+
visitEachChild,
595599
WhileStatement,
596600
WithStatement,
597601
WrappedExpression,
@@ -12208,3 +12212,121 @@ export function getOptionsSyntaxByValue(optionsObject: ObjectLiteralExpression |
1220812212
export function forEachOptionsSyntaxByName<T>(optionsObject: ObjectLiteralExpression | undefined, name: string, callback: (prop: PropertyAssignment) => T | undefined): T | undefined {
1220912213
return forEachPropertyAssignment(optionsObject, name, callback);
1221012214
}
12215+
12216+
/**
12217+
* Creates a deep, memberwise clone of a node with no source map location.
12218+
*
12219+
* WARNING: This is an expensive operation and is only intended to be used in refactorings
12220+
* and code fixes (because those are triggered by explicit user actions).
12221+
*
12222+
* @internal
12223+
*/
12224+
// Moved here to compiler utilities for usage in node builder for quickinfo.
12225+
export function getSynthesizedDeepClone<T extends Node | undefined>(node: T, includeTrivia = true): T {
12226+
const clone = node && getSynthesizedDeepCloneWorker(node);
12227+
if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone);
12228+
return setParentRecursive(clone, /*incremental*/ false);
12229+
}
12230+
12231+
/** @internal */
12232+
export function getSynthesizedDeepCloneWithReplacements<T extends Node>(
12233+
node: T,
12234+
includeTrivia: boolean,
12235+
replaceNode: (node: Node) => Node | undefined,
12236+
): T {
12237+
let clone = replaceNode(node);
12238+
if (clone) {
12239+
setOriginalNode(clone, node);
12240+
}
12241+
else {
12242+
clone = getSynthesizedDeepCloneWorker(node as NonNullable<T>, replaceNode);
12243+
}
12244+
12245+
if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone);
12246+
return clone as T;
12247+
}
12248+
12249+
function getSynthesizedDeepCloneWorker<T extends Node>(node: T, replaceNode?: (node: Node) => Node | undefined): T {
12250+
const nodeClone: <T extends Node>(n: T) => T = replaceNode
12251+
? n => getSynthesizedDeepCloneWithReplacements(n, /*includeTrivia*/ true, replaceNode)
12252+
: getSynthesizedDeepClone;
12253+
const nodesClone: <T extends Node>(ns: NodeArray<T> | undefined) => NodeArray<T> | undefined = replaceNode
12254+
? ns => ns && getSynthesizedDeepClonesWithReplacements(ns, /*includeTrivia*/ true, replaceNode)
12255+
: ns => ns && getSynthesizedDeepClones(ns);
12256+
const visited = visitEachChild(node, nodeClone, /*context*/ undefined, nodesClone, nodeClone);
12257+
12258+
if (visited === node) {
12259+
// This only happens for leaf nodes - internal nodes always see their children change.
12260+
const clone = isStringLiteral(node) ? setOriginalNode(factory.createStringLiteralFromNode(node), node) as Node as T :
12261+
isNumericLiteral(node) ? setOriginalNode(factory.createNumericLiteral(node.text, node.numericLiteralFlags), node) as Node as T :
12262+
factory.cloneNode(node);
12263+
return setTextRange(clone, node);
12264+
}
12265+
12266+
// PERF: As an optimization, rather than calling factory.cloneNode, we'll update
12267+
// the new node created by visitEachChild with the extra changes factory.cloneNode
12268+
// would have made.
12269+
(visited as Mutable<T>).parent = undefined!;
12270+
return visited;
12271+
}
12272+
12273+
/** @internal */
12274+
export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T>, includeTrivia?: boolean): NodeArray<T>;
12275+
/** @internal */
12276+
export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T> | undefined, includeTrivia?: boolean): NodeArray<T> | undefined;
12277+
/** @internal */
12278+
export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T> | undefined, includeTrivia = true): NodeArray<T> | undefined {
12279+
if (nodes) {
12280+
const cloned = factory.createNodeArray(nodes.map(n => getSynthesizedDeepClone(n, includeTrivia)), nodes.hasTrailingComma);
12281+
setTextRange(cloned, nodes);
12282+
return cloned;
12283+
}
12284+
return nodes;
12285+
}
12286+
12287+
/** @internal */
12288+
export function getSynthesizedDeepClonesWithReplacements<T extends Node>(
12289+
nodes: NodeArray<T>,
12290+
includeTrivia: boolean,
12291+
replaceNode: (node: Node) => Node | undefined,
12292+
): NodeArray<T> {
12293+
return factory.createNodeArray(nodes.map(n => getSynthesizedDeepCloneWithReplacements(n, includeTrivia, replaceNode)), nodes.hasTrailingComma);
12294+
}
12295+
12296+
/**
12297+
* Sets EmitFlags to suppress leading and trailing trivia on the node.
12298+
*
12299+
* @internal
12300+
*/
12301+
export function suppressLeadingAndTrailingTrivia(node: Node): void {
12302+
suppressLeadingTrivia(node);
12303+
suppressTrailingTrivia(node);
12304+
}
12305+
12306+
/**
12307+
* Sets EmitFlags to suppress leading trivia on the node.
12308+
*
12309+
* @internal
12310+
*/
12311+
export function suppressLeadingTrivia(node: Node): void {
12312+
addEmitFlagsRecursively(node, EmitFlags.NoLeadingComments, getFirstChild);
12313+
}
12314+
12315+
/**
12316+
* Sets EmitFlags to suppress trailing trivia on the node.
12317+
*
12318+
* @internal @knipignore
12319+
*/
12320+
export function suppressTrailingTrivia(node: Node): void {
12321+
addEmitFlagsRecursively(node, EmitFlags.NoTrailingComments, getLastChild);
12322+
}
12323+
12324+
function addEmitFlagsRecursively(node: Node, flag: EmitFlags, getChild: (n: Node) => Node | undefined) {
12325+
addEmitFlags(node, flag);
12326+
const child = getChild(node);
12327+
if (child) addEmitFlagsRecursively(child, flag, getChild);
12328+
}
12329+
12330+
function getFirstChild(node: Node): Node | undefined {
12331+
return forEachChild(node, child => child);
12332+
}

src/services/symbolDisplay.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -887,9 +887,10 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
887887
typeWriterOut,
888888
);
889889
const printer = getPrinter();
890+
const sourceFile = symbol.valueDeclaration && getSourceFileOfNode(symbol.valueDeclaration);
890891
nodes.forEach((node, i) => {
891892
if (i > 0) writer.writeLine();
892-
printer.writeNode(EmitHint.Unspecified, node, /*sourceFile*/ undefined, writer);
893+
printer.writeNode(EmitHint.Unspecified, node, sourceFile, writer);
893894
});
894895
});
895896
addRange(displayParts, expandedDisplayParts);

src/services/utilities.ts

Lines changed: 0 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
__String,
3-
addEmitFlags,
43
addSyntheticLeadingComment,
54
addSyntheticTrailingComment,
65
AnyImportOrRequireStatement,
@@ -51,7 +50,6 @@ import {
5150
DocumentSpan,
5251
DoStatement,
5352
ElementAccessExpression,
54-
EmitFlags,
5553
emitModuleKindIsNonNodeESM,
5654
emptyArray,
5755
EndOfFileToken,
@@ -101,7 +99,6 @@ import {
10199
getImpliedNodeFormatForFileWorker,
102100
getIndentString,
103101
getJSDocEnumTag,
104-
getLastChild,
105102
getLineAndCharacterOfPosition,
106103
getLineStarts,
107104
getLocaleSpecificMessage,
@@ -223,7 +220,6 @@ import {
223220
isNamespaceExport,
224221
isNamespaceImport,
225222
isNewExpression,
226-
isNumericLiteral,
227223
isObjectBindingPattern,
228224
isObjectLiteralExpression,
229225
isOptionalChain,
@@ -292,7 +288,6 @@ import {
292288
ModuleResolutionKind,
293289
ModuleSpecifierResolutionHost,
294290
moduleSpecifiers,
295-
Mutable,
296291
NewExpression,
297292
NewLineKind,
298293
Node,
@@ -332,9 +327,6 @@ import {
332327
ScriptTarget,
333328
SemicolonPreference,
334329
setConfigFileInOptions,
335-
setOriginalNode,
336-
setParentRecursive,
337-
setTextRange,
338330
Signature,
339331
SignatureDeclaration,
340332
singleOrUndefined,
@@ -387,7 +379,6 @@ import {
387379
unescapeLeadingUnderscores,
388380
UserPreferences,
389381
VariableDeclaration,
390-
visitEachChild,
391382
VoidExpression,
392383
walkUpParenthesizedExpressions,
393384
WriterContextOut,
@@ -3128,113 +3119,6 @@ export function getPrecedingNonSpaceCharacterPosition(text: string, position: nu
31283119
return position + 1;
31293120
}
31303121

3131-
/**
3132-
* Creates a deep, memberwise clone of a node with no source map location.
3133-
*
3134-
* WARNING: This is an expensive operation and is only intended to be used in refactorings
3135-
* and code fixes (because those are triggered by explicit user actions).
3136-
*
3137-
* @internal
3138-
*/
3139-
export function getSynthesizedDeepClone<T extends Node | undefined>(node: T, includeTrivia = true): T {
3140-
const clone = node && getSynthesizedDeepCloneWorker(node);
3141-
if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone);
3142-
return setParentRecursive(clone, /*incremental*/ false);
3143-
}
3144-
3145-
/** @internal */
3146-
export function getSynthesizedDeepCloneWithReplacements<T extends Node>(
3147-
node: T,
3148-
includeTrivia: boolean,
3149-
replaceNode: (node: Node) => Node | undefined,
3150-
): T {
3151-
let clone = replaceNode(node);
3152-
if (clone) {
3153-
setOriginalNode(clone, node);
3154-
}
3155-
else {
3156-
clone = getSynthesizedDeepCloneWorker(node as NonNullable<T>, replaceNode);
3157-
}
3158-
3159-
if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone);
3160-
return clone as T;
3161-
}
3162-
3163-
function getSynthesizedDeepCloneWorker<T extends Node>(node: T, replaceNode?: (node: Node) => Node | undefined): T {
3164-
const nodeClone: <T extends Node>(n: T) => T = replaceNode
3165-
? n => getSynthesizedDeepCloneWithReplacements(n, /*includeTrivia*/ true, replaceNode)
3166-
: getSynthesizedDeepClone;
3167-
const nodesClone: <T extends Node>(ns: NodeArray<T> | undefined) => NodeArray<T> | undefined = replaceNode
3168-
? ns => ns && getSynthesizedDeepClonesWithReplacements(ns, /*includeTrivia*/ true, replaceNode)
3169-
: ns => ns && getSynthesizedDeepClones(ns);
3170-
const visited = visitEachChild(node, nodeClone, /*context*/ undefined, nodesClone, nodeClone);
3171-
3172-
if (visited === node) {
3173-
// This only happens for leaf nodes - internal nodes always see their children change.
3174-
const clone = isStringLiteral(node) ? setOriginalNode(factory.createStringLiteralFromNode(node), node) as Node as T :
3175-
isNumericLiteral(node) ? setOriginalNode(factory.createNumericLiteral(node.text, node.numericLiteralFlags), node) as Node as T :
3176-
factory.cloneNode(node);
3177-
return setTextRange(clone, node);
3178-
}
3179-
3180-
// PERF: As an optimization, rather than calling factory.cloneNode, we'll update
3181-
// the new node created by visitEachChild with the extra changes factory.cloneNode
3182-
// would have made.
3183-
(visited as Mutable<T>).parent = undefined!;
3184-
return visited;
3185-
}
3186-
3187-
/** @internal */
3188-
export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T>, includeTrivia?: boolean): NodeArray<T>;
3189-
/** @internal */
3190-
export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T> | undefined, includeTrivia?: boolean): NodeArray<T> | undefined;
3191-
/** @internal */
3192-
export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T> | undefined, includeTrivia = true): NodeArray<T> | undefined {
3193-
if (nodes) {
3194-
const cloned = factory.createNodeArray(nodes.map(n => getSynthesizedDeepClone(n, includeTrivia)), nodes.hasTrailingComma);
3195-
setTextRange(cloned, nodes);
3196-
return cloned;
3197-
}
3198-
return nodes;
3199-
}
3200-
3201-
/** @internal */
3202-
export function getSynthesizedDeepClonesWithReplacements<T extends Node>(
3203-
nodes: NodeArray<T>,
3204-
includeTrivia: boolean,
3205-
replaceNode: (node: Node) => Node | undefined,
3206-
): NodeArray<T> {
3207-
return factory.createNodeArray(nodes.map(n => getSynthesizedDeepCloneWithReplacements(n, includeTrivia, replaceNode)), nodes.hasTrailingComma);
3208-
}
3209-
3210-
/**
3211-
* Sets EmitFlags to suppress leading and trailing trivia on the node.
3212-
*
3213-
* @internal
3214-
*/
3215-
export function suppressLeadingAndTrailingTrivia(node: Node): void {
3216-
suppressLeadingTrivia(node);
3217-
suppressTrailingTrivia(node);
3218-
}
3219-
3220-
/**
3221-
* Sets EmitFlags to suppress leading trivia on the node.
3222-
*
3223-
* @internal
3224-
*/
3225-
export function suppressLeadingTrivia(node: Node): void {
3226-
addEmitFlagsRecursively(node, EmitFlags.NoLeadingComments, getFirstChild);
3227-
}
3228-
3229-
/**
3230-
* Sets EmitFlags to suppress trailing trivia on the node.
3231-
*
3232-
* @internal @knipignore
3233-
*/
3234-
export function suppressTrailingTrivia(node: Node): void {
3235-
addEmitFlagsRecursively(node, EmitFlags.NoTrailingComments, getLastChild);
3236-
}
3237-
32383122
/** @internal */
32393123
export function copyComments(sourceNode: Node, targetNode: Node): void {
32403124
const sourceFile = sourceNode.getSourceFile();
@@ -3257,16 +3141,6 @@ function hasLeadingLineBreak(node: Node, text: string) {
32573141
return false;
32583142
}
32593143

3260-
function addEmitFlagsRecursively(node: Node, flag: EmitFlags, getChild: (n: Node) => Node | undefined) {
3261-
addEmitFlags(node, flag);
3262-
const child = getChild(node);
3263-
if (child) addEmitFlagsRecursively(child, flag, getChild);
3264-
}
3265-
3266-
function getFirstChild(node: Node): Node | undefined {
3267-
return node.forEachChild(child => child);
3268-
}
3269-
32703144
/** @internal */
32713145
export function getUniqueName(baseName: string, sourceFile: SourceFile): string {
32723146
let nameText = baseName;

0 commit comments

Comments
 (0)