/tags/jsdoc_toolkit-2.0.1/jsdoc-toolkit/app/lib/JSDOC/Walker.js

http://jsdoc-toolkit.googlecode.com/ · JavaScript · 416 lines · 301 code · 90 blank · 25 comment · 173 complexity · 8d45962e909f435b03af467d216e7945 MD5 · raw file

  1. if (typeof JSDOC == "undefined") JSDOC = {};
  2. /** @constructor */
  3. JSDOC.Walker = function(/**JSDOC.TokenStream*/ts) {
  4. this.init();
  5. if (typeof ts != "undefined") {
  6. this.walk(ts);
  7. }
  8. }
  9. JSDOC.Walker.prototype.init = function() {
  10. this.ts = null;
  11. var globalSymbol = new JSDOC.Symbol("_global_", [], "GLOBAL", new JSDOC.DocComment(""));
  12. globalSymbol.isNamespace = true;
  13. globalSymbol.srcFile = "";
  14. globalSymbol.isPrivate = false;
  15. JSDOC.Parser.addSymbol(globalSymbol);
  16. this.lastDoc = null;
  17. this.token = null;
  18. /**
  19. The chain of symbols under which we are currently nested.
  20. @type Array
  21. */
  22. this.namescope = [globalSymbol];
  23. this.namescope.last = function(n){ if (!n) n = 0; return this[this.length-(1+n)] || "" };
  24. }
  25. JSDOC.Walker.prototype.walk = function(/**JSDOC.TokenStream*/ts) {
  26. this.ts = ts;
  27. while (this.token = this.ts.look()) {
  28. if (this.token.popNamescope) {
  29. var symbol = this.namescope.pop();
  30. if (symbol.is("FUNCTION")) {
  31. if (this.ts.look(1).is("LEFT_PAREN") && symbol.comment.getTag("function").length == 0) {
  32. symbol.isa = "OBJECT";
  33. }
  34. }
  35. }
  36. this.step();
  37. if (!this.ts.next()) break;
  38. }
  39. }
  40. JSDOC.Walker.prototype.step = function() {
  41. if (this.token.is("JSDOC")) { // it's a doc comment
  42. var doc = new JSDOC.DocComment(this.token.data);
  43. if (doc.getTag("lends").length > 0) { // it's a new namescope
  44. var lends = doc.getTag("lends")[0];
  45. var name = lends.desc
  46. if (!name) throw "@lends tag requires a value.";
  47. var symbol = new JSDOC.Symbol(name, [], "OBJECT", doc);
  48. this.namescope.push(symbol);
  49. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  50. if (matching) matching.popNamescope = name;
  51. else LOG.warn("Mismatched } character. Can't parse code.");
  52. this.lastDoc = null;
  53. return true;
  54. }
  55. else if (doc.getTag("name").length > 0 && doc.getTag("overview").length == 0) { // it's a virtual symbol
  56. var virtualName = doc.getTag("name")[0].desc;
  57. if (!virtualName) throw "@name tag requires a value.";
  58. var symbol = new JSDOC.Symbol(virtualName, [], "VIRTUAL", doc);
  59. JSDOC.Parser.addSymbol(symbol);
  60. this.lastDoc = null;
  61. return true;
  62. }
  63. else if (doc.meta) { // it's a meta doclet
  64. if (doc.meta == "@+") JSDOC.DocComment.shared = doc.src;
  65. else if (doc.meta == "@-") JSDOC.DocComment.shared = "";
  66. else if (doc.meta == "nocode+") JSDOC.Parser.conf.ignoreCode = true;
  67. else if (doc.meta == "nocode-") JSDOC.Parser.conf.ignoreCode = JSDOC.opt.n;
  68. else throw "Unrecognized meta comment: "+doc.meta;
  69. this.lastDoc = null;
  70. return true;
  71. }
  72. else if (doc.getTag("overview").length > 0) { // it's a file overview
  73. symbol = new JSDOC.Symbol("", [], "FILE", doc);
  74. JSDOC.Parser.addSymbol(symbol);
  75. this.lastDoc = null;
  76. return true;
  77. }
  78. else {
  79. this.lastDoc = doc;
  80. return false;
  81. }
  82. }
  83. else if (!JSDOC.Parser.conf.ignoreCode) { // it's code
  84. if (this.token.is("NAME")) {
  85. var symbol;
  86. var name = this.token.data;
  87. var doc = null; if (this.lastDoc) doc = this.lastDoc;
  88. var params = [];
  89. // it's inside an anonymous object
  90. if (this.ts.look(1).is("COLON") && this.ts.look(-1).is("LEFT_CURLY") && !(this.ts.look(-2).is("JSDOC") || this.namescope.last().comment.getTag("lends").length || this.ts.look(-2).is("ASSIGN") || this.ts.look(-2).is("COLON"))) {
  91. name = "$anonymous";
  92. name = this.namescope.last().alias+"-"+name
  93. params = [];
  94. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  95. JSDOC.Parser.addSymbol(symbol);
  96. this.namescope.push(symbol);
  97. var matching = this.ts.getMatchingToken(null, "RIGHT_CURLY");
  98. if (matching) matching.popNamescope = name;
  99. else LOG.warn("Mismatched } character. Can't parse code.");
  100. }
  101. // function foo() {}
  102. else if (this.ts.look(-1).is("FUNCTION") && this.ts.look(1).is("LEFT_PAREN")) {
  103. var isInner;
  104. if (this.lastDoc) doc = this.lastDoc;
  105. name = this.namescope.last().alias+"-"+name;
  106. if (!this.namescope.last().is("GLOBAL")) isInner = true;
  107. params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
  108. symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
  109. if (isInner) symbol.isInner = true;
  110. JSDOC.Parser.addSymbol(symbol);
  111. this.namescope.push(symbol);
  112. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  113. if (matching) matching.popNamescope = name;
  114. else LOG.warn("Mismatched } character. Can't parse code.");
  115. }
  116. // foo = function() {}
  117. else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("FUNCTION")) {
  118. var isInner;
  119. if (this.ts.look(-1).is("VAR") || this.isInner) {
  120. name = this.namescope.last().alias+"-"+name
  121. if (!this.namescope.last().is("GLOBAL")) isInner = true;
  122. }
  123. else if (name.indexOf("this.") == 0) {
  124. name = this.resolveThis(name);
  125. }
  126. if (this.lastDoc) doc = this.lastDoc;
  127. params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
  128. symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
  129. if (isInner) symbol.isInner = true;
  130. JSDOC.Parser.addSymbol(symbol);
  131. this.namescope.push(symbol);
  132. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  133. if (matching) matching.popNamescope = name;
  134. else LOG.warn("Mismatched } character. Can't parse code.");
  135. }
  136. // foo = new function() {}
  137. else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("NEW") && this.ts.look(3).is("FUNCTION")) {
  138. var isInner;
  139. if (this.ts.look(-1).is("VAR") || this.isInner) {
  140. name = this.namescope.last().alias+"-"+name
  141. if (!this.namescope.last().is("GLOBAL")) isInner = true;
  142. }
  143. else if (name.indexOf("this.") == 0) {
  144. name = this.resolveThis(name);
  145. }
  146. if (this.lastDoc) doc = this.lastDoc;
  147. params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
  148. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  149. if (isInner) symbol.isInner = true;
  150. JSDOC.Parser.addSymbol(symbol);
  151. symbol.scopeType = "INSTANCE";
  152. this.namescope.push(symbol);
  153. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  154. if (matching) matching.popNamescope = name;
  155. else LOG.warn("Mismatched } character. Can't parse code.");
  156. }
  157. // foo: function() {}
  158. else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("FUNCTION")) {
  159. name = (this.namescope.last().alias+"."+name).replace("#.", "#");
  160. if (this.lastDoc) doc = this.lastDoc;
  161. params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
  162. if (doc && doc.getTag("constructs").length) {
  163. name = name.replace(/\.prototype(\.|$)/, "#");
  164. if (name.indexOf("#") > -1) name = name.match(/(^[^#]+)/)[0];
  165. else name = this.namescope.last().alias;
  166. symbol = new JSDOC.Symbol(name, params, "CONSTRUCTOR", doc);
  167. }
  168. else {
  169. symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
  170. }
  171. JSDOC.Parser.addSymbol(symbol);
  172. this.namescope.push(symbol);
  173. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  174. if (matching) matching.popNamescope = name;
  175. else LOG.warn("Mismatched } character. Can't parse code.");
  176. }
  177. // foo = {}
  178. else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("LEFT_CURLY")) {
  179. var isInner;
  180. if (this.ts.look(-1).is("VAR") || this.isInner) {
  181. name = this.namescope.last().alias+"-"+name
  182. if (!this.namescope.last().is("GLOBAL")) isInner = true;
  183. }
  184. else if (name.indexOf("this.") == 0) {
  185. name = this.resolveThis(name);
  186. }
  187. if (this.lastDoc) doc = this.lastDoc;
  188. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  189. if (isInner) symbol.isInner = true;
  190. if (doc) JSDOC.Parser.addSymbol(symbol);
  191. this.namescope.push(symbol);
  192. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  193. if (matching) matching.popNamescope = name;
  194. else LOG.warn("Mismatched } character. Can't parse code.");
  195. }
  196. // foo = x
  197. else if (this.ts.look(1).is("ASSIGN")) {
  198. var isInner;
  199. if (this.ts.look(-1).is("VAR") || this.isInner) {
  200. name = this.namescope.last().alias+"-"+name
  201. if (!this.namescope.last().is("GLOBAL")) isInner = true;
  202. }
  203. else if (name.indexOf("this.") == 0) {
  204. name = this.resolveThis(name);
  205. }
  206. if (this.lastDoc) doc = this.lastDoc;
  207. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  208. if (isInner) symbol.isInner = true;
  209. if (doc) JSDOC.Parser.addSymbol(symbol);
  210. }
  211. // foo: {}
  212. else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("LEFT_CURLY")) {
  213. name = (this.namescope.last().alias+"."+name).replace("#.", "#");
  214. if (this.lastDoc) doc = this.lastDoc;
  215. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  216. if (doc) JSDOC.Parser.addSymbol(symbol);
  217. this.namescope.push(symbol);
  218. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  219. if (matching) matching.popNamescope = name;
  220. else LOG.warn("Mismatched } character. Can't parse code.");
  221. }
  222. // foo: x
  223. else if (this.ts.look(1).is("COLON")) {
  224. name = (this.namescope.last().alias+"."+name).replace("#.", "#");;
  225. if (this.lastDoc) doc = this.lastDoc;
  226. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  227. if (doc) JSDOC.Parser.addSymbol(symbol);
  228. }
  229. // foo(...)
  230. else if (this.ts.look(1).is("LEFT_PAREN")) {
  231. var functionCall = {name: name};
  232. if (!this.ts.look(2).is("RIGHT_PAREN")) functionCall.arg1 = this.ts.look(2).data;
  233. if (typeof JSDOC.PluginManager != "undefined") {
  234. JSDOC.PluginManager.run("onFunctionCall", functionCall);
  235. if (functionCall.doc) {
  236. this.ts.insertAhead(new JSDOC.Token(functionCall.doc, "COMM", "JSDOC"));
  237. }
  238. }
  239. }
  240. this.lastDoc = null;
  241. }
  242. else if (this.token.is("FUNCTION")) { // it's an anonymous function
  243. if (
  244. (!this.ts.look(-1).is("COLON") || !this.ts.look(-1).is("ASSIGN"))
  245. && !this.ts.look(1).is("NAME")
  246. ) {
  247. if (this.lastDoc) doc = this.lastDoc;
  248. name = "$anonymous";
  249. name = this.namescope.last().alias+"-"+name
  250. params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
  251. symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
  252. JSDOC.Parser.addSymbol(symbol);
  253. this.namescope.push(symbol);
  254. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  255. if (matching) matching.popNamescope = name;
  256. else LOG.warn("Mismatched } character. Can't parse code.");
  257. }
  258. }
  259. }
  260. return true;
  261. }
  262. /**
  263. Resolves what "this." means when it appears in a name.
  264. @param name The name that starts with "this.".
  265. @returns The name with "this." resolved.
  266. */
  267. JSDOC.Walker.prototype.resolveThis = function(name) {
  268. name.match(/^this\.(.+)$/)
  269. var nameFragment = RegExp.$1;
  270. if (!nameFragment) return name;
  271. var symbol = this.namescope.last();
  272. var scopeType = symbol.scopeType || symbol.isa;
  273. // if we are in a constructor function, `this` means the instance
  274. if (scopeType == "CONSTRUCTOR") {
  275. name = symbol.alias+"#"+nameFragment;
  276. }
  277. // if we are in an anonymous constructor function, `this` means the instance
  278. else if (scopeType == "INSTANCE") {
  279. name = symbol.alias+"."+nameFragment;
  280. }
  281. // if we are in a function, `this` means the container (possibly the global)
  282. else if (scopeType == "FUNCTION") {
  283. // in a method of a prototype, so `this` means the constructor
  284. if (symbol.alias.match(/(^.*)[#.-][^#.-]+/)) {
  285. var parentName = RegExp.$1;
  286. var parent = JSDOC.Parser.symbols.getSymbol(parentName);
  287. if (!parent) {
  288. if (JSDOC.Lang.isBuiltin(parentName)) parent = JSDOC.Parser.addBuiltin(parentName);
  289. else {
  290. if (symbol.alias.indexOf("$anonymous") < 0) // these will be ignored eventually
  291. LOG.warn("Can't document "+symbol.alias+" without first documenting "+parentName+".");
  292. }
  293. }
  294. if (parent) name = parentName+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
  295. }
  296. else {
  297. parent = this.namescope.last(1);
  298. name = parent.alias+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
  299. }
  300. }
  301. // otherwise it means the global
  302. else {
  303. name = nameFragment;
  304. }
  305. return name;
  306. }
  307. JSDOC.Walker.onParamList = function(/**Array*/paramTokens) {
  308. if (!paramTokens) {
  309. LOG.warn("Malformed parameter list. Can't parse code.");
  310. return [];
  311. }
  312. var params = [];
  313. for (var i = 0, l = paramTokens.length; i < l; i++) {
  314. if (paramTokens[i].is("JSDOC")) {
  315. var paramType = paramTokens[i].data.replace(/(^\/\*\* *| *\*\/$)/g, "");
  316. if (paramTokens[i+1] && paramTokens[i+1].is("NAME")) {
  317. i++;
  318. params.push({type: paramType, name: paramTokens[i].data});
  319. }
  320. }
  321. else if (paramTokens[i].is("NAME")) {
  322. params.push({name: paramTokens[i].data});
  323. }
  324. }
  325. return params;
  326. }