tree.js 3.72 KB
/*
 Copyright 2012-2015, Yahoo Inc.
 Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */
'use strict';

/**
 * An object with methods that are called during the traversal of the coverage tree.
 * A visitor has the following methods that are called during tree traversal.
 *
 *   * `onStart(root, state)` - called before traversal begins
 *   * `onSummary(node, state)` - called for every summary node
 *   * `onDetail(node, state)` - called for every detail node
 *   * `onSummaryEnd(node, state)` - called after all children have been visited for
 *      a summary node.
 *   * `onEnd(root, state)` - called after traversal ends
 *
 * @param delegate - a partial visitor that only implements the methods of interest
 *  The visitor object supplies the missing methods as noops. For example, reports
 *  that only need the final coverage summary need implement `onStart` and nothing
 *  else. Reports that use only detailed coverage information need implement `onDetail`
 *  and nothing else.
 * @constructor
 */
class Visitor {
    constructor(delegate) {
        this.delegate = delegate;
    }
}

['Start', 'End', 'Summary', 'SummaryEnd', 'Detail']
    .map(k => `on${k}`)
    .forEach(fn => {
        Object.defineProperty(Visitor.prototype, fn, {
            writable: true,
            value(node, state) {
                if (typeof this.delegate[fn] === 'function') {
                    this.delegate[fn](node, state);
                }
            }
        });
    });

class CompositeVisitor extends Visitor {
    constructor(visitors) {
        super();

        if (!Array.isArray(visitors)) {
            visitors = [visitors];
        }
        this.visitors = visitors.map(v => {
            if (v instanceof Visitor) {
                return v;
            }
            return new Visitor(v);
        });
    }
}

['Start', 'Summary', 'SummaryEnd', 'Detail', 'End']
    .map(k => `on${k}`)
    .forEach(fn => {
        Object.defineProperty(CompositeVisitor.prototype, fn, {
            value(node, state) {
                this.visitors.forEach(v => {
                    v[fn](node, state);
                });
            }
        });
    });

class BaseNode {
    isRoot() {
        return !this.getParent();
    }

    /**
     * visit all nodes depth-first from this node down. Note that `onStart`
     * and `onEnd` are never called on the visitor even if the current
     * node is the root of the tree.
     * @param visitor a full visitor that is called during tree traversal
     * @param state optional state that is passed around
     */
    visit(visitor, state) {
        if (this.isSummary()) {
            visitor.onSummary(this, state);
        } else {
            visitor.onDetail(this, state);
        }

        this.getChildren().forEach(child => {
            child.visit(visitor, state);
        });

        if (this.isSummary()) {
            visitor.onSummaryEnd(this, state);
        }
    }
}

/**
 * abstract base class for a coverage tree.
 * @constructor
 */
class BaseTree {
    constructor(root) {
        this.root = root;
    }

    /**
     * returns the root node of the tree
     */
    getRoot() {
        return this.root;
    }

    /**
     * visits the tree depth-first with the supplied partial visitor
     * @param visitor - a potentially partial visitor
     * @param state - the state to be passed around during tree traversal
     */
    visit(visitor, state) {
        if (!(visitor instanceof Visitor)) {
            visitor = new Visitor(visitor);
        }
        visitor.onStart(this.getRoot(), state);
        this.getRoot().visit(visitor, state);
        visitor.onEnd(this.getRoot(), state);
    }
}

module.exports = {
    BaseTree,
    BaseNode,
    Visitor,
    CompositeVisitor
};