Skip to content

Commit 68c292c

Browse files
committed
Adds a generic algorithm to create a shallow, memberwise clone of a node.
1 parent 883b8d9 commit 68c292c

3 files changed

Lines changed: 62 additions & 18 deletions

File tree

src/compiler/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -860,4 +860,4 @@ namespace ts {
860860
}
861861
return copiedList;
862862
}
863-
}
863+
}

src/compiler/emitter.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
549549
[ModuleKind.CommonJS]() {},
550550
};
551551

552-
552+
553553
return doEmit;
554554

555555
function doEmit(jsFilePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) {
@@ -5991,9 +5991,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
59915991
}
59925992

59935993
// Clone the type name and parent it to a location outside of the current declaration.
5994-
const typeName = cloneEntityName(node.typeName);
5995-
typeName.parent = location;
5996-
5994+
const typeName = cloneEntityName(node.typeName, location);
59975995
const result = resolver.getTypeReferenceSerializationKind(typeName);
59985996
switch (result) {
59995997
case TypeReferenceSerializationKind.Unknown:

src/compiler/utilities.ts

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,20 +1580,66 @@ namespace ts {
15801580
return isFunctionLike(n) || n.kind === SyntaxKind.ModuleDeclaration || n.kind === SyntaxKind.SourceFile;
15811581
}
15821582

1583-
export function cloneEntityName(node: EntityName): EntityName {
1584-
if (node.kind === SyntaxKind.Identifier) {
1585-
const clone = <Identifier>createSynthesizedNode(SyntaxKind.Identifier);
1586-
clone.text = (<Identifier>node).text;
1587-
return clone;
1583+
/**
1584+
* Creates a shallow, memberwise clone of a node. The "pos", "end", "flags", and "parent" properties
1585+
* are excluded by default, and can be provided via the "location", "flags", and "parent" parameters.
1586+
* @param node The node to clone.
1587+
* @param location An optional TextRange to use to supply the new position.
1588+
* @param flags The NodeFlags to use for the cloned node.
1589+
* @param parent The parent for the new node.
1590+
*/
1591+
export function cloneNode<T extends Node>(node: T, location?: TextRange, flags?: NodeFlags, parent?: Node): T {
1592+
// We don't use "clone" from core.ts here, as we need to preserve the prototype chain of
1593+
// the original node. We also need to exclude specific properties and only include own-
1594+
// properties (to skip members already defined on the shared prototype).
1595+
const clone = location !== undefined
1596+
? <T>createNode(node.kind, location.pos, location.end)
1597+
: <T>createSynthesizedNode(node.kind);
1598+
1599+
for (const key in node) {
1600+
if (isExcludedPropertyForClone(key) || !node.hasOwnProperty(key)) {
1601+
continue;
1602+
}
1603+
1604+
(<any>clone)[key] = (<any>node)[key];
15881605
}
1589-
else {
1590-
const clone = <QualifiedName>createSynthesizedNode(SyntaxKind.QualifiedName);
1591-
clone.left = cloneEntityName((<QualifiedName>node).left);
1592-
clone.left.parent = clone;
1593-
clone.right = <Identifier>cloneEntityName((<QualifiedName>node).right);
1594-
clone.right.parent = clone;
1595-
return clone;
1606+
1607+
if (flags !== undefined) {
1608+
clone.flags = flags;
15961609
}
1610+
1611+
if (parent !== undefined) {
1612+
clone.parent = parent;
1613+
}
1614+
1615+
return clone;
1616+
}
1617+
1618+
function isExcludedPropertyForClone(property: string) {
1619+
return property === "pos"
1620+
|| property === "end"
1621+
|| property === "flags"
1622+
|| property === "parent";
1623+
}
1624+
1625+
/**
1626+
* Creates a deep clone of an EntityName, with new parent pointers.
1627+
* @param node The EntityName to clone.
1628+
* @param parent The parent for the cloned node.
1629+
*/
1630+
export function cloneEntityName(node: EntityName, parent?: Node): EntityName {
1631+
const clone = cloneNode(node, node, node.flags, parent);
1632+
if (isQualifiedName(clone)) {
1633+
const { left, right } = clone;
1634+
clone.left = cloneEntityName(left, clone);
1635+
clone.right = cloneNode(right, right, right.flags, parent);
1636+
}
1637+
1638+
return clone;
1639+
}
1640+
1641+
export function isQualifiedName(node: Node): node is QualifiedName {
1642+
return node.kind === SyntaxKind.QualifiedName;
15971643
}
15981644

15991645
export function nodeIsSynthesized(node: Node): boolean {
@@ -1936,7 +1982,7 @@ namespace ts {
19361982
const bundledSources = filter(host.getSourceFiles(),
19371983
sourceFile => !isDeclarationFile(sourceFile) && // Not a declaration file
19381984
(!isExternalModule(sourceFile) || // non module file
1939-
(getEmitModuleKind(options) && isExternalModule(sourceFile)))); // module that can emit - note falsy value from getEmitModuleKind means the module kind that shouldn't be emitted
1985+
(getEmitModuleKind(options) && isExternalModule(sourceFile)))); // module that can emit - note falsy value from getEmitModuleKind means the module kind that shouldn't be emitted
19401986
if (bundledSources.length) {
19411987
const jsFilePath = options.outFile || options.out;
19421988
const emitFileNames: EmitFileNames = {

0 commit comments

Comments
 (0)