create.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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, walkReducer) {
  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. var breakWalk = reverse
  83. ? ref.reduceRight(walkReducer, false)
  84. : ref.reduce(walkReducer, false);
  85. if (breakWalk) {
  86. return true;
  87. }
  88. } else if (walk(ref)) {
  89. return true;
  90. }
  91. }
  92. }
  93. if (useContext) {
  94. context[contextName] = prevContextValue;
  95. }
  96. };
  97. }
  98. function createFastTraveralMap(iterators) {
  99. return {
  100. Atrule: {
  101. StyleSheet: iterators.StyleSheet,
  102. Atrule: iterators.Atrule,
  103. Rule: iterators.Rule,
  104. Block: iterators.Block
  105. },
  106. Rule: {
  107. StyleSheet: iterators.StyleSheet,
  108. Atrule: iterators.Atrule,
  109. Rule: iterators.Rule,
  110. Block: iterators.Block
  111. },
  112. Declaration: {
  113. StyleSheet: iterators.StyleSheet,
  114. Atrule: iterators.Atrule,
  115. Rule: iterators.Rule,
  116. Block: iterators.Block,
  117. DeclarationList: iterators.DeclarationList
  118. }
  119. };
  120. }
  121. module.exports = function createWalker(config) {
  122. var types = getTypesFromConfig(config);
  123. var iteratorsNatural = {};
  124. var iteratorsReverse = {};
  125. var breakWalk = Symbol('break-walk');
  126. var skipNode = Symbol('skip-node');
  127. for (var name in types) {
  128. if (hasOwnProperty.call(types, name) && types[name] !== null) {
  129. iteratorsNatural[name] = createTypeIterator(types[name], false);
  130. iteratorsReverse[name] = createTypeIterator(types[name], true);
  131. }
  132. }
  133. var fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural);
  134. var fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse);
  135. var walk = function(root, options) {
  136. function walkNode(node, item, list) {
  137. var enterRet = enter.call(context, node, item, list);
  138. if (enterRet === breakWalk) {
  139. debugger;
  140. return true;
  141. }
  142. if (enterRet === skipNode) {
  143. return false;
  144. }
  145. if (iterators.hasOwnProperty(node.type)) {
  146. if (iterators[node.type](node, context, walkNode, walkReducer)) {
  147. return true;
  148. }
  149. }
  150. if (leave.call(context, node, item, list) === breakWalk) {
  151. return true;
  152. }
  153. return false;
  154. }
  155. var walkReducer = (ret, data, item, list) => ret || walkNode(data, item, list);
  156. var enter = noop;
  157. var leave = noop;
  158. var iterators = iteratorsNatural;
  159. var context = {
  160. break: breakWalk,
  161. skip: skipNode,
  162. root: root,
  163. stylesheet: null,
  164. atrule: null,
  165. atrulePrelude: null,
  166. rule: null,
  167. selector: null,
  168. block: null,
  169. declaration: null,
  170. function: null
  171. };
  172. if (typeof options === 'function') {
  173. enter = options;
  174. } else if (options) {
  175. enter = ensureFunction(options.enter);
  176. leave = ensureFunction(options.leave);
  177. if (options.reverse) {
  178. iterators = iteratorsReverse;
  179. }
  180. if (options.visit) {
  181. if (fastTraversalIteratorsNatural.hasOwnProperty(options.visit)) {
  182. iterators = options.reverse
  183. ? fastTraversalIteratorsReverse[options.visit]
  184. : fastTraversalIteratorsNatural[options.visit];
  185. } else if (!types.hasOwnProperty(options.visit)) {
  186. throw new Error('Bad value `' + options.visit + '` for `visit` option (should be: ' + Object.keys(types).join(', ') + ')');
  187. }
  188. enter = invokeForType(enter, options.visit);
  189. leave = invokeForType(leave, options.visit);
  190. }
  191. }
  192. if (enter === noop && leave === noop) {
  193. throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\'t a function');
  194. }
  195. walkNode(root);
  196. };
  197. walk.break = breakWalk;
  198. walk.skip = skipNode;
  199. walk.find = function(ast, fn) {
  200. var found = null;
  201. walk(ast, function(node, item, list) {
  202. if (fn.call(this, node, item, list)) {
  203. found = node;
  204. return breakWalk;
  205. }
  206. });
  207. return found;
  208. };
  209. walk.findLast = function(ast, fn) {
  210. var found = null;
  211. walk(ast, {
  212. reverse: true,
  213. enter: function(node, item, list) {
  214. if (fn.call(this, node, item, list)) {
  215. found = node;
  216. return breakWalk;
  217. }
  218. }
  219. });
  220. return found;
  221. };
  222. walk.findAll = function(ast, fn) {
  223. var found = [];
  224. walk(ast, function(node, item, list) {
  225. if (fn.call(this, node, item, list)) {
  226. found.push(node);
  227. }
  228. });
  229. return found;
  230. };
  231. return walk;
  232. };