Skip to content

Commit

Permalink
feat: add factory property to traversal control
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed May 8, 2022
1 parent 1a8de49 commit d4c5a35
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 100 deletions.
2 changes: 2 additions & 0 deletions deno/ts_morph.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4555,6 +4555,8 @@ export interface ForEachDescendantTraversalControl {
}

export interface TransformTraversalControl {
/** Factory to create nodes with. */
factory: ts.NodeFactory;
/**
* The node currently being transformed.
* @remarks Use the result of `.visitChildren()` instead before transforming if visiting the children.
Expand Down
1 change: 1 addition & 0 deletions deno/ts_morph.js
Original file line number Diff line number Diff line change
Expand Up @@ -3698,6 +3698,7 @@ class Node {
}
function innerVisit(node, context) {
const traversal = {
factory: context.factory,
visitChildren() {
node = ts.visitEachChild(node, child => innerVisit(child, context), context);
return node;
Expand Down
2 changes: 2 additions & 0 deletions packages/ts-morph/lib/ts-morph.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4555,6 +4555,8 @@ export interface ForEachDescendantTraversalControl {
}

export interface TransformTraversalControl {
/** Factory to create nodes with. */
factory: ts.NodeFactory;
/**
* The node currently being transformed.
* @remarks Use the result of `.visitChildren()` instead before transforming if visiting the children.
Expand Down
201 changes: 101 additions & 100 deletions packages/ts-morph/src/compiler/ast/common/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ export class Node<NodeType extends ts.Node = ts.Node> {
() => `Expected the node to be of kind ${getSyntaxKindName(kind)}, but it was ${getSyntaxKindName(this.getKind())}.`,
);
}

/**
* Returns if the node is the specified kind.
*
Expand Down Expand Up @@ -1472,121 +1472,122 @@ export class Node<NodeType extends ts.Node = ts.Node> {
* ```
*/
transform(visitNode: (traversal: TransformTraversalControl) => ts.Node): Node {
const compilerFactory = this._context.compilerFactory;
const printer = ts.createPrinter({
newLine: this._context.manipulationSettings.getNewLineKind(),
removeComments: false,
const compilerFactory = this._context.compilerFactory;
const printer = ts.createPrinter({
newLine: this._context.manipulationSettings.getNewLineKind(),
removeComments: false,
});
interface Transformation { start: number; end: number; compilerNode: ts.Node; }
const transformations: Transformation[] = [];
const compilerSourceFile = this._sourceFile.compilerNode;
const compilerNode = this.compilerNode;
const transformerFactory: ts.TransformerFactory<ts.Node> = context => {
return rootNode => innerVisit(rootNode, context);
};

if (this.getKind() === ts.SyntaxKind.SourceFile) {
ts.transform(compilerNode, [transformerFactory], this._context.compilerOptions.get());

replaceSourceFileTextStraight({
sourceFile: this._sourceFile,
newText: getTransformedText([0, this.getEnd()]),
});
interface Transformation { start: number; end: number; compilerNode: ts.Node; }
const transformations: Transformation[] = [];
const compilerSourceFile = this._sourceFile.compilerNode;
const compilerNode = this.compilerNode;
const transformerFactory: ts.TransformerFactory<ts.Node> = context => {
return rootNode => innerVisit(rootNode, context);
};

if (this.getKind() === ts.SyntaxKind.SourceFile) {
ts.transform(compilerNode, [transformerFactory], this._context.compilerOptions.get());

replaceSourceFileTextStraight({
sourceFile: this._sourceFile,
newText: getTransformedText([0, this.getEnd()]),
});

return this;
} else {
const parent = this.getParentSyntaxList() || this.getParentOrThrow();
const childIndex = this.getChildIndex();
const start = this.getStart(true);
const end = this.getEnd();

ts.transform(compilerNode, [transformerFactory], this._context.compilerOptions.get());

insertIntoParentTextRange({
parent,
insertPos: start,
newText: getTransformedText([start, end]),
replacing: {
textLength: end - start,
},
});

return parent.getChildren()[childIndex];
}

function innerVisit(node: ts.Node, context: ts.TransformationContext) {
const traversal: TransformTraversalControl = {
visitChildren() {
node = ts.visitEachChild(node, child => innerVisit(child, context), context);
return node;
},
currentNode: node,
};
const resultNode = visitNode(traversal);
handleTransformation(node, resultNode);
return resultNode;
}

function handleTransformation(oldNode: ts.Node, newNode: ts.Node) {
if (oldNode === newNode)
return;
return this;
} else {
const parent = this.getParentSyntaxList() || this.getParentOrThrow();
const childIndex = this.getChildIndex();
const start = this.getStart(true);
const end = this.getEnd();

const start = oldNode.getStart(compilerSourceFile, true);
const end = oldNode.end;
let lastTransformation: Transformation | undefined;
ts.transform(compilerNode, [transformerFactory], this._context.compilerOptions.get());

// remove any prior transformations nested within this transformation
while ((lastTransformation = transformations[transformations.length - 1]) && lastTransformation.start > start)
transformations.pop();
insertIntoParentTextRange({
parent,
insertPos: start,
newText: getTransformedText([start, end]),
replacing: {
textLength: end - start,
},
});

const wrappedNode = compilerFactory.getExistingNodeFromCompilerNode(oldNode);
transformations.push({
start,
end,
compilerNode: newNode,
});
return parent.getChildren()[childIndex];
}

function innerVisit(node: ts.Node, context: ts.TransformationContext) {
const traversal: TransformTraversalControl = {
factory: context.factory,
visitChildren() {
node = ts.visitEachChild(node, child => innerVisit(child, context), context);
return node;
},
currentNode: node,
};
const resultNode = visitNode(traversal);
handleTransformation(node, resultNode);
return resultNode;
}

function handleTransformation(oldNode: ts.Node, newNode: ts.Node) {
if (oldNode === newNode)
return;

const start = oldNode.getStart(compilerSourceFile, true);
const end = oldNode.end;
let lastTransformation: Transformation | undefined;

// remove any prior transformations nested within this transformation
while ((lastTransformation = transformations[transformations.length - 1]) && lastTransformation.start > start)
transformations.pop();

const wrappedNode = compilerFactory.getExistingNodeFromCompilerNode(oldNode);
transformations.push({
start,
end,
compilerNode: newNode,
});

// It's very difficult and expensive to tell about changes that could have happened to the descendants
// via updating properties. For this reason, descendant nodes will always be forgotten.
if (wrappedNode != null) {
if (oldNode.kind !== newNode.kind)
wrappedNode.forget();
else
wrappedNode.forgetDescendants();
}
// It's very difficult and expensive to tell about changes that could have happened to the descendants
// via updating properties. For this reason, descendant nodes will always be forgotten.
if (wrappedNode != null) {
if (oldNode.kind !== newNode.kind)
wrappedNode.forget();
else
wrappedNode.forgetDescendants();
}
}

function getTransformedText(replaceRange: [number, number]) {
const fileText = compilerSourceFile.getFullText();
let finalText = "";
let lastPos = replaceRange[0];

for (const transform of transformations) {
finalText += fileText.substring(lastPos, transform.start);
finalText += printer.printNode(ts.EmitHint.Unspecified, transform.compilerNode, compilerSourceFile);
lastPos = transform.end;
}
function getTransformedText(replaceRange: [number, number]) {
const fileText = compilerSourceFile.getFullText();
let finalText = "";
let lastPos = replaceRange[0];

finalText += fileText.substring(lastPos, replaceRange[1]);
return finalText;
for (const transform of transformations) {
finalText += fileText.substring(lastPos, transform.start);
finalText += printer.printNode(ts.EmitHint.Unspecified, transform.compilerNode, compilerSourceFile);
lastPos = transform.end;
}

finalText += fileText.substring(lastPos, replaceRange[1]);
return finalText;
}
}

/**
* Gets the leading comment ranges of the current node.
*/
getLeadingCommentRanges(): CommentRange[] {
return this._leadingCommentRanges || (this._leadingCommentRanges = this._getCommentsAtPos(this.getFullStart(), (text: string, pos: number) => {
const comments = ts.getLeadingCommentRanges(text, pos) || [];
// if this is a comment, then only include leading comment ranges before this one
if (this.getKind() === SyntaxKind.SingleLineCommentTrivia || this.getKind() === SyntaxKind.MultiLineCommentTrivia) {
const thisPos = this.getPos();
return comments.filter(r => r.pos < thisPos);
}
else {
return comments;
}
}));
return this._leadingCommentRanges || (this._leadingCommentRanges = this._getCommentsAtPos(this.getFullStart(), (text: string, pos: number) => {
const comments = ts.getLeadingCommentRanges(text, pos) || [];
// if this is a comment, then only include leading comment ranges before this one
if (this.getKind() === SyntaxKind.SingleLineCommentTrivia || this.getKind() === SyntaxKind.MultiLineCommentTrivia) {
const thisPos = this.getPos();
return comments.filter(r => r.pos < thisPos);
}
else {
return comments;
}
}));
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/ts-morph/src/compiler/ast/common/TraversalControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface ForEachDescendantTraversalControl {
}

export interface TransformTraversalControl {
/** Factory to create nodes with. */
factory: ts.NodeFactory;
/**
* The node currently being transformed.
* @remarks Use the result of `.visitChildren()` instead before transforming if visiting the children.
Expand Down

0 comments on commit d4c5a35

Please sign in to comment.