PageRenderTime 56ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/tags/jsdoc_toolkit-1.3.3/app/JsParse.js

http://jsdoc-toolkit.googlecode.com/
JavaScript | 359 lines | 243 code | 47 blank | 69 comment | 94 complexity | 0e902a89888506b8bafe3932ca63e703 MD5 | raw file
  1. /**
  2. * @fileOverview
  3. * @name JsParse
  4. * @author Michael Mathews micmath@gmail.com
  5. * @url $HeadURL: http://jsdoc-toolkit.googlecode.com/svn/tags/jsdoc_toolkit-1.3.3/app/JsParse.js $
  6. * @revision $Id: JsParse.js 236 2007-09-17 23:10:39Z micmath $
  7. * @license <a href="http://en.wikipedia.org/wiki/MIT_License">X11/MIT License</a>
  8. * (See the accompanying README file for full details.)
  9. */
  10. /**
  11. * @class Find objects in tokenized JavaScript source code.
  12. * @constructor
  13. * @author Michael Mathews <a href="mailto:micmath@gmail.com">micmath@gmail.com</a>
  14. */
  15. function JsParse() {};
  16. /**
  17. * Populate the symbols array with symbols found in the
  18. * given token stream.
  19. * @param {TokenStream} tokenStream
  20. */
  21. JsParse.prototype.parse = function(tokenStream) {
  22. /**
  23. * All symbols found in the tokenStream.
  24. * @type Symbol[]
  25. */
  26. this.symbols = [];
  27. /**
  28. * Overview found in the tokenStream.
  29. * @type Symbol
  30. */
  31. this.overview = null;
  32. while(tokenStream.next()) {
  33. if (this._findDocComment(tokenStream)) continue;
  34. if (this._findFunction(tokenStream)) continue;
  35. if (this._findVariable(tokenStream)) continue;
  36. }
  37. }
  38. /**
  39. * Try to find a JsDoc comment in the tokenStream.
  40. * @param {TokenStream} ts
  41. * @return {boolean} Was a JsDoc comment found?
  42. */
  43. JsParse.prototype._findDocComment = function(ts) {
  44. if (ts.look().is("JSDOC")) {
  45. var doc = ts.look().data;
  46. if (/@(projectdescription|(file)?overview)\b/i.test(doc)) {
  47. this.overview = new Symbol("", [], "FILE", doc);
  48. delete ts.tokens[ts.cursor];
  49. return true;
  50. }
  51. else if (/@name\s+([a-z0-9_$.]+)\s*/i.test(doc)) {
  52. this.symbols.push(new Symbol(RegExp.$1, [], SYM.VIRTUAL, doc));
  53. delete ts.tokens[ts.cursor];
  54. return true;
  55. }
  56. else if (/@scope\s+([a-z0-9_$.]+)\s*/i.test(doc)) {
  57. var scope = RegExp.$1;
  58. if (scope) {
  59. scope = scope.replace(/\.prototype\b/, "/");
  60. this._onObLiteral(scope, new TokenStream(ts.balance("LEFT_CURLY")));
  61. return true;
  62. }
  63. }
  64. }
  65. return false;
  66. }
  67. /**
  68. * Try to find a function definition in the tokenStream
  69. * @param {TokenStream} ts
  70. * @return {boolean} Was a function definition found?
  71. */
  72. JsParse.prototype._findFunction = function(ts) {
  73. if (ts.look().is("NAME")) {
  74. var name = ts.look().data;
  75. var doc = "";
  76. var isa = null;
  77. var body = "";
  78. var paramTokens = [];
  79. var params = [];
  80. // like function foo()
  81. if (ts.look(-1).is("FUNCTION")) {
  82. isa = SYM.FUNCTION;
  83. if (ts.look(-2).is("JSDOC")) {
  84. doc = ts.look(-2).data;
  85. }
  86. paramTokens = ts.balance("LEFT_PAREN");
  87. body = ts.balance("LEFT_CURLY");
  88. }
  89. // like var foo = function()
  90. else if (ts.look(1).is("ASSIGN") && ts.look(2).is("FUNCTION")) {
  91. isa = SYM.FUNCTION;
  92. if (ts.look(-1).is("VAR") && ts.look(-2).is("JSDOC")) {
  93. doc = ts.look(-2).data;
  94. }
  95. else if (ts.look(-1).is("JSDOC")) {
  96. doc = ts.look(-1).data;
  97. }
  98. paramTokens = ts.balance("LEFT_PAREN");
  99. body = ts.balance("LEFT_CURLY");
  100. // like foo = function(n) {return n}(42)
  101. if (ts.look(1).is("LEFT_PAREN")) {
  102. isa = SYM.OBJECT;
  103. ts.balance("LEFT_PAREN");
  104. if (doc) { // we only keep these if they're documented
  105. name = name.replace(/\.prototype\.?/, "/");
  106. if (!/\/$/.test(name)) { // assigning to prototype of already existing symbol
  107. this.symbols.push(new Symbol(name, [], isa, doc));
  108. }
  109. }
  110. this._onFnBody(name, new TokenStream(body));
  111. return true;
  112. }
  113. }
  114. // like var foo = new function()
  115. else if (ts.look(1).is("ASSIGN") && ts.look(2).is("NEW") && ts.look(3).is("FUNCTION")) {
  116. isa = SYM.OBJECT;
  117. if (ts.look(-1).is("VAR") && ts.look(-2).is("JSDOC")) {
  118. doc = ts.look(-2).data;
  119. }
  120. else if (ts.look(-1).is("JSDOC")) {
  121. doc = ts.look(-1).data;
  122. }
  123. paramTokens = ts.balance("LEFT_PAREN");
  124. body = ts.balance("LEFT_CURLY");
  125. if (doc) { // we only keep these if they're documented
  126. name = name.replace(/\.prototype\.?/, "/");
  127. if (!/\/$/.test(name)) { // assigning to prototype of already existing symbol
  128. this.symbols.push(new Symbol(name, [], isa, doc));
  129. }
  130. }
  131. this._onFnBody(name, new TokenStream(body));
  132. return true;
  133. }
  134. if (isa && name) {
  135. if (isa == SYM.FUNCTION) {
  136. for (var i = 0; i < paramTokens.length; i++) {
  137. if (paramTokens[i].is("NAME"))
  138. params.push(paramTokens[i].data);
  139. }
  140. }
  141. // like Foo.bar.prototype.baz = function() {}
  142. var ns = name;
  143. if (name.indexOf(".prototype") > 0) {
  144. isa = SYM.FUNCTION;
  145. name = name.replace(/\.prototype\.?/, "/");
  146. }
  147. this.symbols.push(new Symbol(name, params, isa, doc));
  148. if (body) {
  149. if (ns.indexOf(".prototype") > 0) {
  150. if (/@constructor\b/.test(doc)) {
  151. ns = ns.replace(/\.prototype\.?/, "/");
  152. }
  153. else {
  154. ns = ns.replace(/\.prototype\.[^.]+$/, "/");
  155. }
  156. }
  157. this._onFnBody(ns, new TokenStream(body));
  158. }
  159. return true;
  160. }
  161. }
  162. return false;
  163. }
  164. /**
  165. * Try to find a variable definition in the tokenStream
  166. * @param {TokenStream} ts
  167. * @return {boolean} Was a variable definition found?
  168. */
  169. JsParse.prototype._findVariable = function(ts) {
  170. if (ts.look().is("NAME") && ts.look(1).is("ASSIGN")) {
  171. // like var foo = 1
  172. var name = ts.look().data;
  173. isa = SYM.OBJECT;
  174. var doc;
  175. if (ts.look(-1).is("JSDOC")) doc = ts.look(-1).data;
  176. else if (ts.look(-1).is("VAR") && ts.look(-2).is("JSDOC")) doc = ts.look(-2).data;
  177. name = name.replace(/\.prototype\.?/, "/");
  178. if (doc) { // we only keep these if they're documented
  179. if (!/\/$/.test(name)) { // assigning to prototype of already existing symbol
  180. this.symbols.push(new Symbol(name, [], isa, doc));
  181. }
  182. if (/@class\b/i.test(doc)) {
  183. name = name +"/";
  184. }
  185. }
  186. // like foo = {
  187. if (ts.look(2).is("LEFT_CURLY")) {
  188. this._onObLiteral(name, new TokenStream(ts.balance("LEFT_CURLY")));
  189. }
  190. return true;
  191. }
  192. return false;
  193. }
  194. /**
  195. * Handle sub-parsing of the content within an object literal.
  196. * @private
  197. * @param {String} nspace The name attached to this object.
  198. * @param {TokenStream} ts The content of the object literal.
  199. */
  200. JsParse.prototype._onObLiteral = function(nspace, ts) {
  201. while (ts.next()) {
  202. if (this._findDocComment(ts)) {
  203. }
  204. else if (ts.look().is("NAME") && ts.look(1).is("COLON")) {
  205. var name = nspace+((nspace.charAt(nspace.length-1)=="/")?"":".")+ts.look().data;
  206. // like foo: function
  207. if (ts.look(2).is("FUNCTION")) {
  208. var isa = SYM.FUNCTION;
  209. var doc = "";
  210. if (ts.look(-1).is("JSDOC")) doc = ts.look(-1).data;
  211. var paramTokens = ts.balance("LEFT_PAREN");
  212. var params = [];
  213. for (var i = 0; i < paramTokens.length; i++) {
  214. if (paramTokens[i].is("NAME"))
  215. params.push(paramTokens[i].data);
  216. }
  217. var body = ts.balance("LEFT_CURLY");
  218. this.symbols.push(new Symbol(name, params, isa, doc));
  219. // find methods in the body of this function
  220. this._onFnBody(name, new TokenStream(body));
  221. }
  222. // like foo: {...}
  223. else if (ts.look(2).is("LEFT_CURLY")) { // another nested object literal
  224. if (ts.look(-1).is("JSDOC")) {
  225. var isa = SYM.OBJECT;
  226. var doc = ts.look(-1).data;
  227. this.symbols.push(new Symbol(name, [], isa, doc));
  228. }
  229. this._onObLiteral(name, new TokenStream(ts.balance("LEFT_CURLY"))); // recursive
  230. }
  231. else { // like foo: 1, or foo: "one"
  232. if (ts.look(-1).is("JSDOC")) { // we only grab these if they are documented
  233. var isa = SYM.OBJECT;
  234. var doc = ts.look(-1).data;
  235. this.symbols.push(new Symbol(name, [], isa, doc));
  236. }
  237. while (!ts.look().is("COMMA")) { // skip to end of RH value ignoring things like bar({blah, blah})
  238. if (ts.look().is("LEFT_PAREN")) ts.balance("LEFT_PAREN");
  239. else if (ts.look().is("LEFT_CURLY")) ts.balance("LEFT_CURLY");
  240. else if (!ts.next()) break;
  241. }
  242. }
  243. }
  244. }
  245. }
  246. /**
  247. * Handle sub-parsing of the content within a function body.
  248. * @private
  249. * @param {String} nspace The name attached to this function.
  250. * @param {TokenStream} fs The content of the function body.
  251. */
  252. JsParse.prototype._onFnBody = function(nspace, fs) {
  253. while (fs.look()) {
  254. if (this._findDocComment(fs)) {
  255. }
  256. else if (fs.look().is("NAME") && fs.look(1).is("ASSIGN")) {
  257. var name = fs.look().data;
  258. // like this.foo =
  259. if (name.indexOf("this.") == 0) {
  260. // like this.foo = function
  261. if (fs.look(2).is("FUNCTION")) {
  262. var isa = SYM.FUNCTION;
  263. var doc = (fs.look(-1).is("JSDOC"))? fs.look(-1).data : "";
  264. name = name.replace(/^this\./, (nspace+"/").replace("//", "/"))
  265. var paramTokens = fs.balance("LEFT_PAREN");
  266. var params = [];
  267. for (var i = 0; i < paramTokens.length; i++) {
  268. if (paramTokens[i].is("NAME")) params.push(paramTokens[i].data);
  269. }
  270. body = fs.balance("LEFT_CURLY");
  271. // like this.foo = function(n) {return n}(42)
  272. if (fs.look(1).is("LEFT_PAREN")) { // false alarm, it's not really a named function definition
  273. isa = SYM.OBJECT;
  274. fs.balance("LEFT_PAREN");
  275. if (doc) { // we only grab these if they are documented
  276. this.symbols.push(
  277. new Symbol(name, [], isa, doc)
  278. );
  279. }
  280. break;
  281. }
  282. this.symbols.push(
  283. new Symbol(name, params, isa, doc)
  284. );
  285. if (body) {
  286. this._onFnBody(name, new TokenStream(body)); // recursive
  287. }
  288. }
  289. else {
  290. var isa = SYM.OBJECT;
  291. var doc = (fs.look(-1).is("JSDOC"))? fs.look(-1).data : "";
  292. name = name.replace(/^this\./, (nspace+"/").replace("//", "/"))
  293. if (doc) {
  294. this.symbols.push(
  295. new Symbol(name, [], isa, doc)
  296. );
  297. }
  298. // like this.foo = { ... }
  299. if (fs.look(2).is("LEFT_CURLY")) {
  300. var literal = fs.balance("LEFT_CURLY");
  301. this._onObLiteral(name, new TokenStream(literal));
  302. }
  303. }
  304. }
  305. // like <thisfunction>.prototype.foo =
  306. else if (name.indexOf(nspace+".prototype.") == 0) {
  307. this._findFunction(fs);
  308. }
  309. }
  310. if (!fs.next()) break;
  311. }
  312. }