1import Exception from '../exception'; 2 3function Visitor() { 4 this.parents = []; 5} 6 7Visitor.prototype = { 8 constructor: Visitor, 9 mutating: false, 10 11 // Visits a given value. If mutating, will replace the value if necessary. 12 acceptKey: function(node, name) { 13 let value = this.accept(node[name]); 14 if (this.mutating) { 15 // Hacky sanity check: This may have a few false positives for type for the helper 16 // methods but will generally do the right thing without a lot of overhead. 17 if (value && !Visitor.prototype[value.type]) { 18 throw new Exception( 19 'Unexpected node type "' + 20 value.type + 21 '" found when accepting ' + 22 name + 23 ' on ' + 24 node.type 25 ); 26 } 27 node[name] = value; 28 } 29 }, 30 31 // Performs an accept operation with added sanity check to ensure 32 // required keys are not removed. 33 acceptRequired: function(node, name) { 34 this.acceptKey(node, name); 35 36 if (!node[name]) { 37 throw new Exception(node.type + ' requires ' + name); 38 } 39 }, 40 41 // Traverses a given array. If mutating, empty respnses will be removed 42 // for child elements. 43 acceptArray: function(array) { 44 for (let i = 0, l = array.length; i < l; i++) { 45 this.acceptKey(array, i); 46 47 if (!array[i]) { 48 array.splice(i, 1); 49 i--; 50 l--; 51 } 52 } 53 }, 54 55 accept: function(object) { 56 if (!object) { 57 return; 58 } 59 60 /* istanbul ignore next: Sanity code */ 61 if (!this[object.type]) { 62 throw new Exception('Unknown type: ' + object.type, object); 63 } 64 65 if (this.current) { 66 this.parents.unshift(this.current); 67 } 68 this.current = object; 69 70 let ret = this[object.type](object); 71 72 this.current = this.parents.shift(); 73 74 if (!this.mutating || ret) { 75 return ret; 76 } else if (ret !== false) { 77 return object; 78 } 79 }, 80 81 Program: function(program) { 82 this.acceptArray(program.body); 83 }, 84 85 MustacheStatement: visitSubExpression, 86 Decorator: visitSubExpression, 87 88 BlockStatement: visitBlock, 89 DecoratorBlock: visitBlock, 90 91 PartialStatement: visitPartial, 92 PartialBlockStatement: function(partial) { 93 visitPartial.call(this, partial); 94 95 this.acceptKey(partial, 'program'); 96 }, 97 98 ContentStatement: function(/* content */) {}, 99 CommentStatement: function(/* comment */) {}, 100 101 SubExpression: visitSubExpression, 102 103 PathExpression: function(/* path */) {}, 104 105 StringLiteral: function(/* string */) {}, 106 NumberLiteral: function(/* number */) {}, 107 BooleanLiteral: function(/* bool */) {}, 108 UndefinedLiteral: function(/* literal */) {}, 109 NullLiteral: function(/* literal */) {}, 110 111 Hash: function(hash) { 112 this.acceptArray(hash.pairs); 113 }, 114 HashPair: function(pair) { 115 this.acceptRequired(pair, 'value'); 116 } 117}; 118 119function visitSubExpression(mustache) { 120 this.acceptRequired(mustache, 'path'); 121 this.acceptArray(mustache.params); 122 this.acceptKey(mustache, 'hash'); 123} 124function visitBlock(block) { 125 visitSubExpression.call(this, block); 126 127 this.acceptKey(block, 'program'); 128 this.acceptKey(block, 'inverse'); 129} 130function visitPartial(partial) { 131 this.acceptRequired(partial, 'name'); 132 this.acceptArray(partial.params); 133 this.acceptKey(partial, 'hash'); 134} 135 136export default Visitor; 137