UNPKG

cst

Version:

JavaScript CST Implementation

963 lines (844 loc) 29.1 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 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 _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } var _utilsLines = require('../utils/lines'); var _ElementAssert = require('./ElementAssert'); var _ElementAssert2 = _interopRequireDefault(_ElementAssert); /** * Base class for Node, Token and Fragment. * * @name Element * @class * @abstract */ var Element = (function () { /** * @param {String} type * @param {Element[]} children */ function Element(type, children) { _classCallCheck(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); } } /** * Artificial class for correct flow behaviour. */ _createClass(Element, [{ key: 'removeChild', // ==== Child Element Manipulation ================================================================================= /** * Removes specified element from the element child list. * * @param {Element} element * * @returns {Element} */ 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.ownerProgram; if (ownerProgram) { ownerProgram._removeElementsFromProgram([element]); } element._parentElement = 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 = undefined; var newElements = undefined; 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.ownerProgram; 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 = undefined; var newElements = undefined; 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.ownerProgram; if (ownerProgram) { ownerProgram._addElementsToProgram(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 = undefined; var newElements = undefined; 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.ownerProgram; 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 = undefined; var newElements = undefined; 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.ownerProgram; if (ownerProgram) { ownerProgram._removeElementsFromProgram(replacedChildren); } for (var i = 0; i < replacedChildren.length; i++) { var replacedChild = replacedChildren[i]; replacedChild._parentElement = null; replacedChild._previousSibling = null; replacedChild._nextSibling = null; } if (ownerProgram && newElements) { ownerProgram._addElementsToProgram(newElements); } } /** * 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._parentElement = this; previousChild._previousSibling = null; 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._parentElement = this; child._previousSibling = previousChild; 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); } }, { key: 'type', /** * Element type. * Important: some Node types also exist in Tokens. Do not rely on type only, use isToken, isNode. * * @returns {String} */ get: function get() { return this._type; } /** * True if Element is a Token. * * @returns {Boolean} */ }, { key: 'isToken', get: function get() { return false; } /** * True if Element is a whitespace Token. * * @returns {Boolean} */ }, { key: 'isWhitespace', get: function get() { return false; } /** * True if Element is a code Token. * * @returns {Boolean} */ }, { key: 'isCode', get: function get() { return true; } /** * True if Element is a comment Token. * * @returns {Boolean} */ }, { key: 'isComment', get: function get() { return false; } /** * True if Element is Comment or Whitespace. * * @returns {Boolean} */ }, { key: 'isNonCodeToken', get: function get() { return this.isComment || this.isWhitespace; } /** * True if Element is a Node. * * @returns {Boolean} */ }, { key: 'isNode', get: function get() { return false; } /** * True if Element is a Node which can be used as Expression. * * @returns {Boolean} */ }, { key: 'isExpression', get: function get() { return false; } /** * True if Element is a Node which can be used as Statement. * * @returns {Boolean} */ }, { key: 'isStatement', get: function get() { return false; } /** * True if Element is a Node which can be used as Pattern. * * @returns {Boolean} */ }, { key: 'isPattern', get: function get() { return false; } /** * True if Element is a Node which can be used as left part of Assignment. * * @returns {Boolean} */ }, { key: 'isAssignable', get: function get() { return false; } /** * True if Element is a Fragment. * * @returns {Boolean} */ }, { key: 'isFragment', get: function get() { return false; } // ==== Traversing ================================================================================================= /** * Parent element or null if parent element does not exist. * * @returns {Element|null} */ }, { key: 'parentElement', get: function get() { return this._parentElement; } /** * Owner Program for this element or null if element does not have Program in its parent hierarchy. * * @returns {Program} */ }, { key: 'ownerProgram', get: function get() { var element = this; while (element && !element._isProgram) { element = element._parentElement; } return element; } /** * Next sibling within parent. * Null if element is the last child in parent or if element does not have parent. * * @returns {Element|null} */ }, { key: 'nextSibling', get: function get() { return this._nextSibling; } /** * Previous sibling within parent. * Null if element is the first child in parent or if element does not have parent. * * @returns {Element|null} */ }, { key: 'previousSibling', get: function get() { return this._previousSibling; } /** * Next token. Null if token was not found. * * @returns {Element|null} */ }, { key: 'nextToken', get: function get() { if (this._nextSibling) { return this._nextSibling.firstToken; } if (this._parentElement) { return this._parentElement.nextToken; } return null; } /** * Previous token. Null if token was not found. * * @returns {Element|null} */ }, { key: 'previousToken', get: function get() { if (this._previousSibling) { return this._previousSibling.lastToken; } if (this._parentElement) { return this._parentElement.previousToken; } return null; } /** * Next token (non-whitespace and non-comment). Null if token was not found. * * @returns {Element|null} */ }, { key: 'nextCodeToken', get: function get() { var token = this.nextToken; while (token && !token.isCode) { token = token.nextToken; } return token; } /** * Previous token (non-whitespace and non-comment). Null if token was not found. * * @returns {Element|null} */ }, { key: 'previousCodeToken', get: function get() { var token = this.previousToken; while (token && !token.isCode) { token = token.previousToken; } return token; } /** * Next non-whitespace token. Null if token was not found. * * @returns {Element|null} */ }, { key: 'nextNonWhitespaceToken', get: function get() { var token = this.nextToken; while (token && token.isWhitespace) { token = token.nextToken; } return token; } /** * Previous non-whitespace token. Null if token was not found. * * @returns {Element|null} */ }, { key: 'previousNonWhitespaceToken', get: function get() { var token = this.previousToken; while (token && token.isWhitespace) { token = token.previousToken; } return token; } /** * Next whitespace token. Null if token was not found. * * @returns {Element|null} */ }, { key: 'nextWhitespaceToken', get: function get() { var token = this.nextToken; while (token && !token.isWhitespace) { token = token.nextToken; } return token; } /** * Previous whitespace token. Null if token was not found. * * @returns {Element|null} */ }, { key: 'previousWhitespaceToken', get: function get() { var token = this.previousToken; while (token && !token.isWhitespace) { token = token.previousToken; } return token; } /** * First token inside element child tree. * * @returns {Element|null} */ }, { key: 'firstToken', get: function get() { var element = this._firstChild; while (element && !element.isToken) { element = element._firstChild; } return element; } /** * Last token inside element child tree. * * @returns {Element|null} */ }, { key: 'lastToken', get: function get() { var element = this._lastChild; while (element && !element.isToken) { element = element._lastChild; } return element; } /** * First child element. Null if element does not have children. * * @returns {Element|null} */ }, { key: 'firstChild', get: function get() { return this._firstChild; } /** * Last child element. Null if element does not have children. * * @returns {Element|null} */ }, { key: 'lastChild', get: function get() { return this._lastChild; } /** * Direct children of the element. * * @returns {ElementList} */ }, { key: 'childElements', get: function get() { return this._childElements.concat(); } /** * Direct child count. * * @returns {Number} */ }, { key: 'childCount', get: function get() { return this._childElements.length; } /** * Calculates and returns Element range. * * @returns {Number[]} */ }, { key: 'range', get: function get() { var counter = 0; var previous = this.previousToken; while (previous) { counter += previous.sourceCodeLength; previous = previous.previousToken; } return [counter, counter + this.sourceCodeLength]; } /** * Calculates and returns Element loc. * * @returns {Object} */ }, { key: 'loc', get: function get() { var prevToken = this.previousToken; 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.newlineCount; prevToken = prevToken.previousToken; } break; } prevToken = prevToken.previousToken; } var elementLines = this.sourceCodeLines; 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: 'sourceCodeLength', get: function get() { var length = 0; var child = this._firstChild; while (child) { length += child.sourceCodeLength; child = child._nextSibling; } return length; } /** * Generated source code. * * @returns {String} */ }, { key: 'sourceCode', get: function get() { var code = ''; var child = this._firstChild; while (child) { code += child.sourceCode; child = child._nextSibling; } return code; } /** * Generated source code lines. * * @returns {String[]} */ }, { key: 'sourceCodeLines', get: function get() { return (0, _utilsLines.getLines)(this.sourceCode); } /** * Generated source code line break count. * * @returns {Number} */ }, { key: 'newlineCount', get: function get() { var count = 0; var child = this._firstChild; while (child) { count += child.newlineCount; child = child._nextSibling; } return count; } }]); return Element; })(); exports['default'] = Element; var ConcreteElement = (function (_Element) { _inherits(ConcreteElement, _Element); function ConcreteElement(children) { _classCallCheck(this, ConcreteElement); _get(Object.getPrototypeOf(ConcreteElement.prototype), 'constructor', this).call(this, 'ConcreteElement', children); } return ConcreteElement; })(Element); module.exports = exports['default'];