create.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. const { hasOwnProperty } = Object.prototype;
  2. const 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. const structure = nodeType.structure;
  15. const walkers = [];
  16. for (const key in structure) {
  17. if (hasOwnProperty.call(structure, key) === false) {
  18. continue;
  19. }
  20. let fieldTypes = structure[key];
  21. const walker = {
  22. name: key,
  23. type: false,
  24. nullable: false
  25. };
  26. if (!Array.isArray(fieldTypes)) {
  27. fieldTypes = [fieldTypes];
  28. }
  29. for (const fieldType of fieldTypes) {
  30. if (fieldType === null) {
  31. walker.nullable = true;
  32. } else if (typeof fieldType === 'string') {
  33. walker.type = 'node';
  34. } else if (Array.isArray(fieldType)) {
  35. walker.type = 'list';
  36. }
  37. }
  38. if (walker.type) {
  39. walkers.push(walker);
  40. }
  41. }
  42. if (walkers.length) {
  43. return {
  44. context: nodeType.walkContext,
  45. fields: walkers
  46. };
  47. }
  48. return null;
  49. }
  50. function getTypesFromConfig(config) {
  51. const types = {};
  52. for (const name in config.node) {
  53. if (hasOwnProperty.call(config.node, name)) {
  54. const nodeType = config.node[name];
  55. if (!nodeType.structure) {
  56. throw new Error('Missed `structure` field in `' + name + '` node type definition');
  57. }
  58. types[name] = getWalkersFromStructure(name, nodeType);
  59. }
  60. }
  61. return types;
  62. }
  63. function createTypeIterator(config, reverse) {
  64. const fields = config.fields.slice();
  65. const contextName = config.context;
  66. const useContext = typeof contextName === 'string';
  67. if (reverse) {
  68. fields.reverse();
  69. }
  70. return function(node, context, walk, walkReducer) {
  71. let prevContextValue;
  72. if (useContext) {
  73. prevContextValue = context[contextName];
  74. context[contextName] = node;
  75. }
  76. for (const field of fields) {
  77. const ref = node[field.name];
  78. if (!field.nullable || ref) {
  79. if (field.type === 'list') {
  80. const breakWalk = reverse
  81. ? ref.reduceRight(walkReducer, false)
  82. : ref.reduce(walkReducer, false);
  83. if (breakWalk) {
  84. return true;
  85. }
  86. } else if (walk(ref)) {
  87. return true;
  88. }
  89. }
  90. }
  91. if (useContext) {
  92. context[contextName] = prevContextValue;
  93. }
  94. };
  95. }
  96. function createFastTraveralMap({
  97. StyleSheet,
  98. Atrule,
  99. Rule,
  100. Block,
  101. DeclarationList
  102. }) {
  103. return {
  104. Atrule: {
  105. StyleSheet,
  106. Atrule,
  107. Rule,
  108. Block
  109. },
  110. Rule: {
  111. StyleSheet,
  112. Atrule,
  113. Rule,
  114. Block
  115. },
  116. Declaration: {
  117. StyleSheet,
  118. Atrule,
  119. Rule,
  120. Block,
  121. DeclarationList
  122. }
  123. };
  124. }
  125. export function createWalker(config) {
  126. const types = getTypesFromConfig(config);
  127. const iteratorsNatural = {};
  128. const iteratorsReverse = {};
  129. const breakWalk = Symbol('break-walk');
  130. const skipNode = Symbol('skip-node');
  131. for (const name in types) {
  132. if (hasOwnProperty.call(types, name) && types[name] !== null) {
  133. iteratorsNatural[name] = createTypeIterator(types[name], false);
  134. iteratorsReverse[name] = createTypeIterator(types[name], true);
  135. }
  136. }
  137. const fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural);
  138. const fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse);
  139. const walk = function(root, options) {
  140. function walkNode(node, item, list) {
  141. const enterRet = enter.call(context, node, item, list);
  142. if (enterRet === breakWalk) {
  143. return true;
  144. }
  145. if (enterRet === skipNode) {
  146. return false;
  147. }
  148. if (iterators.hasOwnProperty(node.type)) {
  149. if (iterators[node.type](node, context, walkNode, walkReducer)) {
  150. return true;
  151. }
  152. }
  153. if (leave.call(context, node, item, list) === breakWalk) {
  154. return true;
  155. }
  156. return false;
  157. }
  158. let enter = noop;
  159. let leave = noop;
  160. let iterators = iteratorsNatural;
  161. let walkReducer = (ret, data, item, list) => ret || walkNode(data, item, list);
  162. const context = {
  163. break: breakWalk,
  164. skip: skipNode,
  165. root,
  166. stylesheet: null,
  167. atrule: null,
  168. atrulePrelude: null,
  169. rule: null,
  170. selector: null,
  171. block: null,
  172. declaration: null,
  173. function: null
  174. };
  175. if (typeof options === 'function') {
  176. enter = options;
  177. } else if (options) {
  178. enter = ensureFunction(options.enter);
  179. leave = ensureFunction(options.leave);
  180. if (options.reverse) {
  181. iterators = iteratorsReverse;
  182. }
  183. if (options.visit) {
  184. if (fastTraversalIteratorsNatural.hasOwnProperty(options.visit)) {
  185. iterators = options.reverse
  186. ? fastTraversalIteratorsReverse[options.visit]
  187. : fastTraversalIteratorsNatural[options.visit];
  188. } else if (!types.hasOwnProperty(options.visit)) {
  189. throw new Error('Bad value `' + options.visit + '` for `visit` option (should be: ' + Object.keys(types).sort().join(', ') + ')');
  190. }
  191. enter = invokeForType(enter, options.visit);
  192. leave = invokeForType(leave, options.visit);
  193. }
  194. }
  195. if (enter === noop && leave === noop) {
  196. throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\'t a function');
  197. }
  198. walkNode(root);
  199. };
  200. walk.break = breakWalk;
  201. walk.skip = skipNode;
  202. walk.find = function(ast, fn) {
  203. let found = null;
  204. walk(ast, function(node, item, list) {
  205. if (fn.call(this, node, item, list)) {
  206. found = node;
  207. return breakWalk;
  208. }
  209. });
  210. return found;
  211. };
  212. walk.findLast = function(ast, fn) {
  213. let found = null;
  214. walk(ast, {
  215. reverse: true,
  216. enter(node, item, list) {
  217. if (fn.call(this, node, item, list)) {
  218. found = node;
  219. return breakWalk;
  220. }
  221. }
  222. });
  223. return found;
  224. };
  225. walk.findAll = function(ast, fn) {
  226. const found = [];
  227. walk(ast, function(node, item, list) {
  228. if (fn.call(this, node, item, list)) {
  229. found.push(node);
  230. }
  231. });
  232. return found;
  233. };
  234. return walk;
  235. };