PageRenderTime 56ms CodeModel.GetById 16ms app.highlight 36ms RepoModel.GetById 1ms app.codeStats 0ms

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

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