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

http://jsdoc-toolkit.googlecode.com/ · JavaScript · 473 lines · 345 code · 102 blank · 26 comment · 189 complexity · c2ec5cc73fcec49866e61fc112de9bc5 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("exports").length > 0) {
  44. var exports = doc.getTag("exports")[0];
  45. exports.desc.match(/(\S+) as (\S+)/i);
  46. var n1 = RegExp.$1;
  47. var n2 = RegExp.$2;
  48. if (!n1 && n2) throw "@exports tag requires a value like: 'name as ns.name'";
  49. JSDOC.Parser.rename = (JSDOC.Parser.rename || {});
  50. JSDOC.Parser.rename[n1] = n2
  51. }
  52. if (doc.getTag("lends").length > 0) {
  53. var lends = doc.getTag("lends")[0];
  54. var name = lends.desc
  55. if (!name) throw "@lends tag requires a value.";
  56. var symbol = new JSDOC.Symbol(name, [], "OBJECT", doc);
  57. this.namescope.push(symbol);
  58. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  59. if (matching) matching.popNamescope = name;
  60. else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
  61. this.lastDoc = null;
  62. return true;
  63. }
  64. else if (doc.getTag("name").length > 0 && doc.getTag("overview").length == 0) { // it's a virtual symbol
  65. var virtualName = doc.getTag("name")[0].desc;
  66. if (!virtualName) throw "@name tag requires a value.";
  67. var symbol = new JSDOC.Symbol(virtualName, [], "VIRTUAL", doc);
  68. JSDOC.Parser.addSymbol(symbol);
  69. this.lastDoc = null;
  70. return true;
  71. }
  72. else if (doc.meta) { // it's a meta doclet
  73. if (doc.meta == "@+") JSDOC.DocComment.shared = doc.src;
  74. else if (doc.meta == "@-") JSDOC.DocComment.shared = "";
  75. else if (doc.meta == "nocode+") JSDOC.Parser.conf.ignoreCode = true;
  76. else if (doc.meta == "nocode-") JSDOC.Parser.conf.ignoreCode = JSDOC.opt.n;
  77. else throw "Unrecognized meta comment: "+doc.meta;
  78. this.lastDoc = null;
  79. return true;
  80. }
  81. else if (doc.getTag("overview").length > 0) { // it's a file overview
  82. symbol = new JSDOC.Symbol("", [], "FILE", doc);
  83. JSDOC.Parser.addSymbol(symbol);
  84. this.lastDoc = null;
  85. return true;
  86. }
  87. else {
  88. this.lastDoc = doc;
  89. return false;
  90. }
  91. }
  92. else if (!JSDOC.Parser.conf.ignoreCode) { // it's code
  93. if (this.token.is("NAME")) {
  94. var symbol;
  95. var name = this.token.data;
  96. var doc = null; if (this.lastDoc) doc = this.lastDoc;
  97. var params = [];
  98. // it's inside an anonymous object
  99. 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"))) {
  100. name = "$anonymous";
  101. name = this.namescope.last().alias+"-"+name
  102. params = [];
  103. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  104. JSDOC.Parser.addSymbol(symbol);
  105. this.namescope.push(symbol);
  106. var matching = this.ts.getMatchingToken(null, "RIGHT_CURLY");
  107. if (matching) matching.popNamescope = name;
  108. else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
  109. }
  110. // function foo() {}
  111. else if (this.ts.look(-1).is("FUNCTION") && this.ts.look(1).is("LEFT_PAREN")) {
  112. var isInner;
  113. if (this.lastDoc) doc = this.lastDoc;
  114. name = this.namescope.last().alias+"-"+name;
  115. if (!this.namescope.last().is("GLOBAL")) isInner = true;
  116. params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
  117. symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
  118. if (isInner) symbol.isInner = true;
  119. if (this.ts.look(1).is("JSDOC")) {
  120. var inlineReturn = ""+this.ts.look(1).data;
  121. inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
  122. symbol.type = inlineReturn;
  123. }
  124. JSDOC.Parser.addSymbol(symbol);
  125. this.namescope.push(symbol);
  126. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  127. if (matching) matching.popNamescope = name;
  128. else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
  129. }
  130. // foo = function() {}
  131. else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("FUNCTION")) {
  132. var isInner;
  133. if (this.ts.look(-1).is("VAR") || this.isInner) {
  134. name = this.namescope.last().alias+"-"+name
  135. if (!this.namescope.last().is("GLOBAL")) isInner = true;
  136. }
  137. else if (name.indexOf("this.") == 0) {
  138. name = this.resolveThis(name);
  139. }
  140. if (this.lastDoc) doc = this.lastDoc;
  141. params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
  142. symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
  143. if (isInner) symbol.isInner = true;
  144. if (this.ts.look(1).is("JSDOC")) {
  145. var inlineReturn = ""+this.ts.look(1).data;
  146. inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
  147. symbol.type = inlineReturn;
  148. }
  149. JSDOC.Parser.addSymbol(symbol);
  150. this.namescope.push(symbol);
  151. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  152. if (matching) matching.popNamescope = name;
  153. else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
  154. }
  155. // foo = new function() {}
  156. else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("NEW") && this.ts.look(3).is("FUNCTION")) {
  157. var isInner;
  158. if (this.ts.look(-1).is("VAR") || this.isInner) {
  159. name = this.namescope.last().alias+"-"+name
  160. if (!this.namescope.last().is("GLOBAL")) isInner = true;
  161. }
  162. else if (name.indexOf("this.") == 0) {
  163. name = this.resolveThis(name);
  164. }
  165. if (this.lastDoc) doc = this.lastDoc;
  166. params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
  167. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  168. if (isInner) symbol.isInner = true;
  169. if (this.ts.look(1).is("JSDOC")) {
  170. var inlineReturn = ""+this.ts.look(1).data;
  171. inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
  172. symbol.type = inlineReturn;
  173. }
  174. JSDOC.Parser.addSymbol(symbol);
  175. symbol.scopeType = "INSTANCE";
  176. this.namescope.push(symbol);
  177. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  178. if (matching) matching.popNamescope = name;
  179. else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
  180. }
  181. // foo: function() {}
  182. else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("FUNCTION")) {
  183. name = (this.namescope.last().alias+"."+name).replace("#.", "#");
  184. if (this.lastDoc) doc = this.lastDoc;
  185. params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
  186. if (doc && doc.getTag("constructs").length) {
  187. name = name.replace(/\.prototype(\.|$)/, "#");
  188. if (name.indexOf("#") > -1) name = name.match(/(^[^#]+)/)[0];
  189. else name = this.namescope.last().alias;
  190. symbol = new JSDOC.Symbol(name, params, "CONSTRUCTOR", doc);
  191. }
  192. else {
  193. symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
  194. }
  195. if (this.ts.look(1).is("JSDOC")) {
  196. var inlineReturn = ""+this.ts.look(1).data;
  197. inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
  198. symbol.type = inlineReturn;
  199. }
  200. JSDOC.Parser.addSymbol(symbol);
  201. this.namescope.push(symbol);
  202. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  203. if (matching) matching.popNamescope = name;
  204. else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
  205. }
  206. // foo = {}
  207. else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("LEFT_CURLY")) {
  208. var isInner;
  209. if (this.ts.look(-1).is("VAR") || this.isInner) {
  210. name = this.namescope.last().alias+"-"+name
  211. if (!this.namescope.last().is("GLOBAL")) isInner = true;
  212. }
  213. else if (name.indexOf("this.") == 0) {
  214. name = this.resolveThis(name);
  215. }
  216. if (this.lastDoc) doc = this.lastDoc;
  217. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  218. if (isInner) symbol.isInner = true;
  219. if (doc) JSDOC.Parser.addSymbol(symbol);
  220. this.namescope.push(symbol);
  221. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  222. if (matching) matching.popNamescope = name;
  223. else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
  224. }
  225. // var foo;
  226. else if (this.ts.look(1).is("SEMICOLON")) {
  227. var isInner;
  228. if (this.ts.look(-1).is("VAR") || this.isInner) {
  229. name = this.namescope.last().alias+"-"+name
  230. if (!this.namescope.last().is("GLOBAL")) isInner = true;
  231. if (this.lastDoc) doc = this.lastDoc;
  232. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  233. if (isInner) symbol.isInner = true;
  234. if (doc) JSDOC.Parser.addSymbol(symbol);
  235. }
  236. }
  237. // foo = x
  238. else if (this.ts.look(1).is("ASSIGN")) {
  239. var isInner;
  240. if (this.ts.look(-1).is("VAR") || this.isInner) {
  241. name = this.namescope.last().alias+"-"+name
  242. if (!this.namescope.last().is("GLOBAL")) isInner = true;
  243. }
  244. else if (name.indexOf("this.") == 0) {
  245. name = this.resolveThis(name);
  246. }
  247. if (this.lastDoc) doc = this.lastDoc;
  248. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  249. if (isInner) symbol.isInner = true;
  250. if (doc) JSDOC.Parser.addSymbol(symbol);
  251. }
  252. // foo: {}
  253. else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("LEFT_CURLY")) {
  254. name = (this.namescope.last().alias+"."+name).replace("#.", "#");
  255. if (this.lastDoc) doc = this.lastDoc;
  256. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  257. if (doc) JSDOC.Parser.addSymbol(symbol);
  258. this.namescope.push(symbol);
  259. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  260. if (matching) matching.popNamescope = name;
  261. else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
  262. }
  263. // foo: x
  264. else if (this.ts.look(1).is("COLON")) {
  265. name = (this.namescope.last().alias+"."+name).replace("#.", "#");;
  266. if (this.lastDoc) doc = this.lastDoc;
  267. symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
  268. if (doc) JSDOC.Parser.addSymbol(symbol);
  269. }
  270. // foo(...)
  271. else if (this.ts.look(1).is("LEFT_PAREN")) {
  272. if (typeof JSDOC.PluginManager != "undefined") {
  273. var functionCall = {name: name};
  274. var cursor = this.ts.cursor;
  275. params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
  276. this.ts.cursor = cursor;
  277. for (var i = 0; i < params.length; i++)
  278. functionCall["arg" + (i + 1)] = params[i].name;
  279. JSDOC.PluginManager.run("onFunctionCall", functionCall);
  280. if (functionCall.doc) {
  281. this.ts.insertAhead(new JSDOC.Token(functionCall.doc, "COMM", "JSDOC"));
  282. }
  283. }
  284. }
  285. this.lastDoc = null;
  286. }
  287. else if (this.token.is("FUNCTION")) { // it's an anonymous function
  288. if (
  289. (!this.ts.look(-1).is("COLON") || !this.ts.look(-1).is("ASSIGN"))
  290. && !this.ts.look(1).is("NAME")
  291. ) {
  292. if (this.lastDoc) doc = this.lastDoc;
  293. name = "$anonymous";
  294. name = this.namescope.last().alias+"-"+name
  295. params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
  296. symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
  297. JSDOC.Parser.addSymbol(symbol);
  298. this.namescope.push(symbol);
  299. var matching = this.ts.getMatchingToken("LEFT_CURLY");
  300. if (matching) matching.popNamescope = name;
  301. else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
  302. }
  303. }
  304. }
  305. return true;
  306. }
  307. /**
  308. Resolves what "this." means when it appears in a name.
  309. @param name The name that starts with "this.".
  310. @returns The name with "this." resolved.
  311. */
  312. JSDOC.Walker.prototype.resolveThis = function(name) {
  313. name.match(/^this\.(.+)$/)
  314. var nameFragment = RegExp.$1;
  315. if (!nameFragment) return name;
  316. var symbol = this.namescope.last();
  317. var scopeType = symbol.scopeType || symbol.isa;
  318. // if we are in a constructor function, `this` means the instance
  319. if (scopeType == "CONSTRUCTOR") {
  320. name = symbol.alias+"#"+nameFragment;
  321. }
  322. // if we are in an anonymous constructor function, `this` means the instance
  323. else if (scopeType == "INSTANCE") {
  324. name = symbol.alias+"."+nameFragment;
  325. }
  326. // if we are in a function, `this` means the container (possibly the global)
  327. else if (scopeType == "FUNCTION") {
  328. // in a method of a prototype, so `this` means the constructor
  329. if (symbol.alias.match(/(^.*)[#.-][^#.-]+/)) {
  330. var parentName = RegExp.$1;
  331. var parent = JSDOC.Parser.symbols.getSymbol(parentName);
  332. if (!parent) {
  333. if (JSDOC.Lang.isBuiltin(parentName)) parent = JSDOC.Parser.addBuiltin(parentName);
  334. else {
  335. if (symbol.alias.indexOf("$anonymous") < 0) // these will be ignored eventually
  336. LOG.warn("Trying to document "+symbol.alias+" without first documenting "+parentName+".");
  337. }
  338. }
  339. if (parent) name = parentName+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
  340. }
  341. else {
  342. parent = this.namescope.last(1);
  343. name = parent.alias+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
  344. }
  345. }
  346. // otherwise it means the global
  347. else {
  348. name = nameFragment;
  349. }
  350. return name;
  351. }
  352. JSDOC.Walker.onParamList = function(/**Array*/paramTokens) {
  353. if (!paramTokens) {
  354. LOG.warn("Malformed parameter list. Can't parse code.");
  355. return [];
  356. }
  357. var params = [];
  358. for (var i = 0, l = paramTokens.length; i < l; i++) {
  359. if (paramTokens[i].is("JSDOC")) {
  360. var paramType = paramTokens[i].data.replace(/(^\/\*\* *| *\*\/$)/g, "");
  361. if (paramTokens[i+1] && paramTokens[i+1].is("NAME")) {
  362. i++;
  363. params.push({type: paramType, name: paramTokens[i].data});
  364. }
  365. }
  366. else if (paramTokens[i].is("NAME")) {
  367. params.push({name: paramTokens[i].data});
  368. }
  369. }
  370. return params;
  371. }