can generate jsdoc; private methods marked as such
[dygraphs.git] / jsdoc-toolkit / app / lib / JSDOC / Walker.js
1 if (typeof JSDOC == "undefined") JSDOC = {};
2
3 /** @constructor */
4 JSDOC.Walker = function(/**JSDOC.TokenStream*/ts) {
5 this.init();
6 if (typeof ts != "undefined") {
7 this.walk(ts);
8 }
9 }
10
11 JSDOC.Walker.prototype.init = function() {
12 this.ts = null;
13
14 var globalSymbol = new JSDOC.Symbol("_global_", [], "GLOBAL", new JSDOC.DocComment(""));
15 globalSymbol.isNamespace = true;
16 globalSymbol.srcFile = "";
17 globalSymbol.isPrivate = false;
18 JSDOC.Parser.addSymbol(globalSymbol);
19 this.lastDoc = null;
20 this.token = null;
21
22 /**
23 The chain of symbols under which we are currently nested.
24 @type Array
25 */
26 this.namescope = [globalSymbol];
27 this.namescope.last = function(n){ if (!n) n = 0; return this[this.length-(1+n)] || "" };
28 }
29
30 JSDOC.Walker.prototype.walk = function(/**JSDOC.TokenStream*/ts) {
31 this.ts = ts;
32 while (this.token = this.ts.look()) {
33 if (this.token.popNamescope) {
34
35 var symbol = this.namescope.pop();
36 if (symbol.is("FUNCTION")) {
37 if (this.ts.look(1).is("LEFT_PAREN") && symbol.comment.getTag("function").length == 0) {
38 symbol.isa = "OBJECT";
39 }
40 }
41 }
42 this.step();
43 if (!this.ts.next()) break;
44 }
45 }
46
47 JSDOC.Walker.prototype.step = function() {
48 if (this.token.is("JSDOC")) { // it's a doc comment
49
50 var doc = new JSDOC.DocComment(this.token.data);
51
52
53 if (doc.getTag("exports").length > 0) {
54 var exports = doc.getTag("exports")[0];
55
56 exports.desc.match(/(\S+) as (\S+)/i);
57 var n1 = RegExp.$1;
58 var n2 = RegExp.$2;
59
60 if (!n1 && n2) throw "@exports tag requires a value like: 'name as ns.name'";
61
62 JSDOC.Parser.rename = (JSDOC.Parser.rename || {});
63 JSDOC.Parser.rename[n1] = n2
64 }
65
66 if (doc.getTag("lends").length > 0) {
67 var lends = doc.getTag("lends")[0];
68
69 var name = lends.desc
70 if (!name) throw "@lends tag requires a value.";
71
72 var symbol = new JSDOC.Symbol(name, [], "OBJECT", doc);
73
74 this.namescope.push(symbol);
75
76 var matching = this.ts.getMatchingToken("LEFT_CURLY");
77 if (matching) matching.popNamescope = name;
78 else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
79
80 this.lastDoc = null;
81 return true;
82 }
83 else if (doc.getTag("name").length > 0 && doc.getTag("overview").length == 0) { // it's a virtual symbol
84 var virtualName = doc.getTag("name")[0].desc;
85 if (!virtualName) throw "@name tag requires a value.";
86
87 if (doc.getTag("memberOf").length > 0) {
88 virtualName = (doc.getTag("memberOf")[0] + "." + virtualName)
89 .replace(/([#.])\./, "$1");
90 doc.deleteTag("memberOf");
91 }
92
93 var symbol = new JSDOC.Symbol(virtualName, [], "VIRTUAL", doc);
94
95 JSDOC.Parser.addSymbol(symbol);
96
97 this.lastDoc = null;
98 return true;
99 }
100 else if (doc.meta) { // it's a meta doclet
101 if (doc.meta == "@+") JSDOC.DocComment.shared = doc.src;
102 else if (doc.meta == "@-") JSDOC.DocComment.shared = "";
103 else if (doc.meta == "nocode+") JSDOC.Parser.conf.ignoreCode = true;
104 else if (doc.meta == "nocode-") JSDOC.Parser.conf.ignoreCode = JSDOC.opt.n;
105 else throw "Unrecognized meta comment: "+doc.meta;
106
107 this.lastDoc = null;
108 return true;
109 }
110 else if (doc.getTag("overview").length > 0) { // it's a file overview
111 symbol = new JSDOC.Symbol("", [], "FILE", doc);
112
113 JSDOC.Parser.addSymbol(symbol);
114
115 this.lastDoc = null;
116 return true;
117 }
118 else {
119 this.lastDoc = doc;
120 return false;
121 }
122 }
123 else if (!JSDOC.Parser.conf.ignoreCode) { // it's code
124 if (this.token.is("NAME")) { // it's the name of something
125 var symbol;
126 var name = this.token.data;
127 var doc = null; if (this.lastDoc) doc = this.lastDoc;
128 var params = [];
129
130 // it's inside an anonymous object
131 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"))) {
132 name = "$anonymous";
133 name = this.namescope.last().alias+"-"+name
134
135 params = [];
136
137 symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
138
139 JSDOC.Parser.addSymbol(symbol);
140
141 this.namescope.push(symbol);
142
143 var matching = this.ts.getMatchingToken(null, "RIGHT_CURLY");
144 if (matching) matching.popNamescope = name;
145 else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
146 }
147 // function foo() {}
148 else if (this.ts.look(-1).is("FUNCTION") && this.ts.look(1).is("LEFT_PAREN")) {
149 var isInner;
150
151 if (this.lastDoc) doc = this.lastDoc;
152
153 if (doc && doc.getTag("memberOf").length > 0) {
154 name = (doc.getTag("memberOf")[0]+"."+name).replace("#.", "#");
155 doc.deleteTag("memberOf");
156 }
157 else {
158 name = this.namescope.last().alias+"-"+name;
159 if (!this.namescope.last().is("GLOBAL")) isInner = true;
160 }
161
162 if (!this.namescope.last().is("GLOBAL")) isInner = true;
163
164 params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
165
166 symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
167 if (isInner) symbol.isInner = true;
168
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
175 JSDOC.Parser.addSymbol(symbol);
176
177 this.namescope.push(symbol);
178
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 = function() {}
184 else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("FUNCTION")) {
185 var constructs;
186 var isConstructor = false;
187 if (doc && (constructs = doc.getTag("constructs")) && constructs.length) {
188 if (constructs[0].desc) {
189 name = constructs[0].desc;
190 isConstructor = true;
191 }
192 }
193
194 var isInner;
195 if (this.ts.look(-1).is("VAR") || this.isInner) {
196 if (doc && doc.getTag("memberOf").length > 0) {
197 name = (doc.getTag("memberOf")[0]+"."+name).replace("#.", "#");
198 doc.deleteTag("memberOf");
199 }
200 else {
201 name = this.namescope.last().alias+"-"+name;
202 if (!this.namescope.last().is("GLOBAL")) isInner = true;
203 }
204 if (!this.namescope.last().is("GLOBAL")) isInner = true;
205 }
206 else if (name.indexOf("this.") == 0) {
207 name = this.resolveThis(name);
208 }
209
210 if (this.lastDoc) doc = this.lastDoc;
211 params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
212
213 symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
214
215 if (isInner) symbol.isInner = true;
216 if (isConstructor) symbol.isa = "CONSTRUCTOR";
217
218 if (this.ts.look(1).is("JSDOC")) {
219 var inlineReturn = ""+this.ts.look(1).data;
220 inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
221 symbol.type = inlineReturn;
222 }
223
224 JSDOC.Parser.addSymbol(symbol);
225
226 this.namescope.push(symbol);
227
228 var matching = this.ts.getMatchingToken("LEFT_CURLY");
229 if (matching) matching.popNamescope = name;
230 else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
231 }
232 // foo = new function() {} or foo = (function() {}
233 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")) {
234 var isInner;
235 if (this.ts.look(-1).is("VAR") || this.isInner) {
236 name = this.namescope.last().alias+"-"+name
237 if (!this.namescope.last().is("GLOBAL")) isInner = true;
238 }
239 else if (name.indexOf("this.") == 0) {
240 name = this.resolveThis(name);
241 }
242
243 this.ts.next(3); // advance past the "new" or "("
244
245 if (this.lastDoc) doc = this.lastDoc;
246 params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
247
248 symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
249 if (isInner) symbol.isInner = true;
250
251 if (this.ts.look(1).is("JSDOC")) {
252 var inlineReturn = ""+this.ts.look(1).data;
253 inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
254 symbol.type = inlineReturn;
255 }
256
257 JSDOC.Parser.addSymbol(symbol);
258
259 symbol.scopeType = "INSTANCE";
260 this.namescope.push(symbol);
261
262 var matching = this.ts.getMatchingToken("LEFT_CURLY");
263 if (matching) matching.popNamescope = name;
264 else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
265 }
266 // foo: function() {}
267 else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("FUNCTION")) {
268 name = (this.namescope.last().alias+"."+name).replace("#.", "#");
269
270 if (this.lastDoc) doc = this.lastDoc;
271 params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
272
273 if (doc && doc.getTag("constructs").length) {
274 name = name.replace(/\.prototype(\.|$)/, "#");
275
276 if (name.indexOf("#") > -1) name = name.match(/(^[^#]+)/)[0];
277 else name = this.namescope.last().alias;
278
279 symbol = new JSDOC.Symbol(name, params, "CONSTRUCTOR", doc);
280 }
281 else {
282 symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
283 }
284
285 if (this.ts.look(1).is("JSDOC")) {
286 var inlineReturn = ""+this.ts.look(1).data;
287 inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
288 symbol.type = inlineReturn;
289 }
290
291 JSDOC.Parser.addSymbol(symbol);
292
293 this.namescope.push(symbol);
294
295 var matching = this.ts.getMatchingToken("LEFT_CURLY");
296 if (matching) matching.popNamescope = name;
297 else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
298 }
299 // foo = {}
300 else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("LEFT_CURLY")) {
301 var isInner;
302 if (this.ts.look(-1).is("VAR") || this.isInner) {
303 name = this.namescope.last().alias+"-"+name
304 if (!this.namescope.last().is("GLOBAL")) isInner = true;
305 }
306 else if (name.indexOf("this.") == 0) {
307 name = this.resolveThis(name);
308 }
309
310 if (this.lastDoc) doc = this.lastDoc;
311
312 symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
313 if (isInner) symbol.isInner = true;
314
315
316 if (doc) JSDOC.Parser.addSymbol(symbol);
317
318 this.namescope.push(symbol);
319
320 var matching = this.ts.getMatchingToken("LEFT_CURLY");
321 if (matching) matching.popNamescope = name;
322 else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
323 }
324 // var foo;
325 else if (this.ts.look(1).is("SEMICOLON")) {
326 var isInner;
327
328 if (this.ts.look(-1).is("VAR") || this.isInner) {
329 name = this.namescope.last().alias+"-"+name
330 if (!this.namescope.last().is("GLOBAL")) isInner = true;
331
332 if (this.lastDoc) doc = this.lastDoc;
333
334 symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
335 if (isInner) symbol.isInner = true;
336
337
338 if (doc) JSDOC.Parser.addSymbol(symbol);
339 }
340 }
341 // foo = x
342 else if (this.ts.look(1).is("ASSIGN")) {
343 var isInner;
344 if (this.ts.look(-1).is("VAR") || this.isInner) {
345 name = this.namescope.last().alias+"-"+name
346 if (!this.namescope.last().is("GLOBAL")) isInner = true;
347 }
348 else if (name.indexOf("this.") == 0) {
349 name = this.resolveThis(name);
350 }
351
352 if (this.lastDoc) doc = this.lastDoc;
353
354 symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
355 if (isInner) symbol.isInner = true;
356
357
358 if (doc) JSDOC.Parser.addSymbol(symbol);
359 }
360 // foo: {}
361 else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("LEFT_CURLY")) {
362 name = (this.namescope.last().alias+"."+name).replace("#.", "#");
363
364 if (this.lastDoc) doc = this.lastDoc;
365
366 symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
367
368
369 if (doc) JSDOC.Parser.addSymbol(symbol);
370
371 this.namescope.push(symbol);
372
373 var matching = this.ts.getMatchingToken("LEFT_CURLY");
374 if (matching) matching.popNamescope = name;
375 else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
376 }
377 // foo: x
378 else if (this.ts.look(1).is("COLON")) {
379 name = (this.namescope.last().alias+"."+name).replace("#.", "#");;
380
381 if (this.lastDoc) doc = this.lastDoc;
382
383 symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
384
385
386 if (doc) JSDOC.Parser.addSymbol(symbol);
387 }
388 // foo(...)
389 else if (this.ts.look(1).is("LEFT_PAREN")) {
390 if (typeof JSDOC.PluginManager != "undefined") {
391 var functionCall = {name: name};
392
393 var cursor = this.ts.cursor;
394 params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
395 this.ts.cursor = cursor;
396
397 for (var i = 0; i < params.length; i++)
398 functionCall["arg" + (i + 1)] = params[i].name;
399
400 JSDOC.PluginManager.run("onFunctionCall", functionCall);
401 if (functionCall.doc) {
402 this.ts.insertAhead(new JSDOC.Token(functionCall.doc, "COMM", "JSDOC"));
403 }
404 }
405 }
406 this.lastDoc = null;
407 }
408 else if (this.token.is("FUNCTION")) { // it's an anonymous function
409 if (
410 (!this.ts.look(-1).is("COLON") || !this.ts.look(-1).is("ASSIGN"))
411 && !this.ts.look(1).is("NAME")
412 ) {
413 if (this.lastDoc) doc = this.lastDoc;
414
415 name = "$anonymous";
416 name = this.namescope.last().alias+"-"+name
417
418 params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
419
420 symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
421
422 JSDOC.Parser.addSymbol(symbol);
423
424 this.namescope.push(symbol);
425
426 var matching = this.ts.getMatchingToken("LEFT_CURLY");
427 if (matching) matching.popNamescope = name;
428 else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
429 }
430 }
431 }
432 return true;
433 }
434
435 /**
436 Resolves what "this." means when it appears in a name.
437 @param name The name that starts with "this.".
438 @returns The name with "this." resolved.
439 */
440 JSDOC.Walker.prototype.resolveThis = function(name) {
441 name.match(/^this\.(.+)$/)
442 var nameFragment = RegExp.$1;
443 if (!nameFragment) return name;
444
445 var symbol = this.namescope.last();
446 var scopeType = symbol.scopeType || symbol.isa;
447
448 // if we are in a constructor function, `this` means the instance
449 if (scopeType == "CONSTRUCTOR") {
450 name = symbol.alias+"#"+nameFragment;
451 }
452
453 // if we are in an anonymous constructor function, `this` means the instance
454 else if (scopeType == "INSTANCE") {
455 name = symbol.alias+"."+nameFragment;
456 }
457
458 // if we are in a function, `this` means the container (possibly the global)
459 else if (scopeType == "FUNCTION") {
460 // in a method of a prototype, so `this` means the constructor
461 if (symbol.alias.match(/(^.*)[#.-][^#.-]+/)) {
462 var parentName = RegExp.$1;
463 var parent = JSDOC.Parser.symbols.getSymbol(parentName);
464
465 if (!parent) {
466 if (JSDOC.Lang.isBuiltin(parentName)) parent = JSDOC.Parser.addBuiltin(parentName);
467 else {
468 if (symbol.alias.indexOf("$anonymous") < 0) // these will be ignored eventually
469 LOG.warn("Trying to document "+symbol.alias+" without first documenting "+parentName+".");
470 }
471 }
472 if (parent) name = parentName+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
473 }
474 else {
475 parent = this.namescope.last(1);
476 name = parent.alias+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
477 }
478 }
479 // otherwise it means the global
480 else {
481 name = nameFragment;
482 }
483
484 return name;
485 }
486
487 JSDOC.Walker.onParamList = function(/**Array*/paramTokens) {
488 if (!paramTokens) {
489 LOG.warn("Malformed parameter list. Can't parse code.");
490 return [];
491 }
492 var params = [];
493 for (var i = 0, l = paramTokens.length; i < l; i++) {
494 if (paramTokens[i].is("JSDOC")) {
495 var paramType = paramTokens[i].data.replace(/(^\/\*\* *| *\*\/$)/g, "");
496
497 if (paramTokens[i+1] && paramTokens[i+1].is("NAME")) {
498 i++;
499 params.push({type: paramType, name: paramTokens[i].data});
500 }
501 }
502 else if (paramTokens[i].is("NAME")) {
503 params.push({name: paramTokens[i].data});
504 }
505 }
506 return params;
507 }