PageRenderTime 60ms CodeModel.GetById 16ms app.highlight 39ms RepoModel.GetById 1ms app.codeStats 0ms

/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
  1if (typeof JSDOC == "undefined") JSDOC = {};
  2
  3/** @constructor */
  4JSDOC.Walker = function(/**JSDOC.TokenStream*/ts) {
  5	this.init();
  6	if (typeof ts != "undefined") {
  7		this.walk(ts);
  8	}
  9}
 10
 11JSDOC.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
 30JSDOC.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
 47JSDOC.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 */
440JSDOC.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
487JSDOC.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}