PageRenderTime 538ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

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

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