Commit | Line | Data |
---|---|---|
629a09ae DV |
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 | } |