All files / transform traverser.js

69.7% Statements 23/33
63.16% Branches 12/19
60% Functions 3/5
68.97% Lines 20/29
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148                                                              6x                                                     66x 28x     16x     16x     16x   38x 38x   38x 26x 54x   54x   54x   54x   54x                                       60x   60x 60x                   60x     60x                                                        
import Node from '../parser/nodes/node';
 
/**
 * Traverses the AST. The backbone of transformers and other times. If you ever
 * need to climb the AST of unknown types, use this. Do note that if you're
 * dealing with a transformation, that does already have a `Transformer` (which
 * is a subclass of this), so you might not need a subclass of this. 
 * 
 * Subclassing is easy, check the API, but the most likely method you want to 
 * override is `receivedNode` which gets the parent, and name, you can get the
 * node then using `parent[name]`. Both values are passed as if the node is
 * changed you can still obtain a reference to it. Again, it's reccomended to
 * try to use `ASTTool` and modify your transformations before creating a
 * traverser objects as the queue generation is blocking and GC iterations can
 * be slow as they need to iterate yet again.
 * 
 * @abstract
 */
export default class Traverser {
    
    /**
     * Instantiates a traverser object. Note: This must be subclassed to be
     * used. See {@link Transformer} or {@link ASTGarbageCollector} for more
     * information.
     * 
     * @param {boolean} shouldProcess - Whether or not the node should be setup
     *     with special data. (Note: if false, only assume the raw node will be
     *     passed.)
     */
    constructor(shouldProcess: boolean = true) {
        /** @private */
        this.shouldProcess = shouldProcess;
    }
    
    /**
     * Queues an AST to be traversed, this in turn calls the abstract function
     * `#receivedNode(parent:name:)` which can be used to determine what to do
     * with the node. The AST is recursed in order.
     * 
     * This method runs top-down in a deterministic order and is not specifically
     * run async. If it is, you can safely assume that the order of execution
     * will be the same and for every `#processsedNode` there will be an equiv.
     * `#finishedNode` call.
     * 
     * This method handles strings and arrays but behavior can be overloaded to
     * add the ability to handle nodes of a non-conforming type to `Node` or a
     * `Node[]`. An example of such would be:
     * 
     *     /** @override *\/
     *     queue(ast: any) {
     *         if (ast instanceof MyClass) handle(ast);
     *         else super.queue(ast);
     *     }
     * 
     * @param {any} ast - An AST as outputted by a `Parser`
     */
    queue(ast: any) {
        // Recursively add all AST nodes in an array
         if (ast instanceof Array) {
            for (let i = 0; i < ast.length; i++) {
                // If it's a node array. Then we also want to queue itself and queue
                // the node itself so its children will be added.
                this.processNode(ast, i);
                
                // Requeue the further children
                this.queue(ast[i], ast);
                
                // Notify that the node is finished if defined
                this.finishedNode(ast, i);
            }
         } else Eif (ast instanceof Node) {
             let children = ast.children, name, child;
             
             if (children) {
                for (let i = 0; i < children.length; i++) {
                    name = children[i];
                    
                    this.processNode(ast, name);
                    
                    child = ast[ name ];
                    
                    if (child != null) this.queue(child, ast);
                    
                    this.finishedNode(ast, name);
                }
             }
         } else {
             if (process.env["VSL_ENV"] != "dev_debug") console.log(ast);
             throw new TypeError(`Unexpected AST node: ${ast} of type ${ast.constructor.name}`);
         }
    }
    
    /**
     * Override this method if you'd like to process each node that comes
     * through. Don't forget to call `super.processNode`! You don't have to but
     * you probably want to.
     * 
     * @param {Node | Node[]} parent - The parent node of the given node being
     *     processed
     * @param {string} name - The name of the current node being processed
     * @protected
     */
    processNode(parent: any, name: string) {
        let node = parent[name];
        
        Eif (this.shouldProcess) {
            Iif (process.env.VSL_ENV === "dev_debug") {
                console.log("-- Received Node --");
                console.log("Parent: ", parent.constructor.name);
                console.log("Name: ", name);
                console.log("Exists: ", !!(parent[name]));
                // console.log("Node: ", parent[name]);
                // console.log("Scope: ", this.scope);
                console.log("\n\n");
            }
            
            Eif (node) node.parentNode = parent;
        }
        
        if (node instanceof Node) this.receivedNode(parent, name);
    }
    
    /**
     * Called everytime the traverser encounters a node
     * 
     * @param {Node|Node[]} parent - The parent node of the given ast
     * @param {any} name - The reference to the child relative to the parent.
     * 
     * @abstract
     */
    receivedNode(parent: Node | Node[], name: string) {
        throw new TypeError(`${this.constructor.name}: Did not implement required method #receivedNode(parent:name:)`);
    }
    
    
    /**
     * _Optional_ method which is called once the traverser finishes processing
     * a node's children
     * 
     * @param {Node|Node[]} parent - The parent node of the given ast
     * @param {any} name - The reference to the child relative to the parent.
     * 
     * @abstract
     */
    finishedNode(parent: Node | Node[], name: string) {
        return;
    }
}