Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic algorithm to create a shallow, memberwise clone of a node. #5726

Merged
merged 3 commits into from
Nov 20, 2015
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -860,4 +860,4 @@ namespace ts {
}
return copiedList;
}
}
}
6 changes: 2 additions & 4 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
[ModuleKind.CommonJS]() {},
};


return doEmit;

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

// Clone the type name and parent it to a location outside of the current declaration.
const typeName = cloneEntityName(node.typeName);
typeName.parent = location;

const typeName = cloneEntityName(node.typeName, location);
const result = resolver.getTypeReferenceSerializationKind(typeName);
switch (result) {
case TypeReferenceSerializationKind.Unknown:
Expand Down
72 changes: 59 additions & 13 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1580,20 +1580,66 @@ namespace ts {
return isFunctionLike(n) || n.kind === SyntaxKind.ModuleDeclaration || n.kind === SyntaxKind.SourceFile;
}

export function cloneEntityName(node: EntityName): EntityName {
if (node.kind === SyntaxKind.Identifier) {
const clone = <Identifier>createSynthesizedNode(SyntaxKind.Identifier);
clone.text = (<Identifier>node).text;
return clone;
/**
* Creates a shallow, memberwise clone of a node. The "pos", "end", "flags", and "parent" properties
* are excluded by default, and can be provided via the "location", "flags", and "parent" parameters.
* @param node The node to clone.
* @param location An optional TextRange to use to supply the new position.
* @param flags The NodeFlags to use for the cloned node.
* @param parent The parent for the new node.
*/
export function cloneNode<T extends Node>(node: T, location?: TextRange, flags?: NodeFlags, parent?: Node): T {
// We don't use "clone" from core.ts here, as we need to preserve the prototype chain of
// the original node. We also need to exclude specific properties and only include own-
// properties (to skip members already defined on the shared prototype).
const clone = location !== undefined
? <T>createNode(node.kind, location.pos, location.end)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kind is copied again in the for loop, right? I just want to make sure I'm reading the code correctly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I originally wrote the cloneNode function before @vladima modified the Node constructor, when kind was set on the prototype. I can probably drop isExcludedPropertyForClone in favor of clone.hasOwnProperty(key).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will copy over flags and parent now, won't it? Which is bad unless they are provided by the caller.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, flags and parent are set in the Node constructor. See line 808 of core.ts:

    function Node(kind: SyntaxKind, pos: number, end: number) {
        this.kind = kind;
        this.pos = pos;
        this.end = end;
        this.flags = NodeFlags.None;
        this.parent = undefined;
    }

And line 191 of services.ts:

        constructor(kind: SyntaxKind, pos: number, end: number) {
            this.kind = kind;
            this.pos = pos;
            this.end = end;
            this.flags = NodeFlags.None;
            this.parent = undefined;
        }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All right, sounds good.

: <T>createSynthesizedNode(node.kind);

for (const key in node) {
if (isExcludedPropertyForClone(key) || !node.hasOwnProperty(key)) {
continue;
}

(<any>clone)[key] = (<any>node)[key];
}
else {
const clone = <QualifiedName>createSynthesizedNode(SyntaxKind.QualifiedName);
clone.left = cloneEntityName((<QualifiedName>node).left);
clone.left.parent = clone;
clone.right = <Identifier>cloneEntityName((<QualifiedName>node).right);
clone.right.parent = clone;
return clone;

if (flags !== undefined) {
clone.flags = flags;
}

if (parent !== undefined) {
clone.parent = parent;
}

return clone;
}

function isExcludedPropertyForClone(property: string) {
return property === "pos"
|| property === "end"
|| property === "flags"
|| property === "parent";
}

/**
* Creates a deep clone of an EntityName, with new parent pointers.
* @param node The EntityName to clone.
* @param parent The parent for the cloned node.
*/
export function cloneEntityName(node: EntityName, parent?: Node): EntityName {
const clone = cloneNode(node, node, node.flags, parent);
if (isQualifiedName(clone)) {
const { left, right } = clone;
clone.left = cloneEntityName(left, clone);
clone.right = cloneNode(right, right, right.flags, parent);
}

return clone;
}

export function isQualifiedName(node: Node): node is QualifiedName {
return node.kind === SyntaxKind.QualifiedName;
}

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