PageRenderTime 55ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

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

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