create.js 6.0 KB

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