ts-simple-ast
Version:
TypeScript compiler wrapper for AST navigation and code generation.
683 lines (681 loc) • 23.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const errors = require("./../../errors");
const textSeek_1 = require("./../../manipulation/textSeek");
const utils_1 = require("./../../utils");
class Node {
/**
* Initializes a new instance.
* @internal
* @param global - Global container.
* @param node - Underlying node.
* @param sourceFile - Source file for the node.
*/
constructor(global, node, sourceFile) {
this.global = global;
this._compilerNode = node;
this.sourceFile = sourceFile;
}
/**
* Gets the underlying compiler node.
*/
get compilerNode() {
if (this._compilerNode == null)
throw new errors.InvalidOperationError("Attempted to get information from a node that was removed from the AST.");
return this._compilerNode;
}
/**
* Releases the node from the cache and ast.
* @internal
*/
dispose() {
for (const child of this.getChildren()) {
child.dispose();
}
this.global.compilerFactory.removeNodeFromCache(this);
this._compilerNode = undefined;
}
/**
* Sets the source file.
* @internal
* @param sourceFile - Source file to set.
*/
setSourceFile(sourceFile) {
this.sourceFile = sourceFile;
for (const child of this.getChildren())
child.setSourceFile(sourceFile);
}
/**
* Gets the syntax kind.
*/
getKind() {
return this.compilerNode.kind;
}
/**
* Gets the syntax kind name.
*/
getKindName() {
return ts.SyntaxKind[this.compilerNode.kind];
}
/**
* Gets the compiler symbol.
*/
getSymbol() {
const boundSymbol = this.compilerNode.symbol;
if (boundSymbol != null)
return this.global.compilerFactory.getSymbol(boundSymbol);
const typeChecker = this.global.typeChecker;
const typeCheckerSymbol = typeChecker.getSymbolAtLocation(this);
if (typeCheckerSymbol != null)
return typeCheckerSymbol;
const nameNode = this.compilerNode.name;
if (nameNode != null)
return this.global.compilerFactory.getNodeFromCompilerNode(nameNode, this.sourceFile).getSymbol();
return undefined;
}
/**
* If the node contains the provided range (inclusive).
* @param pos - Start position.
* @param end - End position.
*/
containsRange(pos, end) {
return this.getPos() <= pos && end <= this.getEnd();
}
/**
* Gets the first child by a condition or throws.
* @param condition - Condition.
*/
getFirstChildOrThrow(condition) {
return errors.throwIfNullOrUndefined(this.getFirstChild(condition), "Could not find a child that matched the specified condition.");
}
/**
* Gets the first child by a condition.
* @param condition - Condition.
*/
getFirstChild(condition) {
for (const child of this.getChildrenIterator()) {
if (condition == null || condition(child))
return child;
}
return undefined;
}
/**
* Gets the last child by a condition or throws.
* @param condition - Condition.
*/
getLastChildOrThrow(condition) {
return errors.throwIfNullOrUndefined(this.getLastChild(condition), "Could not find a child that matched the specified condition.");
}
/**
* Gets the last child by a condition.
* @param condition - Condition.
*/
getLastChild(condition) {
for (const child of this.getChildren().reverse()) {
if (condition == null || condition(child))
return child;
}
return undefined;
}
/**
* Gets the first descendant by a condition or throws.
* @param condition - Condition.
*/
getFirstDescendantOrThrow(condition) {
return errors.throwIfNullOrUndefined(this.getFirstDescendant(condition), "Could not find a descendant that matched the specified condition.");
}
/**
* Gets the first descendant by a condition.
* @param condition - Condition.
*/
getFirstDescendant(condition) {
for (const descendant of this.getDescendantsIterator()) {
if (condition == null || condition(descendant))
return descendant;
}
return undefined;
}
/**
* Offset this node's positions (pos and end) and all of its children by the given offset.
* @internal
* @param offset - Offset.
*/
offsetPositions(offset) {
this.compilerNode.pos += offset;
this.compilerNode.end += offset;
for (const child of this.getChildren()) {
child.offsetPositions(offset);
}
}
/**
* Gets the previous sibling or throws.
* @param condition - Optional condition for getting the previous sibling.
*/
getPreviousSiblingOrThrow(condition) {
return errors.throwIfNullOrUndefined(this.getPreviousSibling(condition), "Could not find the previous sibling.");
}
/**
* Gets the previous sibling.
* @param condition - Optional condition for getting the previous sibling.
*/
getPreviousSibling(condition) {
for (const sibling of this.getPreviousSiblings()) {
if (condition == null || condition(sibling))
return sibling;
}
return undefined;
}
/**
* Gets the next sibling or throws.
* @param condition - Optional condition for getting the next sibling.
*/
getNextSiblingOrThrow(condition) {
return errors.throwIfNullOrUndefined(this.getNextSibling(condition), "Could not find the next sibling.");
}
/**
* Gets the next sibling.
* @param condition - Optional condition for getting the previous sibling.
*/
getNextSibling(condition) {
for (const sibling of this.getNextSiblings()) {
if (condition == null || condition(sibling))
return sibling;
}
return undefined;
}
/**
* Gets the previous siblings.
*
* Note: Closest sibling is the zero index.
*/
getPreviousSiblings() {
const parent = this.getParentSyntaxList() || this.getParentOrThrow();
const previousSiblings = [];
for (const child of parent.getChildrenIterator()) {
if (child === this)
break;
previousSiblings.splice(0, 0, child);
}
return previousSiblings;
}
/**
* Gets the next siblings.
*
* Note: Closest sibling is the zero index.
*/
getNextSiblings() {
let foundChild = false;
const nextSiblings = [];
const parent = this.getParentSyntaxList() || this.getParentOrThrow();
for (const child of parent.getChildrenIterator()) {
if (!foundChild) {
foundChild = child === this;
continue;
}
nextSiblings.push(child);
}
return nextSiblings;
}
/**
* Gets the children of the node.
*/
getChildren() {
return this.compilerNode.getChildren().map(n => this.global.compilerFactory.getNodeFromCompilerNode(n, this.sourceFile));
}
/**
* @internal
*/
*getChildrenIterator() {
for (const compilerChild of this.compilerNode.getChildren(this.sourceFile.compilerNode)) {
yield this.global.compilerFactory.getNodeFromCompilerNode(compilerChild, this.sourceFile);
}
}
/**
* Gets the child syntax list or throws if it doesn't exist.
*/
getChildSyntaxListOrThrow() {
return errors.throwIfNullOrUndefined(this.getChildSyntaxList(), "A child syntax list was expected.");
}
/**
* Gets the child syntax list if it exists.
*/
getChildSyntaxList() {
let node = this;
if (utils_1.TypeGuards.isBodyableNode(node) || utils_1.TypeGuards.isBodiedNode(node)) {
do {
node = utils_1.TypeGuards.isBodyableNode(node) ? node.getBodyOrThrow() : node.getBody();
} while ((utils_1.TypeGuards.isBodyableNode(node) || utils_1.TypeGuards.isBodiedNode(node)) && node.compilerNode.statements == null);
}
if (utils_1.TypeGuards.isSourceFile(node) || utils_1.TypeGuards.isBodyableNode(this) || utils_1.TypeGuards.isBodiedNode(this))
return node.getFirstChildByKind(ts.SyntaxKind.SyntaxList);
let passedBrace = false;
for (const child of node.getChildrenIterator()) {
if (!passedBrace)
passedBrace = child.getKind() === ts.SyntaxKind.FirstPunctuation;
else if (child.getKind() === ts.SyntaxKind.SyntaxList)
return child;
}
return undefined;
}
/**
* Gets the node's descendants.
*/
getDescendants() {
return Array.from(this.getDescendantsIterator());
}
/**
* Gets the node's descendants as an iterator.
* @internal
*/
*getDescendantsIterator() {
for (const child of this.getChildrenIterator()) {
yield child;
for (const childChild of child.getDescendantsIterator())
yield childChild;
}
}
/**
* Gets the child count.
*/
getChildCount() {
return this.compilerNode.getChildCount(this.sourceFile.compilerNode);
}
/**
* Gets the child at the provided position, or undefined if not found.
* @param pos - Position to search for.
*/
getChildAtPos(pos) {
if (pos < this.getPos() || pos >= this.getEnd())
return undefined;
for (const child of this.getChildrenIterator()) {
if (pos >= child.getPos() && pos < child.getEnd())
return child;
}
return undefined;
}
/**
* Gets the most specific descendant at the provided position, or undefined if not found.
* @param pos - Position to search for.
*/
getDescendantAtPos(pos) {
let node;
while (true) {
const nextNode = (node || this).getChildAtPos(pos);
if (nextNode == null)
return node;
else
node = nextNode;
}
}
/**
* Gets the start position with leading trivia.
*/
getPos() {
return this.compilerNode.pos;
}
/**
* Gets the end position.
*/
getEnd() {
return this.compilerNode.end;
}
/**
* Gets the start position without leading trivia.
*/
getStart() {
return this.compilerNode.getStart(this.sourceFile.compilerNode);
}
/**
* Gets the first position that is not whitespace.
*/
getNonWhitespaceStart() {
return textSeek_1.getNextNonWhitespacePos(this.sourceFile.getFullText(), this.getPos());
}
/**
* Gets the width of the node (length without trivia).
*/
getWidth() {
return this.compilerNode.getWidth(this.sourceFile.compilerNode);
}
/**
* Gets the full width of the node (length with trivia).
*/
getFullWidth() {
return this.compilerNode.getFullWidth();
}
/**
* Gets the text without leading trivia.
*/
getText() {
return this.compilerNode.getText(this.sourceFile.compilerNode);
}
/**
* Gets the full text with leading trivia.
*/
getFullText() {
return this.compilerNode.getFullText(this.sourceFile.compilerNode);
}
/**
* Gets the combined modifier flags.
*/
getCombinedModifierFlags() {
return ts.getCombinedModifierFlags(this.compilerNode);
}
/**
* @internal
*/
replaceCompilerNode(compilerNode) {
this._compilerNode = compilerNode;
}
/**
* Gets the source file.
*/
getSourceFile() {
return this.sourceFile;
}
/**
* Gets a compiler node property wrapped in a Node.
* @param propertyName - Property name.
*/
getNodeProperty(propertyName) {
// todo: once filtering keys by type is supported need to (1) make this only show keys that are of type ts.Node and (2) have ability to return an array of nodes.
if (this.compilerNode[propertyName].kind == null)
throw new errors.InvalidOperationError(`Attempted to get property '${propertyName}', but ${"getNodeProperty"} ` +
`only works with properties that return a node.`);
return this.global.compilerFactory.getNodeFromCompilerNode(this.compilerNode[propertyName], this.sourceFile);
}
/**
* Goes up the tree getting all the parents in ascending order.
*/
getAncestors() {
return Array.from(this.getAncestorsIterator());
}
/**
* @internal
*/
*getAncestorsIterator() {
let parent = this.getParent();
while (parent != null) {
yield parent;
parent = parent.getParent();
}
}
/**
* Get the node's parent.
*/
getParent() {
return (this.compilerNode.parent == null) ? undefined : this.global.compilerFactory.getNodeFromCompilerNode(this.compilerNode.parent, this.sourceFile);
}
/**
* Gets the parent or throws an error if it doesn't exist.
*/
getParentOrThrow() {
return errors.throwIfNullOrUndefined(this.getParent(), "A parent is required to do this operation.");
}
/**
* Gets the last token of this node. Usually this is a close brace.
*/
getLastToken() {
const lastToken = this.compilerNode.getLastToken(this.sourceFile.compilerNode);
if (lastToken == null)
throw new errors.NotImplementedError("Not implemented scenario where the last token does not exist.");
return this.global.compilerFactory.getNodeFromCompilerNode(lastToken, this.sourceFile);
}
/**
* Gets if this node is in a syntax list.
*/
isInSyntaxList() {
return this.getParentSyntaxList() != null;
}
/**
* Gets the parent if it's a syntax list or throws an error otherwise.
*/
getParentSyntaxListOrThrow() {
return errors.throwIfNullOrUndefined(this.getParentSyntaxList(), "Expected the parent to be a syntax list.");
}
/**
* Gets the parent if it's a syntax list.
*/
getParentSyntaxList() {
const parent = this.getParent();
if (parent == null)
return undefined;
const pos = this.getPos();
const end = this.getEnd();
for (const child of parent.getChildren()) {
if (child.getPos() > pos || child === this)
return undefined;
if (child.getKind() === ts.SyntaxKind.SyntaxList && child.getPos() <= pos && child.getEnd() >= end)
return child;
}
return undefined; // shouldn't happen
}
/**
* Gets the child index of this node relative to the parent.
*/
getChildIndex() {
const parent = this.getParentSyntaxList() || this.getParentOrThrow();
let i = 0;
for (const child of parent.getChildren()) {
if (child === this)
return i;
i++;
}
/* istanbul ignore next */
throw new errors.NotImplementedError("For some reason the child's parent did not contain the child.");
}
/**
* Gets the indentation text.
*/
getIndentationText() {
const sourceFileText = this.sourceFile.getFullText();
const startLinePos = this.getStartLinePos();
const startPos = this.getStart();
let text = "";
for (let i = startPos - 1; i >= startLinePos; i--) {
const currentChar = sourceFileText[i];
switch (currentChar) {
case " ":
case "\t":
text = currentChar + text;
break;
case "\n":
return text;
default:
text = "";
}
}
return text;
}
/**
* Gets the next indentation level text.
*/
getChildIndentationText() {
if (utils_1.TypeGuards.isSourceFile(this))
return "";
return this.getIndentationText() + this.global.manipulationSettings.getIndentationText();
}
/**
* Gets the position of the start of the line that this node is on.
*/
getStartLinePos() {
const sourceFileText = this.sourceFile.getFullText();
const startPos = this.getStart();
for (let i = startPos - 1; i >= 0; i--) {
const currentChar = sourceFileText[i];
if (currentChar === "\n")
return i + 1;
}
return 0;
}
/**
* Gets if this is the first node on the current line.
*/
isFirstNodeOnLine() {
const sourceFileText = this.sourceFile.getFullText();
const startPos = this.getStart();
for (let i = startPos - 1; i >= 0; i--) {
const currentChar = sourceFileText[i];
if (currentChar === " " || currentChar === "\t")
continue;
if (currentChar === "\n")
return true;
return false;
}
return false;
}
/**
* Gets the children based on a kind.
* @param kind - Syntax kind.
*/
getChildrenOfKind(kind) {
return this.getChildren().filter(c => c.getKind() === kind);
}
/**
* Gets the first child by syntax kind or throws an error if not found.
* @param kind - Syntax kind.
*/
getFirstChildByKindOrThrow(kind) {
return errors.throwIfNullOrUndefined(this.getFirstChildByKind(kind), `A child of the kind ${ts.SyntaxKind[kind]} was expected.`);
}
/**
* Gets the first child by syntax kind.
* @param kind - Syntax kind.
*/
getFirstChildByKind(kind) {
return this.getFirstChild(child => child.getKind() === kind);
}
/**
* Gets the first child if it matches the specified syntax kind or throws an error if not found.
* @param kind - Syntax kind.
*/
getFirstChildIfKindOrThrow(kind) {
return errors.throwIfNullOrUndefined(this.getFirstChildIfKind(kind), `A first child of the kind ${ts.SyntaxKind[kind]} was expected.`);
}
/**
* Gets the first child if it matches the specified syntax kind.
* @param kind - Syntax kind.
*/
getFirstChildIfKind(kind) {
const firstChild = this.getFirstChild();
return firstChild != null && firstChild.getKind() === kind ? firstChild : undefined;
}
/**
* Gets the last child by syntax kind or throws an error if not found.
* @param kind - Syntax kind.
*/
getLastChildByKindOrThrow(kind) {
return errors.throwIfNullOrUndefined(this.getLastChildByKind(kind), `A child of the kind ${ts.SyntaxKind[kind]} was expected.`);
}
/**
* Gets the last child by syntax kind.
* @param kind - Syntax kind.
*/
getLastChildByKind(kind) {
return this.getLastChild(child => child.getKind() === kind);
}
/**
* Gets the last child if it matches the specified syntax kind or throws an error if not found.
* @param kind - Syntax kind.
*/
getLastChildIfKindOrThrow(kind) {
return errors.throwIfNullOrUndefined(this.getLastChildIfKind(kind), `A last child of the kind ${ts.SyntaxKind[kind]} was expected.`);
}
/**
* Gets the last child if it matches the specified syntax kind.
* @param kind - Syntax kind.
*/
getLastChildIfKind(kind) {
const lastChild = this.getLastChild();
return lastChild != null && lastChild.getKind() === kind ? lastChild : undefined;
}
/**
* Gets the previous sibiling if it matches the specified kind, or throws.
* @param kind - Kind to check.
*/
getPreviousSiblingIfKindOrThrow(kind) {
return errors.throwIfNullOrUndefined(this.getPreviousSiblingIfKind(kind), `A previous sibling of kind ${ts.SyntaxKind[kind]} was expected.`);
}
/**
* Gets the next sibiling if it matches the specified kind, or throws.
* @param kind - Kind to check.
*/
getNextSiblingIfKindOrThrow(kind) {
return errors.throwIfNullOrUndefined(this.getNextSiblingIfKind(kind), `A next sibling of kind ${ts.SyntaxKind[kind]} was expected.`);
}
/**
* Gets the previous sibling if it matches the specified kind.
* @param kind - Kind to check.
*/
getPreviousSiblingIfKind(kind) {
const previousSibling = this.getPreviousSibling();
return previousSibling != null && previousSibling.getKind() === kind ? previousSibling : undefined;
}
/**
* Gets the next sibling if it matches the specified kind.
* @param kind - Kind to check.
*/
getNextSiblingIfKind(kind) {
const nextSibling = this.getNextSibling();
return nextSibling != null && nextSibling.getKind() === kind ? nextSibling : undefined;
}
/**
* Gets the parent if it's a certain syntax kind.
*/
getParentIfKind(kind) {
const parentNode = this.getParent();
return parentNode == null || parentNode.getKind() !== kind ? undefined : parentNode;
}
/**
* Gets the parent if it's a certain syntax kind of throws.
*/
getParentIfKindOrThrow(kind) {
return errors.throwIfNullOrUndefined(this.getParentIfKind(kind), `A parent with a syntax kind of ${ts.SyntaxKind[kind]} is required to do this operation.`);
}
/**
* Gets the first ancestor by syntax kind or throws if not found.
* @param kind - Syntax kind.
*/
getFirstAncestorByKindOrThrow(kind) {
return errors.throwIfNullOrUndefined(this.getFirstAncestorByKind(kind), `A parent of kind ${ts.SyntaxKind[kind]} is required to do this operation.`);
}
/**
* Get the first ancestor by syntax kind.
* @param kind - Syntax kind.
*/
getFirstAncestorByKind(kind) {
for (const parent of this.getAncestors()) {
if (parent.getKind() === kind)
return parent;
}
return undefined;
}
/**
* Gets the descendants that match a specified syntax kind.
* @param kind - Kind to check.
*/
getDescendantsOfKind(kind) {
return this.getDescendants().filter(c => c.getKind() === kind);
}
/**
* Gets the first descendant by syntax kind or throws.
* @param kind - Syntax kind.
*/
getFirstDescendantByKindOrThrow(kind) {
return errors.throwIfNullOrUndefined(this.getFirstDescendantByKind(kind), `A descendant of kind ${ts.SyntaxKind[kind]} is required to do this operation.`);
}
/**
* Gets the first descendant by syntax kind.
* @param kind - Syntax kind.
*/
getFirstDescendantByKind(kind) {
for (const descendant of this.getDescendantsIterator()) {
if (descendant.getKind() === kind)
return descendant;
}
return undefined;
}
}
exports.Node = Node;
//# sourceMappingURL=Node.js.map