You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

262 lines
6.9 KiB

4 years ago
  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 = config.fields.slice();
  66. var contextName = config.context;
  67. var useContext = typeof contextName === 'string';
  68. if (reverse) {
  69. fields.reverse();
  70. }
  71. return function(node, context, walk) {
  72. var prevContextValue;
  73. if (useContext) {
  74. prevContextValue = context[contextName];
  75. context[contextName] = node;
  76. }
  77. for (var i = 0; i < fields.length; i++) {
  78. var field = fields[i];
  79. var ref = node[field.name];
  80. if (!field.nullable || ref) {
  81. if (field.type === 'list') {
  82. if (reverse) {
  83. ref.forEachRight(walk);
  84. } else {
  85. ref.forEach(walk);
  86. }
  87. } else {
  88. walk(ref);
  89. }
  90. }
  91. }
  92. if (useContext) {
  93. context[contextName] = prevContextValue;
  94. }
  95. };
  96. }
  97. function createFastTraveralMap(iterators) {
  98. return {
  99. Atrule: {
  100. StyleSheet: iterators.StyleSheet,
  101. Atrule: iterators.Atrule,
  102. Rule: iterators.Rule,
  103. Block: iterators.Block
  104. },
  105. Rule: {
  106. StyleSheet: iterators.StyleSheet,
  107. Atrule: iterators.Atrule,
  108. Rule: iterators.Rule,
  109. Block: iterators.Block
  110. },
  111. Declaration: {
  112. StyleSheet: iterators.StyleSheet,
  113. Atrule: iterators.Atrule,
  114. Rule: iterators.Rule,
  115. Block: iterators.Block
  116. }
  117. };
  118. }
  119. module.exports = function createWalker(config) {
  120. var types = getTypesFromConfig(config);
  121. var iteratorsNatural = {};
  122. var iteratorsReverse = {};
  123. for (var name in types) {
  124. if (hasOwnProperty.call(types, name) && types[name] !== null) {
  125. iteratorsNatural[name] = createTypeIterator(types[name], false);
  126. iteratorsReverse[name] = createTypeIterator(types[name], true);
  127. }
  128. }
  129. var fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural);
  130. var fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse);
  131. var walk = function(root, options) {
  132. function walkNode(node, item, list) {
  133. enter.call(context, node, item, list);
  134. if (iterators.hasOwnProperty(node.type)) {
  135. iterators[node.type](node, context, walkNode);
  136. }
  137. leave.call(context, node, item, list);
  138. }
  139. var enter = noop;
  140. var leave = noop;
  141. var iterators = iteratorsNatural;
  142. var context = {
  143. root: root,
  144. stylesheet: null,
  145. atrule: null,
  146. atrulePrelude: null,
  147. rule: null,
  148. selector: null,
  149. block: null,
  150. declaration: null,
  151. function: null
  152. };
  153. if (typeof options === 'function') {
  154. enter = options;
  155. } else if (options) {
  156. enter = ensureFunction(options.enter);
  157. leave = ensureFunction(options.leave);
  158. if (options.reverse) {
  159. iterators = iteratorsReverse;
  160. }
  161. if (options.visit) {
  162. if (fastTraversalIteratorsNatural.hasOwnProperty(options.visit)) {
  163. iterators = options.reverse
  164. ? fastTraversalIteratorsReverse[options.visit]
  165. : fastTraversalIteratorsNatural[options.visit];
  166. } else if (!types.hasOwnProperty(options.visit)) {
  167. throw new Error('Bad value `' + options.visit + '` for `visit` option (should be: ' + Object.keys(types).join(', ') + ')');
  168. }
  169. enter = invokeForType(enter, options.visit);
  170. leave = invokeForType(leave, options.visit);
  171. }
  172. }
  173. if (enter === noop && leave === noop) {
  174. throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\'t a function');
  175. }
  176. // swap handlers in reverse mode to invert visit order
  177. if (options.reverse) {
  178. var tmp = enter;
  179. enter = leave;
  180. leave = tmp;
  181. }
  182. walkNode(root);
  183. };
  184. walk.find = function(ast, fn) {
  185. var found = null;
  186. walk(ast, function(node, item, list) {
  187. if (found === null && fn.call(this, node, item, list)) {
  188. found = node;
  189. }
  190. });
  191. return found;
  192. };
  193. walk.findLast = function(ast, fn) {
  194. var found = null;
  195. walk(ast, {
  196. reverse: true,
  197. enter: function(node, item, list) {
  198. if (found === null && fn.call(this, node, item, list)) {
  199. found = node;
  200. }
  201. }
  202. });
  203. return found;
  204. };
  205. walk.findAll = function(ast, fn) {
  206. var found = [];
  207. walk(ast, function(node, item, list) {
  208. if (fn.call(this, node, item, list)) {
  209. found.push(node);
  210. }
  211. });
  212. return found;
  213. };
  214. return walk;
  215. };