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

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