index.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. 'use strict';
  2. module.exports = walkAST;
  3. function walkAST(ast, before, after, options) {
  4. if (after && typeof after === 'object' && typeof options === 'undefined') {
  5. options = after;
  6. after = null;
  7. }
  8. options = options || {includeDependencies: false};
  9. var parents = options.parents = options.parents || [];
  10. var replace = function replace(replacement) {
  11. if (Array.isArray(replacement) && !replace.arrayAllowed) {
  12. throw new Error('replace() can only be called with an array if the last parent is a Block or NamedBlock');
  13. }
  14. ast = replacement;
  15. };
  16. replace.arrayAllowed = parents[0] && (
  17. /^(Named)?Block$/.test(parents[0].type) ||
  18. parents[0].type === 'RawInclude' && ast.type === 'IncludeFilter');
  19. if (before) {
  20. var result = before(ast, replace);
  21. if (result === false) {
  22. return ast;
  23. } else if (Array.isArray(ast)) {
  24. // return right here to skip after() call on array
  25. return walkAndMergeNodes(ast);
  26. }
  27. }
  28. parents.unshift(ast);
  29. switch (ast.type) {
  30. case 'NamedBlock':
  31. case 'Block':
  32. ast.nodes = walkAndMergeNodes(ast.nodes);
  33. break;
  34. case 'Case':
  35. case 'Filter':
  36. case 'Mixin':
  37. case 'Tag':
  38. case 'InterpolatedTag':
  39. case 'When':
  40. case 'Code':
  41. case 'While':
  42. if (ast.block) {
  43. ast.block = walkAST(ast.block, before, after, options);
  44. }
  45. break;
  46. case 'Each':
  47. if (ast.block) {
  48. ast.block = walkAST(ast.block, before, after, options);
  49. }
  50. if (ast.alternate) {
  51. ast.alternate = walkAST(ast.alternate, before, after, options);
  52. }
  53. break;
  54. case 'Conditional':
  55. if (ast.consequent) {
  56. ast.consequent = walkAST(ast.consequent, before, after, options);
  57. }
  58. if (ast.alternate) {
  59. ast.alternate = walkAST(ast.alternate, before, after, options);
  60. }
  61. break;
  62. case 'Include':
  63. walkAST(ast.block, before, after, options);
  64. walkAST(ast.file, before, after, options);
  65. break;
  66. case 'Extends':
  67. walkAST(ast.file, before, after, options);
  68. break;
  69. case 'RawInclude':
  70. ast.filters = walkAndMergeNodes(ast.filters);
  71. walkAST(ast.file, before, after, options);
  72. break;
  73. case 'Attrs':
  74. case 'BlockComment':
  75. case 'Comment':
  76. case 'Doctype':
  77. case 'IncludeFilter':
  78. case 'MixinBlock':
  79. case 'YieldBlock':
  80. case 'Text':
  81. break;
  82. case 'FileReference':
  83. if (options.includeDependencies && ast.ast) {
  84. walkAST(ast.ast, before, after, options);
  85. }
  86. break;
  87. default:
  88. throw new Error('Unexpected node type ' + ast.type);
  89. break;
  90. }
  91. parents.shift();
  92. after && after(ast, replace);
  93. return ast;
  94. function walkAndMergeNodes(nodes) {
  95. return nodes.reduce(function (nodes, node) {
  96. var result = walkAST(node, before, after, options);
  97. if (Array.isArray(result)) {
  98. return nodes.concat(result);
  99. } else {
  100. return nodes.concat([result]);
  101. }
  102. }, []);
  103. }
  104. }