index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. 'use strict';
  2. /**
  3. * Create an instance of `Parser` with
  4. * the given `string`, optionally passing
  5. * a `parent` name for namespacing methods
  6. *
  7. * ```js
  8. * var parser = new Parser('function foo(a, b, c){}');
  9. * ```
  10. * @param {String} `str`
  11. * @param {String} `parent`
  12. * @api public
  13. */
  14. function Parser(str, parent) {
  15. this.string = str;
  16. this.parent = parent;
  17. this.fns = [];
  18. }
  19. /**
  20. * Convenience method for creating a property name
  21. * that is prefixed with the parent namespace, if defined.
  22. *
  23. * @param {String} `name`
  24. * @return {String}
  25. * @api public
  26. */
  27. Parser.prototype.name = function(name) {
  28. return this.parent ? (this.parent + name) : '';
  29. };
  30. /**
  31. * Register a parser to use (in addition to those already
  32. * registered as default parsers) with the given `regex` and
  33. * function.
  34. *
  35. * ```js
  36. * var parser = new Parser('function foo(a, b, c){}');
  37. * .use(/function\s*([\w$]+)\s*\(([^)]+)/, function(match) {
  38. * return {
  39. * name: match[1],
  40. * params: matc(h[2] || '').split(/[, ]/)
  41. * };
  42. * })
  43. * ```
  44. * @param {RegExp} `regex`
  45. * @param {Function} `fn`
  46. * @return {Object} The instance for chaining
  47. * @api public
  48. */
  49. Parser.prototype.use = function(regex, fn) {
  50. this.fns.push({regex: regex, fn: fn});
  51. return this;
  52. };
  53. /**
  54. * Parse the string passed to the constructor with
  55. * all registered parsers.
  56. *
  57. * @return {Object|Null}
  58. * @api public
  59. */
  60. Parser.prototype.parse = function() {
  61. this.init();
  62. var len = this.fns.length;
  63. var i = -1;
  64. while (++i < len) {
  65. var parser = this.fns[i];
  66. var re = parser.regex;
  67. var fn = parser.fn;
  68. var match = re.exec(this.string);
  69. if (match) {
  70. var ctx = fn.call(this, match, this.parent);
  71. if (ctx) {
  72. this.value = ctx;
  73. return ctx;
  74. }
  75. }
  76. }
  77. return null;
  78. };
  79. Parser.prototype.init = function() {
  80. // module.exports method
  81. this.use(/^(module\.exports)\s*=\s*function\s*\(([^)]+)/, function(m, parent) {
  82. return {
  83. type: 'method',
  84. receiver: m[1],
  85. name: '',
  86. params: (m[2] || '').split(/[, ]+/),
  87. string: m[1] + '.' + m[2] + '()',
  88. original: m.input
  89. };
  90. });
  91. this.use(/^(module\.exports)\s*=\s*function\s([\w$]+)\s*\(([^)]+)/, function(m, parent) {
  92. return {
  93. type: 'function',
  94. subtype: 'expression',
  95. receiver: m[1],
  96. name: m[2],
  97. params: (m[3] || '').split(/[, ]+/),
  98. string: m[2] + '()',
  99. original: m.input
  100. };
  101. });
  102. // class, possibly exported by name or as a default
  103. this.use(/^\s*(export(\s+default)?\s+)?class\s+([\w$]+)(\s+extends\s+([\w$.]+(?:\(.*\))?))?\s*{/, function(m, parent) {
  104. return {
  105. type: 'class',
  106. ctor: m[3],
  107. name: m[3],
  108. extends: m[5],
  109. string: 'new ' + m[3] + '()'
  110. };
  111. });
  112. // class constructor
  113. this.use(/^\s*constructor\s*\(([^)]+)/, function(m, parent) {
  114. return {
  115. type: 'constructor',
  116. ctor: this.parent,
  117. name: 'constructor',
  118. params: (m[4] || '').split(/[, ]+/),
  119. string: this.name('.prototype.') + 'constructor()'
  120. };
  121. });
  122. // class method
  123. this.use(/^\s*(static)?\s*(\*)?\s*([\w$]+|\[.*\])\s*\(([^)]+)/, function(m, parent) {
  124. return {
  125. type: 'method',
  126. ctor: this.parent,
  127. name: m[2] + m[3],
  128. params: (m[4] || '').split(/[, ]+/),
  129. string: this.name(m[1] ? '.' : '.prototype.') + m[2] + m[3] + '()'
  130. };
  131. });
  132. // named function statement, possibly exported by name or as a default
  133. this.use(/^\s*(export(\s+default)?\s+)?function\s+([\w$]+)\s*\(([^)]+)/, function(m, parent) {
  134. return {
  135. type: 'function',
  136. subtype: 'statement',
  137. name: m[3],
  138. params: (m[4] || '').split(/[, ]+/),
  139. string: m[3] + '()'
  140. };
  141. });
  142. // anonymous function expression exported as a default
  143. this.use(/^\s*export\s+default\s+function\s*\(([^)]+)/, function(m, parent) {
  144. return {
  145. type: 'function',
  146. name: m[1], // undefined
  147. params: (m[4] || '').split(/[, ]+/),
  148. string: m[1] + '()'
  149. };
  150. });
  151. // function expression
  152. this.use(/^return\s+function(?:\s+([\w$]+))?\s*\(([^)]+)/, function(m, parent) {
  153. return {
  154. type: 'function',
  155. subtype: 'expression',
  156. name: m[1],
  157. params: (m[4] || '').split(/[, ]+/),
  158. string: m[1] + '()'
  159. };
  160. });
  161. // function expression
  162. this.use(/^\s*(?:const|let|var)\s+([\w$]+)\s*=\s*function\s*\(([^)]+)/, function(m, parent) {
  163. return {
  164. type: 'function',
  165. subtype: 'expression',
  166. name: m[1],
  167. params: (m[2] || '').split(/[, ]+/),
  168. string: (m[1] || '') + '()'
  169. };
  170. });
  171. // prototype method
  172. this.use(/^\s*([\w$.]+)\s*\.\s*prototype\s*\.\s*([\w$]+)\s*=\s*function\s*\(([^)]+)/, function(m, parent) {
  173. return {
  174. type: 'prototype method',
  175. category: 'method',
  176. ctor: m[1],
  177. name: m[2],
  178. params: (m[3] || '').split(/[, ]+/),
  179. string: m[1] + '.prototype.' + m[2] + '()'
  180. };
  181. });
  182. // prototype property
  183. this.use(/^\s*([\w$.]+)\s*\.\s*prototype\s*\.\s*([\w$]+)\s*=\s*([^\n;]+)/, function(m, parent) {
  184. return {
  185. type: 'prototype property',
  186. ctor: m[1],
  187. name: m[2],
  188. value: trim(m[3]),
  189. string: m[1] + '.prototype.' + m[2]
  190. };
  191. });
  192. // prototype property without assignment
  193. this.use(/^\s*([\w$]+)\s*\.\s*prototype\s*\.\s*([\w$]+)\s*/, function(m, parent) {
  194. return {
  195. type: 'prototype property',
  196. ctor: m[1],
  197. name: m[2],
  198. string: m[1] + '.prototype.' + m[2]
  199. };
  200. });
  201. // inline prototype
  202. this.use(/^\s*([\w$.]+)\s*\.\s*prototype\s*=\s*{/, function(m, parent) {
  203. return {
  204. type: 'prototype',
  205. ctor: m[1],
  206. name: m[1],
  207. string: m[1] + '.prototype'
  208. };
  209. });
  210. // Fat arrow function
  211. this.use(/^\s*\(*\s*([\w$.]+)\s*\)*\s*=>/, function(m, parent) {
  212. return {
  213. type: 'function',
  214. ctor: this.parent,
  215. name: m[1],
  216. string: this.name('.prototype.') + m[1] + '()'
  217. };
  218. });
  219. // inline method
  220. this.use(/^\s*([\w$.]+)\s*:\s*function\s*\(([^)]+)/, function(m, parent) {
  221. return {
  222. type: 'method',
  223. ctor: this.parent,
  224. name: m[1],
  225. string: this.name('.prototype.') + m[1] + '()'
  226. };
  227. });
  228. // inline property
  229. this.use(/^\s*([\w$.]+)\s*:\s*([^\n;]+)/, function(m, parent) {
  230. return {
  231. type: 'property',
  232. ctor: this.parent,
  233. name: m[1],
  234. value: trim(m[2]),
  235. string: this.name('.') + m[1]
  236. };
  237. });
  238. // inline getter/setter
  239. this.use(/^\s*(get|set)\s*([\w$.]+)\s*\(([^)]+)/, function(m, parent) {
  240. return {
  241. type: 'property',
  242. ctor: this.parent,
  243. name: m[2],
  244. string: this.name('.prototype.') + m[2]
  245. };
  246. });
  247. // method
  248. this.use(/^\s*([\w$.]+)\s*\.\s*([\w$]+)\s*=\s*function\s*\(([^)]+)/, function(m, parent) {
  249. return {
  250. type: 'method',
  251. receiver: m[1],
  252. name: m[2],
  253. params: (m[3] || '').split(/[, ]+/),
  254. string: m[1] + '.' + m[2] + '()'
  255. };
  256. });
  257. // property
  258. this.use(/^\s*([\w$.]+)\s*\.\s*([\w$]+)\s*=\s*([^\n;]+)/, function(m, parent) {
  259. return {
  260. type: 'property',
  261. receiver: m[1],
  262. name: m[2],
  263. value: trim(m[3]),
  264. string: m[1] + '.' + m[2]
  265. };
  266. });
  267. // declaration
  268. this.use(/^\s*(?:const|let|var)\s+([\w$]+)\s*=\s*([^\n;]+)/, function(m, parent) {
  269. return {
  270. type: 'declaration',
  271. name: m[1],
  272. value: trim(m[2]),
  273. string: m[1]
  274. };
  275. });
  276. };
  277. function trim(str) {
  278. return toString(str).trim();
  279. }
  280. function toString(str) {
  281. if (!str) return '';
  282. return str;
  283. }
  284. /**
  285. * Expose an instance of `Parser`
  286. */
  287. module.exports = function(str, ctx, i) {
  288. var parser = new Parser(str, ctx, i);
  289. return parser.parse();
  290. };
  291. /**
  292. * Expose `Parser`
  293. */
  294. module.exports.Parser = Parser;