makestatic-core
Version:
Generic file processing library
193 lines (180 loc) • 4.62 kB
JavaScript
/**
* Abstraction for abstract syntax tree parsers and serializers that
* provides a common API for interacting with abstract syntax trees.
*
* This implementation provides a mechanism for lazily converting buffers to
* strings when the tree is parsed and lazily parsing when the `result`
* property is accessed.
*
* It also allows for trees to be marked as dirty, if a tree has not been
* marked as dirty and `serialize` is called the original content is returned.
*
* @class TreeAdapter
*/
class TreeAdapter {
/**
* Create a TreeAdapter.
*
* All constructor parameters are required.
*
* @constructor TreeAdapter
* @param {Function} parse a closure that parses to an AST.
* @param {Function} serialize a closure that serializes an AST
* @param {Function} iterator a closure that iterates the AST
* @param {String|Buffer} content the content to parse.
* @param {Function} [clone] called when this AST adapter is cloned.
*/
constructor (parse, serialize, iterator, content, clone) {
this._dirty = false
this._parse = parse
this._serialize = serialize
this._iterator = iterator
this._content = content
this._clone = clone
}
/**
* Calls the underlying AST parse closure and sets the result on
* this adapter.
*
* @function parse
* @member TreeAdapter
*
* @returns this tree adapter.
*/
parse (...args) {
this._result = this._parse(this, this.content, ...args)
return this
}
/**
* Calls the underlying AST serialize closure.
*
* @function serialize
* @member TreeAdapter
*
* @returns the serialize closure return value.
*/
serialize (...args) {
return this._serialize(this, this.result, ...args)
}
/**
* Gets a copy of this tree adapter sharing the same parse, serialize
* iterator and clone closures.
*
* If the consumer passed a `clone` function to the constructor it is called
* with the cloned tree adapter so that the consumer can decorate the new
* tree if necessary.
*
* @function clone
* @member TreeAdapter
* @param {String} [content] content for the new tree adapter.
*
* @returns a new tree adapter.
*/
clone (content) {
const copy = new TreeAdapter(
this._parse,
this._serialize,
this._iterator,
content || this._content,
this._clone)
if (this._clone) {
this._clone(copy)
}
return copy
}
/**
* Gets a file seal closure for this adapter.
*
* If this AST is not dirty the original content is returned.
*
* @function getSeal
* @member TreeAdapter
*
* @returns a seal closure.
*/
getSeal () {
return () => {
if (!this.dirty) {
return this.content
}
return this.serialize()
}
}
/**
* Calls a function passing the result AST and flags this tree as dirty.
*
* If the passed function does not return a value it is assumed the tree is
* dirty otherwise the return value is coerced to a boolean and set as the
* dirty flag.
*
* @function modify
* @member TreeAdapter
* @param {Function} fn the tree modifier function.
*
* @returns this tree adapter.
*/
modify (fn) {
const res = fn(this.result)
this._dirty = (res === undefined) || Boolean(res)
return this
}
/**
* Walk the abstract syntax tree nodes.
*
* @function walk
* @member TreeAdapter
* @param {Function} predicate test if a node should be visited.
* @param {Function} fn visit a node.
*
* @returns this tree adapter.
*/
walk (predicate, fn) {
const it = this._iterator
it(this, this.result, (node, index) => {
if (predicate(node, index)) {
return fn(node, index)
}
}, it)
return this
}
/**
* Source file content.
*
* @property {Buffer|String} content
* @member TreeAdapter
* @readonly
*/
get content () {
if (Buffer.isBuffer(this._content)) {
this._content = this._content.toString()
}
return this._content
}
/**
* A parse result object.
*
* @property {Object} result
* @member TreeAdapter
* @readonly
*/
get result () {
// lazily parse when we need a result object
if (this._result === undefined) {
this.parse()
}
return this._result
}
/**
* Determines if the tree is dirty.
*
* @property {Boolean} dirty
* @member TreeAdapter
*/
get dirty () {
return this._dirty
}
set dirty (val) {
this._dirty = val
}
}
module.exports = TreeAdapter