visitor.js 3.2 KB

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