PageRenderTime 159ms CodeModel.GetById 131ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

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

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