create.cjs 7.4 KB

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