123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- /**
- * The MIT License (MIT)
- * Copyright (c) 2017-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
- */
- 'use strict';
- var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
- var DEFAULT_COLLECTION_PROP = 'expressions';
- var DEFAULT_SINGLE_PROP = 'expression';
- /**
- * NodePath class encapsulates a traversing node,
- * its parent node, property name in the parent node, and
- * an index (in case if a node is part of a collection).
- * It also provides set of methods for AST manipulation.
- */
- var NodePath = function () {
- /**
- * NodePath constructor.
- *
- * @param Object node - an AST node
- * @param NodePath parentPath - a nullable parent path
- * @param string property - property name of the node in the parent
- * @param number index - index of the node in a collection.
- */
- function NodePath(node) {
- var parentPath = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
- var property = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
- var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
- _classCallCheck(this, NodePath);
- this.node = node;
- this.parentPath = parentPath;
- this.parent = parentPath ? parentPath.node : null;
- this.property = property;
- this.index = index;
- }
- _createClass(NodePath, [{
- key: '_enforceProp',
- value: function _enforceProp(property) {
- if (!this.node.hasOwnProperty(property)) {
- throw new Error('Node of type ' + this.node.type + ' doesn\'t have "' + property + '" collection.');
- }
- }
- /**
- * Sets a node into a children collection or the single child.
- * By default child nodes are supposed to be under `expressions` property.
- * An explicit property can be passed.
- *
- * @param Object node - a node to set into a collection or as single child
- * @param number index - index at which to set
- * @param string property - name of the collection or single property
- */
- }, {
- key: 'setChild',
- value: function setChild(node) {
- var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
- var property = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
- var childPath = void 0;
- if (index != null) {
- if (!property) {
- property = DEFAULT_COLLECTION_PROP;
- }
- this._enforceProp(property);
- this.node[property][index] = node;
- childPath = NodePath.getForNode(node, this, property, index);
- } else {
- if (!property) {
- property = DEFAULT_SINGLE_PROP;
- }
- this._enforceProp(property);
- this.node[property] = node;
- childPath = NodePath.getForNode(node, this, property, null);
- }
- return childPath;
- }
- /**
- * Appends a node to a children collection.
- * By default child nodes are supposed to be under `expressions` property.
- * An explicit property can be passed.
- *
- * @param Object node - a node to set into a collection or as single child
- * @param string property - name of the collection or single property
- */
- }, {
- key: 'appendChild',
- value: function appendChild(node) {
- var property = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
- if (!property) {
- property = DEFAULT_COLLECTION_PROP;
- }
- this._enforceProp(property);
- var end = this.node[property].length;
- return this.setChild(node, end, property);
- }
- /**
- * Inserts a node into a collection.
- * By default child nodes are supposed to be under `expressions` property.
- * An explicit property can be passed.
- *
- * @param Object node - a node to insert into a collection
- * @param number index - index at which to insert
- * @param string property - name of the collection property
- */
- }, {
- key: 'insertChildAt',
- value: function insertChildAt(node, index) {
- var property = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_COLLECTION_PROP;
- this._enforceProp(property);
- this.node[property].splice(index, 0, node);
- // If we inserted a node before the traversing index,
- // we should increase the later.
- if (index <= NodePath.getTraversingIndex()) {
- NodePath.updateTraversingIndex(+1);
- }
- this._rebuildIndex(this.node, property);
- }
- /**
- * Removes a node.
- */
- }, {
- key: 'remove',
- value: function remove() {
- if (this.isRemoved()) {
- return;
- }
- NodePath.registry.delete(this.node);
- this.node = null;
- if (!this.parent) {
- return;
- }
- // A node is in a collection.
- if (this.index !== null) {
- this.parent[this.property].splice(this.index, 1);
- // If we remove a node before the traversing index,
- // we should increase the later.
- if (this.index <= NodePath.getTraversingIndex()) {
- NodePath.updateTraversingIndex(-1);
- }
- // Rebuild index.
- this._rebuildIndex(this.parent, this.property);
- this.index = null;
- this.property = null;
- return;
- }
- // A simple node.
- delete this.parent[this.property];
- this.property = null;
- }
- /**
- * Rebuilds child nodes index (used on remove/insert).
- */
- }, {
- key: '_rebuildIndex',
- value: function _rebuildIndex(parent, property) {
- var parentPath = NodePath.getForNode(parent);
- for (var i = 0; i < parent[property].length; i++) {
- var path = NodePath.getForNode(parent[property][i], parentPath, property, i);
- path.index = i;
- }
- }
- /**
- * Whether the path was removed.
- */
- }, {
- key: 'isRemoved',
- value: function isRemoved() {
- return this.node === null;
- }
- /**
- * Replaces a node with the passed one.
- */
- }, {
- key: 'replace',
- value: function replace(newNode) {
- NodePath.registry.delete(this.node);
- this.node = newNode;
- if (!this.parent) {
- return null;
- }
- // A node is in a collection.
- if (this.index !== null) {
- this.parent[this.property][this.index] = newNode;
- }
- // A simple node.
- else {
- this.parent[this.property] = newNode;
- }
- // Rebuild the node path for the new node.
- return NodePath.getForNode(newNode, this.parentPath, this.property, this.index);
- }
- /**
- * Updates a node inline.
- */
- }, {
- key: 'update',
- value: function update(nodeProps) {
- Object.assign(this.node, nodeProps);
- }
- /**
- * Returns parent.
- */
- }, {
- key: 'getParent',
- value: function getParent() {
- return this.parentPath;
- }
- /**
- * Returns nth child.
- */
- }, {
- key: 'getChild',
- value: function getChild() {
- var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
- if (this.node.expressions) {
- return NodePath.getForNode(this.node.expressions[n], this, DEFAULT_COLLECTION_PROP, n);
- } else if (this.node.expression && n == 0) {
- return NodePath.getForNode(this.node.expression, this, DEFAULT_SINGLE_PROP);
- }
- return null;
- }
- /**
- * Whether a path node is syntactically equal to the passed one.
- *
- * NOTE: we don't rely on `source` property from the `loc` data
- * (which would be the fastest comparison), since it might be unsync
- * after several modifications. We use here simple `JSON.stringify`
- * excluding the `loc` data.
- *
- * @param NodePath other - path to compare to.
- * @return boolean
- */
- }, {
- key: 'hasEqualSource',
- value: function hasEqualSource(path) {
- return JSON.stringify(this.node, jsonSkipLoc) === JSON.stringify(path.node, jsonSkipLoc);
- }
- /**
- * JSON-encodes a node skipping location.
- */
- }, {
- key: 'jsonEncode',
- value: function jsonEncode() {
- var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
- format = _ref.format,
- useLoc = _ref.useLoc;
- return JSON.stringify(this.node, useLoc ? null : jsonSkipLoc, format);
- }
- /**
- * Returns previous sibling.
- */
- }, {
- key: 'getPreviousSibling',
- value: function getPreviousSibling() {
- if (!this.parent || this.index == null) {
- return null;
- }
- return NodePath.getForNode(this.parent[this.property][this.index - 1], NodePath.getForNode(this.parent), this.property, this.index - 1);
- }
- /**
- * Returns next sibling.
- */
- }, {
- key: 'getNextSibling',
- value: function getNextSibling() {
- if (!this.parent || this.index == null) {
- return null;
- }
- return NodePath.getForNode(this.parent[this.property][this.index + 1], NodePath.getForNode(this.parent), this.property, this.index + 1);
- }
- /**
- * Returns a NodePath instance for a node.
- *
- * The same NodePath can be reused in several places, e.g.
- * a parent node passed for all its children.
- */
- }], [{
- key: 'getForNode',
- value: function getForNode(node) {
- var parentPath = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
- var prop = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
- var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : -1;
- if (!node) {
- return null;
- }
- if (!NodePath.registry.has(node)) {
- NodePath.registry.set(node, new NodePath(node, parentPath, prop, index == -1 ? null : index));
- }
- var path = NodePath.registry.get(node);
- if (parentPath !== null) {
- path.parentPath = parentPath;
- path.parent = path.parentPath.node;
- }
- if (prop !== null) {
- path.property = prop;
- }
- if (index >= 0) {
- path.index = index;
- }
- return path;
- }
- /**
- * Initializes the NodePath registry. The registry is a map from
- * a node to its NodePath instance.
- */
- }, {
- key: 'initRegistry',
- value: function initRegistry() {
- if (!NodePath.registry) {
- NodePath.registry = new Map();
- }
- NodePath.registry.clear();
- }
- /**
- * Updates index of a currently traversing collection.
- */
- }, {
- key: 'updateTraversingIndex',
- value: function updateTraversingIndex(dx) {
- return NodePath.traversingIndexStack[NodePath.traversingIndexStack.length - 1] += dx;
- }
- /**
- * Returns current traversing index.
- */
- }, {
- key: 'getTraversingIndex',
- value: function getTraversingIndex() {
- return NodePath.traversingIndexStack[NodePath.traversingIndexStack.length - 1];
- }
- }]);
- return NodePath;
- }();
- NodePath.initRegistry();
- /**
- * Index of a currently traversing collection is stored on top of the
- * `NodePath.traversingIndexStack`. Remove/insert methods can adjust
- * this index.
- */
- NodePath.traversingIndexStack = [];
- // Helper function used to skip `loc` in JSON operations.
- function jsonSkipLoc(prop, value) {
- if (prop === 'loc') {
- return undefined;
- }
- return value;
- }
- module.exports = NodePath;
|