create.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. 'use strict';
  2. var hasOwnProperty = Object.prototype.hasOwnProperty;
  3. var noop = function() {};
  4. function ensureFunction(value) {
  5. return typeof value === 'function' ? value : noop;
  6. }
  7. function invokeForType(fn, type) {
  8. return function(node, item, list) {
  9. if (node.type === type) {
  10. fn.call(this, node, item, list);
  11. }
  12. };
  13. }
  14. function getWalkersFromStructure(name, nodeType) {
  15. var structure = nodeType.structure;
  16. var walkers = [];
  17. for (var key in structure) {
  18. if (hasOwnProperty.call(structure, key) === false) {
  19. continue;
  20. }
  21. var fieldTypes = structure[key];
  22. var walker = {
  23. name: key,
  24. type: false,
  25. nullable: false
  26. };
  27. if (!Array.isArray(structure[key])) {
  28. fieldTypes = [structure[key]];
  29. }
  30. for (var i = 0; i < fieldTypes.length; i++) {
  31. var fieldType = fieldTypes[i];
  32. if (fieldType === null) {
  33. walker.nullable = true;
  34. } else if (typeof fieldType === 'string') {
  35. walker.type = 'node';
  36. } else if (Array.isArray(fieldType)) {
  37. walker.type = 'list';
  38. }
  39. }
  40. if (walker.type) {
  41. walkers.push(walker);
  42. }
  43. }
  44. if (walkers.length) {
  45. return {
  46. context: nodeType.walkContext,
  47. fields: walkers
  48. };
  49. }
  50. return null;
  51. }
  52. function getTypesFromConfig(config) {
  53. var types = {};
  54. for (var name in config.node) {
  55. if (hasOwnProperty.call(config.node, name)) {
  56. var nodeType = config.node[name];
  57. if (!nodeType.structure) {
  58. throw new Error('Missed `structure` field in `' + name + '` node type definition');
  59. }
  60. types[name] = getWalkersFromStructure(name, nodeType);
  61. }
  62. }
  63. return types;
  64. }
  65. function createTypeIterator(config, reverse) {
  66. var fields = reverse ? config.fields.slice().reverse() : config.fields;
  67. var body = fields.map(function(field) {
  68. var ref = 'node.' + field.name;
  69. var line;
  70. if (field.type === 'list') {
  71. line = reverse
  72. ? ref + '.forEachRight(walk);'
  73. : ref + '.forEach(walk);';
  74. } else {
  75. line = 'walk(' + ref + ');';
  76. }
  77. if (field.nullable) {
  78. line = 'if (' + ref + ') {\n ' + line + '}';
  79. }
  80. return line;
  81. });
  82. if (config.context) {
  83. body = [].concat(
  84. 'var old = context.' + config.context + ';',
  85. 'context.' + config.context + ' = node;',
  86. body,
  87. 'context.' + config.context + ' = old;'
  88. );
  89. }
  90. return new Function('node', 'context', 'walk', body.join('\n'));
  91. }
  92. function createFastTraveralMap(iterators) {
  93. return {
  94. Atrule: {
  95. StyleSheet: iterators.StyleSheet,
  96. Atrule: iterators.Atrule,
  97. Rule: iterators.Rule,
  98. Block: iterators.Block
  99. },
  100. Rule: {
  101. StyleSheet: iterators.StyleSheet,
  102. Atrule: iterators.Atrule,
  103. Rule: iterators.Rule,
  104. Block: iterators.Block
  105. },
  106. Declaration: {
  107. StyleSheet: iterators.StyleSheet,
  108. Atrule: iterators.Atrule,
  109. Rule: iterators.Rule,
  110. Block: iterators.Block
  111. }
  112. };
  113. }
  114. module.exports = function createWalker(config) {
  115. var types = getTypesFromConfig(config);
  116. var iteratorsNatural = {};
  117. var iteratorsReverse = {};
  118. for (var name in types) {
  119. if (hasOwnProperty.call(types, name) && types[name] !== null) {
  120. iteratorsNatural[name] = createTypeIterator(types[name], false);
  121. iteratorsReverse[name] = createTypeIterator(types[name], true);
  122. }
  123. }
  124. var fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural);
  125. var fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse);
  126. return function walk(root, options) {
  127. function walkNode(node, item, list) {
  128. enter.call(context, node, item, list);
  129. if (iterators.hasOwnProperty(node.type)) {
  130. iterators[node.type](node, context, walkNode);
  131. }
  132. leave.call(context, node, item, list);
  133. }
  134. var enter = noop;
  135. var leave = noop;
  136. var iterators = iteratorsNatural;
  137. var context = {
  138. root: root,
  139. stylesheet: null,
  140. atrule: null,
  141. atrulePrelude: null,
  142. rule: null,
  143. selector: null,
  144. block: null,
  145. declaration: null,
  146. function: null
  147. };
  148. if (typeof options === 'function') {
  149. enter = options;
  150. } else if (options) {
  151. enter = ensureFunction(options.enter);
  152. leave = ensureFunction(options.leave);
  153. if (options.reverse) {
  154. iterators = iteratorsReverse;
  155. }
  156. if (options.visit) {
  157. if (fastTraversalIteratorsNatural.hasOwnProperty(options.visit)) {
  158. iterators = options.reverse
  159. ? fastTraversalIteratorsReverse[options.visit]
  160. : fastTraversalIteratorsNatural[options.visit];
  161. } else if (!types.hasOwnProperty(options.visit)) {
  162. throw new Error('Bad value `' + options.visit + '` for `visit` option (should be: ' + Object.keys(types).join(', ') + ')');
  163. }
  164. enter = invokeForType(enter, options.visit);
  165. leave = invokeForType(leave, options.visit);
  166. }
  167. }
  168. if (enter === noop && leave === noop) {
  169. throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\'t a function');
  170. }
  171. // swap handlers in reverse mode to invert visit order
  172. if (options.reverse) {
  173. var tmp = enter;
  174. enter = leave;
  175. leave = tmp;
  176. }
  177. walkNode(root);
  178. };
  179. };