UNPKG

cst

Version:

JavaScript CST Implementation

807 lines (671 loc) 24.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _lines = require('../utils/lines'); var _ElementAssert = require('./ElementAssert'); var _ElementAssert2 = _interopRequireDefault(_ElementAssert); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Base class for Node, Token and Fragment. * * @name Element * @class * @abstract */ var Element = function () { /** * @param {String} type * @param {Element[]} children */ function Element(type, children) { (0, _classCallCheck3.default)(this, Element); this.type = type; this.firstChild = null; this.lastChild = null; this.parentElement = null; this.nextSibling = null; this.previousSibling = null; this.childElements = []; if (children) { for (var i = 0; i < children.length; i++) { if (children[i].parentElement) { throw new Error('Cannot add element to several parents'); } } this._setChildren(children); } this.isToken = false; this.isWhitespace = false; this.isCode = true; this.isComment = false; this.isNonCodeToken = this.isComment || this.isWhitespace; this.isNode = false; this.isStatement = false; this.isPattern = false; this.isAssignable = false; this.isFragment = false; } (0, _createClass3.default)(Element, [{ key: 'getOwnerProgram', // ==== Traversing ================================================================================================= /** * Owner Program for this element or null if element does not have Program in its parent hierarchy. * * @returns {Program} */ value: function getOwnerProgram() { var element = this; while (element && !element._isProgram) { element = element.parentElement; } return element; } }, { key: 'getNextToken', value: function getNextToken() { var element = this; while (element) { if (element.nextSibling) { return element.nextSibling.getFirstToken(); } element = element.parentElement; } return null; } }, { key: 'getPreviousToken', value: function getPreviousToken() { var element = this; while (element) { if (element.previousSibling) { return element.previousSibling.getLastToken(); } element = element.parentElement; } return null; } /** * Next token (non-whitespace and non-comment). Null if token was not found. * * @returns {Element|null} */ }, { key: 'getNextCodeToken', value: function getNextCodeToken() { var token = this.getNextToken(); while (token && !token.isCode) { token = token.getNextToken(); } return token; } /** * Previous token (non-whitespace and non-comment). Null if token was not found. * * @returns {Element|null} */ }, { key: 'getPreviousCodeToken', value: function getPreviousCodeToken() { var token = this.getPreviousToken(); while (token && !token.isCode) { token = token.getPreviousToken(); } return token; } /** * Next non-whitespace token. Null if token was not found. * * @returns {Element|null} */ }, { key: 'getNextNonWhitespaceToken', value: function getNextNonWhitespaceToken() { var token = this.getNextToken(); while (token && token.isWhitespace) { token = token.getNextToken(); } return token; } /** * Previous non-whitespace token. Null if token was not found. * * @returns {Element|null} */ }, { key: 'getPreviousNonWhitespaceToken', value: function getPreviousNonWhitespaceToken() { var token = this.getPreviousToken(); while (token && token.isWhitespace) { token = token.getPreviousToken(); } return token; } /** * First token inside element child tree. * * @returns {Element|null} */ }, { key: 'getFirstToken', value: function getFirstToken() { var element = this.firstChild; while (element && !element.isToken) { element = element.firstChild; } return element; } /** * Last token inside element child tree. * * @returns {Element|null} */ }, { key: 'getLastToken', value: function getLastToken() { var element = this.lastChild; while (element && !element.isToken) { element = element.lastChild; } return element; } /** * Calculates and returns Element range. * * @returns {Number[]} */ }, { key: 'getRange', value: function getRange() { var counter = 0; var previous = this.getPreviousToken(); while (previous) { counter += previous._sourceCodeLength; previous = previous.getPreviousToken(); } return [counter, counter + this.getSourceCodeLength()]; } /** * Calculates and returns Element loc. * * @returns {Object} */ }, { key: 'getLoc', value: function getLoc() { var prevToken = this.getPreviousToken(); var startColumn = 0; var startLine = 1; while (prevToken) { var lines = prevToken._sourceCodeLines; startColumn += lines[lines.length - 1].length; if (lines.length > 1) { while (prevToken) { startLine += prevToken.getNewlineCount(); prevToken = prevToken.getPreviousToken(); } break; } prevToken = prevToken.getPreviousToken(); } var elementLines = this.getSourceCodeLines(); var endLine = startLine + elementLines.length - 1; var endColumn = elementLines[elementLines.length - 1].length; if (startLine === endLine) { endColumn += startColumn; } return { start: { line: startLine, column: startColumn }, end: { line: endLine, column: endColumn } }; } // ==== Source Code ================================================================================================ /** * Generated source code length. * * @returns {Number} */ }, { key: 'getSourceCodeLength', value: function getSourceCodeLength() { var length = 0; var child = this.firstChild; while (child) { length += child.getSourceCodeLength(); child = child.nextSibling; } return length; } /** * Generated source code. * * @returns {String} */ }, { key: 'getSourceCode', value: function getSourceCode() { var code = ''; var child = this.firstChild; while (child) { code += child.getSourceCode(); child = child.nextSibling; } return code; } /** * Generated source code lines. * * @returns {String[]} */ }, { key: 'getSourceCodeLines', value: function getSourceCodeLines() { return (0, _lines.getLines)(this.getSourceCode()); } /** * Generated source code line break count. * * @returns {Number} */ }, { key: 'getNewlineCount', value: function getNewlineCount() { var count = 0; var child = this.firstChild; while (child) { count += child.getNewlineCount(); child = child.nextSibling; } return count; } // ==== Child Element Manipulation ================================================================================= /** * Removes specified element from the element child list. * * @param {Element} element * * @returns {Element} */ }, { key: 'removeChild', value: function removeChild(element) { if (element.parentElement !== this) { throw new Error('The element to be removed is not a child of this element.'); } var children = this.childElements.concat(); var elementIndex = children.indexOf(element); children.splice(elementIndex, 1); this._setChildren(children); var ownerProgram = this.getOwnerProgram(); if (ownerProgram) { ownerProgram._removeElementsFromProgram([element]); } setParentElement(element, null); return element; } /** * Removes element. */ }, { key: 'remove', value: function remove() { if (!this.parentElement) { return; } this.parentElement.removeChild(this); } /** * Appends specified element to the end of the child list. * Accepts multiple nodes using `Fragment`. * * @param {Element} newElement */ }, { key: 'appendChild', value: function appendChild(newElement) { var children = void 0; var newElements = void 0; if (newElement.isFragment) { this._ensureCanAdoptFragment(newElement); newElements = newElement.childElements; children = this.childElements.concat(newElement.childElements); } else { if (newElement.parentElement) { throw new Error('Remove element before adding again'); } this._ensureCanAdopt(newElement); newElements = [newElement]; children = this.childElements.concat(newElement); } this._setChildren(children); if (newElements) { var ownerProgram = this.getOwnerProgram(); if (ownerProgram) { ownerProgram._addElementsToProgram(newElements); } } } /** * Prepends specified element to the beginning of the child list. * Accepts multiple nodes using `Fragment`. * * @param {Element} newElement */ }, { key: 'prependChild', value: function prependChild(newElement) { var children = void 0; var newElements = void 0; if (newElement.isFragment) { this._ensureCanAdoptFragment(newElement); newElements = newElement.childElements; children = newElement.childElements.concat(this.childElements); } else { if (newElement.parentElement) { throw new Error('Remove element before adding again'); } this._ensureCanAdopt(newElement); newElements = [newElement]; children = [newElement].concat(this.childElements); } this._setChildren(children); if (newElements) { var ownerProgram = this.getOwnerProgram(); if (ownerProgram) { ownerProgram._prependElementsToProgram(newElements); } } } /** * Inserts specified element before specified reference child. * Accepts multiple nodes using `Fragment`. * * @param {Element} newElement * @param {Element} referenceChild */ }, { key: 'insertChildBefore', value: function insertChildBefore(newElement, referenceChild) { if (referenceChild.parentElement !== this) { throw new Error('Invalid reference child'); } var index = this.childElements.indexOf(referenceChild); var childrenBefore = this.childElements.slice(0, index); var childrenAfter = this.childElements.slice(index); var children = void 0; var newElements = void 0; if (newElement.isFragment) { this._ensureCanAdoptFragment(newElement); newElements = newElement.childElements; children = childrenBefore.concat(newElement.childElements, childrenAfter); } else { if (newElement.parentElement) { throw new Error('Remove element before adding again'); } this._ensureCanAdopt(newElement); children = childrenBefore.concat(newElement, childrenAfter); newElements = [newElement]; } this._setChildren(children); if (newElements) { var ownerProgram = this.getOwnerProgram(); if (ownerProgram) { ownerProgram._addElementsToProgram(newElements); } } } /** * Replaces children from `firstRefChild` to `lastRefChild` with specified element. * Accepts multiple replacement nodes using `Fragment`. * * @param {Element} newElement * @param {Element} firstRefChild * @param {Element} lastRefChild */ }, { key: 'replaceChildren', value: function replaceChildren(newElement, firstRefChild, lastRefChild) { if (!firstRefChild || firstRefChild.parentElement !== this) { throw new Error('Invalid first reference child'); } if (!lastRefChild || lastRefChild.parentElement !== this) { throw new Error('Invalid last reference child'); } var firstIndex = this.childElements.indexOf(firstRefChild); var lastIndex = this.childElements.indexOf(lastRefChild); if (firstIndex > lastIndex) { throw new Error('Invalid reference children order'); } var childrenBefore = this.childElements.slice(0, firstIndex); var childrenAfter = this.childElements.slice(lastIndex + 1); var replacedChildren = this.childElements.slice(firstIndex, lastIndex + 1); var children = void 0; var newElements = void 0; if (newElement.isFragment) { this._ensureCanAdoptFragment(newElement); children = childrenBefore.concat(newElement.childElements, childrenAfter); newElements = newElement.childElements; } else { if (newElement.parentElement) { throw new Error('Remove element before adding again'); } this._ensureCanAdopt(newElement); children = childrenBefore.concat(newElement, childrenAfter); newElements = [newElement]; } this._setChildren(children); var ownerProgram = this.getOwnerProgram(); if (ownerProgram) { ownerProgram._removeElementsFromProgram(replacedChildren); } for (var i = 0; i < replacedChildren.length; i++) { var replacedChild = replacedChildren[i]; replacedChild.previousSibling = null; replacedChild.nextSibling = null; setParentElement(replacedChild, null); } if (ownerProgram && newElements) { ownerProgram._addElementsToProgram(newElements); } } /** * Removes children from `firstRefChild` to `lastRefChild` with specified element. * * @param {Element} firstRefChild * @param {Element} lastRefChild */ }, { key: 'removeChildren', value: function removeChildren(firstRefChild, lastRefChild) { if (!firstRefChild || firstRefChild.parentElement !== this) { throw new Error('Invalid first reference child'); } if (!lastRefChild || lastRefChild.parentElement !== this) { throw new Error('Invalid last reference child'); } var firstIndex = this.childElements.indexOf(firstRefChild); var lastIndex = this.childElements.indexOf(lastRefChild); if (firstIndex > lastIndex) { throw new Error('Invalid reference children order'); } var children = this.childElements.slice(0, firstIndex).concat(this.childElements.slice(lastIndex + 1)); var removedChildren = this.childElements.slice(firstIndex, lastIndex + 1); this._setChildren(children); var ownerProgram = this.getOwnerProgram(); if (ownerProgram) { ownerProgram._removeElementsFromProgram(removedChildren); } for (var i = 0; i < removedChildren.length; i++) { var removedChild = removedChildren[i]; removedChild.previousSibling = null; removedChild.nextSibling = null; setParentElement(removedChild, null); } } /** * Replaces child with specified element. * Accepts multiple replacement nodes using `Fragment`. * * @param {Element} newElement * @param {Element} oldElement */ }, { key: 'replaceChild', value: function replaceChild(newElement, oldElement) { this.replaceChildren(newElement, oldElement, oldElement); } /** * Returns array of child element from firstRefChild to lastRefChild (including reference children). * * @param {Element} firstRefChild * @param {Element} lastRefChild * @returns {Array} */ }, { key: 'getChildrenBetween', value: function getChildrenBetween(firstRefChild, lastRefChild) { if (!firstRefChild || firstRefChild.parentElement !== this) { throw new Error('Invalid first reference child'); } if (!lastRefChild || lastRefChild.parentElement !== this) { throw new Error('Invalid last reference child'); } var firstIndex = this.childElements.indexOf(firstRefChild); var lastIndex = this.childElements.indexOf(lastRefChild); if (firstIndex > lastIndex) { throw new Error('Invalid reference children order'); } return this.childElements.slice(firstIndex, lastIndex + 1); } /** * Makes sure specified child is not already one of the parents of this element. * Throws error on failure. * * @param {Element} child * @private */ }, { key: '_ensureCanAdopt', value: function _ensureCanAdopt(child) { var element = this; while (element) { if (element === child) { throw new Error('The new child element contains the parent.'); } element = element.parentElement; } } /** * Calls _ensureCanAdopt for each fragment element. * * @param {Element} fragment * @private */ }, { key: '_ensureCanAdoptFragment', value: function _ensureCanAdoptFragment(fragment) { var fragmentChild = fragment.firstChild; while (fragmentChild) { this._ensureCanAdopt(fragmentChild); fragmentChild = fragmentChild.nextSibling; } } /** * Assigns new children. Runs element syntax assertions. * * @param {Element[]} newChildren * @private */ }, { key: '_setChildren', value: function _setChildren(newChildren) { this._acceptChildren(new _ElementAssert2.default(newChildren)); if (newChildren.length > 0) { var previousChild = newChildren[0]; this.firstChild = previousChild; previousChild.previousSibling = null; setParentElement(previousChild, this); if (newChildren.length > 1) { // TODO(flow): error with only `let child;` var child = newChildren[1]; for (var i = 1; i < newChildren.length; i++) { child = newChildren[i]; child.previousSibling = previousChild; setParentElement(child, this); previousChild.nextSibling = child; previousChild = child; } child.nextSibling = null; this.lastChild = child; } else { previousChild.nextSibling = null; this.lastChild = previousChild; } } else { this.firstChild = this.lastChild = null; } this.childElements = newChildren; } /** * Runs element syntax assertions. Should be implemented for every Node. * * @param {Object} children * @abstract */ }, { key: '_acceptChildren', value: function _acceptChildren(children) {} // Override /** * Clones current Element structure. * * @returns {Element} */ }, { key: 'cloneElement', value: function cloneElement() { var clonedChildren = new Array(this.childElements.length); for (var i = 0; i < clonedChildren.length; i++) { clonedChildren[i] = this.childElements[i].cloneElement(); } var objectToClone = this; return new objectToClone.constructor(clonedChildren); } }]); return Element; }(); /** * Artificial class for correct flow behaviour. */ exports.default = Element; var ConcreteElement = function (_Element) { (0, _inherits3.default)(ConcreteElement, _Element); function ConcreteElement(children) { (0, _classCallCheck3.default)(this, ConcreteElement); return (0, _possibleConstructorReturn3.default)(this, (ConcreteElement.__proto__ || (0, _getPrototypeOf2.default)(ConcreteElement)).call(this, 'ConcreteElement', children)); } return ConcreteElement; }(Element); function setParentElement(element, parentElement) { element.parentElement = parentElement; if (element._onSetParentElement) { element._onSetParentElement(parentElement); } } //# sourceMappingURL=Element.js.map