1 if (typeof JSDOC
== "undefined") JSDOC
= {};
4 JSDOC
.Walker
= function(/**JSDOC.TokenStream*/ts
) {
6 if (typeof ts
!= "undefined") {
11 JSDOC
.Walker
.prototype.init
= function() {
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
);
23 The chain of symbols under which we are currently nested.
26 this.namescope
= [globalSymbol
];
27 this.namescope
.last
= function(n
){ if (!n
) n
= 0; return this[this.length
-(1+n
)] || "" };
30 JSDOC
.Walker
.prototype.walk
= function(/**JSDOC.TokenStream*/ts
) {
32 while (this.token
= this.ts
.look()) {
33 if (this.token
.popNamescope
) {
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";
43 if (!this.ts
.next()) break;
47 JSDOC
.Walker
.prototype.step
= function() {
48 if (this.token
.is("JSDOC")) { // it's a doc comment
50 var doc
= new JSDOC
.DocComment(this.token
.data
);
53 if (doc
.getTag("exports").length
> 0) {
54 var exports
= doc
.getTag("exports")[0];
56 exports
.desc
.match(/(\S+) as (\S+)/i);
60 if (!n1
&& n2
) throw "@exports tag requires a value like: 'name as ns.name'";
62 JSDOC
.Parser
.rename
= (JSDOC
.Parser
.rename
|| {});
63 JSDOC
.Parser
.rename
[n1
] = n2
66 if (doc
.getTag("lends").length
> 0) {
67 var lends
= doc
.getTag("lends")[0];
70 if (!name
) throw "@lends tag requires a value.";
72 var symbol
= new JSDOC
.Symbol(name
, [], "OBJECT", doc
);
74 this.namescope
.push(symbol
);
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
+ ".");
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.";
87 if (doc
.getTag("memberOf").length
> 0) {
88 virtualName
= (doc
.getTag("memberOf")[0] + "." + virtualName
)
89 .replace(/([#.])\./, "$1");
90 doc
.deleteTag("memberOf");
93 var symbol
= new JSDOC
.Symbol(virtualName
, [], "VIRTUAL", doc
);
95 JSDOC
.Parser
.addSymbol(symbol
);
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
;
110 else if (doc
.getTag("overview").length
> 0) { // it's a file overview
111 symbol
= new JSDOC
.Symbol("", [], "FILE", doc
);
113 JSDOC
.Parser
.addSymbol(symbol
);
123 else if (!JSDOC
.Parser
.conf
.ignoreCode
) { // it's code
124 if (this.token
.is("NAME")) { // it's the name of something
126 var name
= this.token
.data
;
127 var doc
= null; if (this.lastDoc
) doc
= this.lastDoc
;
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"))) {
133 name
= this.namescope
.last().alias
+"-"+name
137 symbol
= new JSDOC
.Symbol(name
, params
, "OBJECT", doc
);
139 JSDOC
.Parser
.addSymbol(symbol
);
141 this.namescope
.push(symbol
);
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
+ ".");
148 else if (this.ts
.look(-1).is("FUNCTION") && this.ts
.look(1).is("LEFT_PAREN")) {
151 if (this.lastDoc
) doc
= this.lastDoc
;
153 if (doc
&& doc
.getTag("memberOf").length
> 0) {
154 name
= (doc
.getTag("memberOf")[0]+"."+name
).replace("#.", "#");
155 doc
.deleteTag("memberOf");
158 name
= this.namescope
.last().alias
+"-"+name
;
159 if (!this.namescope
.last().is("GLOBAL")) isInner
= true;
162 if (!this.namescope
.last().is("GLOBAL")) isInner
= true;
164 params
= JSDOC
.Walker
.onParamList(this.ts
.balance("LEFT_PAREN"));
166 symbol
= new JSDOC
.Symbol(name
, params
, "FUNCTION", doc
);
167 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
;
175 JSDOC
.Parser
.addSymbol(symbol
);
177 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
+ ".");
183 // foo = function() {}
184 else if (this.ts
.look(1).is("ASSIGN") && this.ts
.look(2).is("FUNCTION")) {
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;
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");
201 name
= this.namescope
.last().alias
+"-"+name
;
202 if (!this.namescope
.last().is("GLOBAL")) isInner
= true;
204 if (!this.namescope
.last().is("GLOBAL")) isInner
= true;
206 else if (name
.indexOf("this.") == 0) {
207 name
= this.resolveThis(name
);
210 if (this.lastDoc
) doc
= this.lastDoc
;
211 params
= JSDOC
.Walker
.onParamList(this.ts
.balance("LEFT_PAREN"));
213 symbol
= new JSDOC
.Symbol(name
, params
, "FUNCTION", doc
);
215 if (isInner
) symbol
.isInner
= true;
216 if (isConstructor
) symbol
.isa
= "CONSTRUCTOR";
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
;
224 JSDOC
.Parser
.addSymbol(symbol
);
226 this.namescope
.push(symbol
);
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
+ ".");
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")) {
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;
239 else if (name
.indexOf("this.") == 0) {
240 name
= this.resolveThis(name
);
243 this.ts
.next(3); // advance past the "new" or "("
245 if (this.lastDoc
) doc
= this.lastDoc
;
246 params
= JSDOC
.Walker
.onParamList(this.ts
.balance("LEFT_PAREN"));
248 symbol
= new JSDOC
.Symbol(name
, params
, "OBJECT", doc
);
249 if (isInner
) symbol
.isInner
= true;
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
;
257 JSDOC
.Parser
.addSymbol(symbol
);
259 symbol
.scopeType
= "INSTANCE";
260 this.namescope
.push(symbol
);
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
+ ".");
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("#.", "#");
270 if (this.lastDoc
) doc
= this.lastDoc
;
271 params
= JSDOC
.Walker
.onParamList(this.ts
.balance("LEFT_PAREN"));
273 if (doc
&& doc
.getTag("constructs").length
) {
274 name
= name
.replace(/\.prototype(\.|$)/, "#");
276 if (name
.indexOf("#") > -1) name
= name
.match(/(^[^#]+)/)[0];
277 else name
= this.namescope
.last().alias
;
279 symbol
= new JSDOC
.Symbol(name
, params
, "CONSTRUCTOR", doc
);
282 symbol
= new JSDOC
.Symbol(name
, params
, "FUNCTION", doc
);
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
;
291 JSDOC
.Parser
.addSymbol(symbol
);
293 this.namescope
.push(symbol
);
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
+ ".");
300 else if (this.ts
.look(1).is("ASSIGN") && this.ts
.look(2).is("LEFT_CURLY")) {
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;
306 else if (name
.indexOf("this.") == 0) {
307 name
= this.resolveThis(name
);
310 if (this.lastDoc
) doc
= this.lastDoc
;
312 symbol
= new JSDOC
.Symbol(name
, params
, "OBJECT", doc
);
313 if (isInner
) symbol
.isInner
= true;
316 if (doc
) JSDOC
.Parser
.addSymbol(symbol
);
318 this.namescope
.push(symbol
);
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
+ ".");
325 else if (this.ts
.look(1).is("SEMICOLON")) {
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;
332 if (this.lastDoc
) doc
= this.lastDoc
;
334 symbol
= new JSDOC
.Symbol(name
, params
, "OBJECT", doc
);
335 if (isInner
) symbol
.isInner
= true;
338 if (doc
) JSDOC
.Parser
.addSymbol(symbol
);
342 else if (this.ts
.look(1).is("ASSIGN")) {
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;
348 else if (name
.indexOf("this.") == 0) {
349 name
= this.resolveThis(name
);
352 if (this.lastDoc
) doc
= this.lastDoc
;
354 symbol
= new JSDOC
.Symbol(name
, params
, "OBJECT", doc
);
355 if (isInner
) symbol
.isInner
= true;
358 if (doc
) JSDOC
.Parser
.addSymbol(symbol
);
361 else if (this.ts
.look(1).is("COLON") && this.ts
.look(2).is("LEFT_CURLY")) {
362 name
= (this.namescope
.last().alias
+"."+name
).replace("#.", "#");
364 if (this.lastDoc
) doc
= this.lastDoc
;
366 symbol
= new JSDOC
.Symbol(name
, params
, "OBJECT", doc
);
369 if (doc
) JSDOC
.Parser
.addSymbol(symbol
);
371 this.namescope
.push(symbol
);
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
+ ".");
378 else if (this.ts
.look(1).is("COLON")) {
379 name
= (this.namescope
.last().alias
+"."+name
).replace("#.", "#");;
381 if (this.lastDoc
) doc
= this.lastDoc
;
383 symbol
= new JSDOC
.Symbol(name
, params
, "OBJECT", doc
);
386 if (doc
) JSDOC
.Parser
.addSymbol(symbol
);
389 else if (this.ts
.look(1).is("LEFT_PAREN")) {
390 if (typeof JSDOC
.PluginManager
!= "undefined") {
391 var functionCall
= {name
: name
};
393 var cursor
= this.ts
.cursor
;
394 params
= JSDOC
.Walker
.onParamList(this.ts
.balance("LEFT_PAREN"));
395 this.ts
.cursor
= cursor
;
397 for (var i
= 0; i
< params
.length
; i
++)
398 functionCall
["arg" + (i
+ 1)] = params
[i
].name
;
400 JSDOC
.PluginManager
.run("onFunctionCall", functionCall
);
401 if (functionCall
.doc
) {
402 this.ts
.insertAhead(new JSDOC
.Token(functionCall
.doc
, "COMM", "JSDOC"));
408 else if (this.token
.is("FUNCTION")) { // it's an anonymous function
410 (!this.ts
.look(-1).is("COLON") || !this.ts
.look(-1).is("ASSIGN"))
411 && !this.ts
.look(1).is("NAME")
413 if (this.lastDoc
) doc
= this.lastDoc
;
416 name
= this.namescope
.last().alias
+"-"+name
418 params
= JSDOC
.Walker
.onParamList(this.ts
.balance("LEFT_PAREN"));
420 symbol
= new JSDOC
.Symbol(name
, params
, "FUNCTION", doc
);
422 JSDOC
.Parser
.addSymbol(symbol
);
424 this.namescope
.push(symbol
);
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
+ ".");
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.
440 JSDOC
.Walker
.prototype.resolveThis
= function(name
) {
441 name
.match(/^this\.(.+)$/)
442 var nameFragment
= RegExp
.$1;
443 if (!nameFragment
) return name
;
445 var symbol
= this.namescope
.last();
446 var scopeType
= symbol
.scopeType
|| symbol
.isa
;
448 // if we are in a constructor function, `this` means the instance
449 if (scopeType
== "CONSTRUCTOR") {
450 name
= symbol
.alias
+"#"+nameFragment
;
453 // if we are in an anonymous constructor function, `this` means the instance
454 else if (scopeType
== "INSTANCE") {
455 name
= symbol
.alias
+"."+nameFragment
;
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
);
466 if (JSDOC
.Lang
.isBuiltin(parentName
)) parent
= JSDOC
.Parser
.addBuiltin(parentName
);
468 if (symbol
.alias
.indexOf("$anonymous") < 0) // these will be ignored eventually
469 LOG
.warn("Trying to document "+symbol
.alias
+" without first documenting "+parentName
+".");
472 if (parent
) name
= parentName
+(parent
.is("CONSTRUCTOR")?"#":".")+nameFragment
;
475 parent
= this.namescope
.last(1);
476 name
= parent
.alias
+(parent
.is("CONSTRUCTOR")?"#":".")+nameFragment
;
479 // otherwise it means the global
487 JSDOC
.Walker
.onParamList
= function(/**Array*/paramTokens
) {
489 LOG
.warn("Malformed parameter list. Can't parse code.");
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, "");
497 if (paramTokens
[i
+1] && paramTokens
[i
+1].is("NAME")) {
499 params
.push({type
: paramType
, name
: paramTokens
[i
].data
});
502 else if (paramTokens
[i
].is("NAME")) {
503 params
.push({name
: paramTokens
[i
].data
});