PageRenderTime 46ms CodeModel.GetById 17ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

/tags/jsdoc_toolkit-1.3.3/app/JsParse.js

http://jsdoc-toolkit.googlecode.com/
JavaScript | 359 lines | 243 code | 47 blank | 69 comment | 94 complexity | 0e902a89888506b8bafe3932ca63e703 MD5 | raw file
  1/**
  2 * @fileOverview
  3 * @name JsParse
  4 * @author Michael Mathews micmath@gmail.com
  5 * @url $HeadURL: http://jsdoc-toolkit.googlecode.com/svn/tags/jsdoc_toolkit-1.3.3/app/JsParse.js $
  6 * @revision $Id: JsParse.js 236 2007-09-17 23:10:39Z micmath $
  7 * @license <a href="http://en.wikipedia.org/wiki/MIT_License">X11/MIT License</a>
  8 *          (See the accompanying README file for full details.)
  9 */
 10
 11/**
 12 * @class Find objects in tokenized JavaScript source code.
 13 * @constructor
 14 * @author Michael Mathews <a href="mailto:micmath@gmail.com">micmath@gmail.com</a>
 15 */
 16function JsParse() {};
 17
 18/**
 19 * Populate the symbols array with symbols found in the
 20 * given token stream.
 21 * @param {TokenStream} tokenStream
 22 */
 23JsParse.prototype.parse = function(tokenStream) {
 24	/** 
 25	 * All symbols found in the tokenStream.
 26	 * @type Symbol[]
 27	 */
 28	this.symbols = [];
 29	
 30	/** 
 31	 * Overview found in the tokenStream.
 32	 * @type Symbol
 33	 */
 34	this.overview = null;
 35	
 36	while(tokenStream.next()) {
 37		if (this._findDocComment(tokenStream)) continue;
 38		if (this._findFunction(tokenStream)) continue;
 39		if (this._findVariable(tokenStream)) continue;
 40	}
 41}
 42
 43/**
 44 * Try to find a JsDoc comment in the tokenStream.
 45 * @param {TokenStream} ts
 46 * @return {boolean} Was a JsDoc comment found?
 47 */
 48JsParse.prototype._findDocComment = function(ts) {
 49	if (ts.look().is("JSDOC")) {
 50		var doc = ts.look().data;
 51		if (/@(projectdescription|(file)?overview)\b/i.test(doc)) {
 52			this.overview = new Symbol("", [], "FILE", doc);
 53			delete ts.tokens[ts.cursor];
 54			return true;
 55		}
 56		else if (/@name\s+([a-z0-9_$.]+)\s*/i.test(doc)) {
 57			this.symbols.push(new Symbol(RegExp.$1, [], SYM.VIRTUAL, doc));
 58			delete ts.tokens[ts.cursor];
 59			return true;
 60		}
 61		else if (/@scope\s+([a-z0-9_$.]+)\s*/i.test(doc)) {
 62			var scope = RegExp.$1;
 63			if (scope) {
 64				scope = scope.replace(/\.prototype\b/, "/");
 65				this._onObLiteral(scope, new TokenStream(ts.balance("LEFT_CURLY")));
 66				return true;
 67			}
 68		}
 69	}
 70	return false;
 71}
 72
 73/**
 74 * Try to find a function definition in the tokenStream
 75 * @param {TokenStream} ts
 76 * @return {boolean} Was a function definition found?
 77 */
 78JsParse.prototype._findFunction = function(ts) {
 79	if (ts.look().is("NAME")) {
 80		var name = ts.look().data;
 81		var doc = "";
 82		var isa = null;
 83		var body = "";
 84		var paramTokens = [];
 85		var params = [];
 86		
 87		// like function foo()
 88		if (ts.look(-1).is("FUNCTION")) {
 89			isa = SYM.FUNCTION;
 90			
 91			if (ts.look(-2).is("JSDOC")) {
 92				doc = ts.look(-2).data;
 93			}
 94			paramTokens = ts.balance("LEFT_PAREN");
 95			body = ts.balance("LEFT_CURLY");
 96		}
 97		
 98		// like var foo = function()
 99		else if (ts.look(1).is("ASSIGN") && ts.look(2).is("FUNCTION")) {
100			isa = SYM.FUNCTION;
101			
102			if (ts.look(-1).is("VAR") && ts.look(-2).is("JSDOC")) {
103				doc = ts.look(-2).data;
104			}
105			else if (ts.look(-1).is("JSDOC")) {
106				doc = ts.look(-1).data;
107			}
108			paramTokens = ts.balance("LEFT_PAREN");
109			body = ts.balance("LEFT_CURLY");
110			
111			// like foo = function(n) {return n}(42)
112			if (ts.look(1).is("LEFT_PAREN")) {
113				isa = SYM.OBJECT;
114				
115				ts.balance("LEFT_PAREN");
116				if (doc) { // we only keep these if they're documented
117					name = name.replace(/\.prototype\.?/, "/");
118						
119					if (!/\/$/.test(name)) { // assigning to prototype of already existing symbol
120						this.symbols.push(new Symbol(name, [], isa, doc));
121					}
122				}
123				this._onFnBody(name, new TokenStream(body));
124				return true;
125			}
126		}
127		
128		// like var foo = new function()
129		else if (ts.look(1).is("ASSIGN") && ts.look(2).is("NEW") && ts.look(3).is("FUNCTION")) {
130			isa = SYM.OBJECT;
131		
132			if (ts.look(-1).is("VAR") && ts.look(-2).is("JSDOC")) {
133				doc = ts.look(-2).data;
134			}
135			else if (ts.look(-1).is("JSDOC")) {
136				doc = ts.look(-1).data;
137			}
138			
139			paramTokens = ts.balance("LEFT_PAREN");
140			body = ts.balance("LEFT_CURLY");
141			if (doc) { // we only keep these if they're documented
142				name = name.replace(/\.prototype\.?/, "/");
143						
144				if (!/\/$/.test(name)) { // assigning to prototype of already existing symbol
145					this.symbols.push(new Symbol(name, [], isa, doc));
146				}
147			}
148			this._onFnBody(name, new TokenStream(body));
149			return true;
150		}
151		
152		if (isa && name) {
153			if (isa == SYM.FUNCTION) {
154				for (var i = 0; i < paramTokens.length; i++) {
155					if (paramTokens[i].is("NAME"))
156						params.push(paramTokens[i].data);
157				}
158			}
159			
160			// like Foo.bar.prototype.baz = function() {}
161			var ns = name;
162			if (name.indexOf(".prototype") > 0) {
163				isa = SYM.FUNCTION;
164				name = name.replace(/\.prototype\.?/, "/");
165			}
166			
167			this.symbols.push(new Symbol(name, params, isa, doc));
168			
169			if (body) {
170				if (ns.indexOf(".prototype") > 0) {
171					if (/@constructor\b/.test(doc)) {
172						ns = ns.replace(/\.prototype\.?/, "/");
173					}
174					else {
175						ns = ns.replace(/\.prototype\.[^.]+$/, "/");
176					}
177				}
178				this._onFnBody(ns, new TokenStream(body));
179			}
180			return true;
181		}
182	}
183	return false;
184}
185
186/**
187 * Try to find a variable definition in the tokenStream
188 * @param {TokenStream} ts
189 * @return {boolean} Was a variable definition found?
190 */
191JsParse.prototype._findVariable = function(ts) {
192	if (ts.look().is("NAME") && ts.look(1).is("ASSIGN")) {
193		// like var foo = 1
194		var name = ts.look().data;
195		isa = SYM.OBJECT;
196		
197		var doc;
198		if (ts.look(-1).is("JSDOC")) doc = ts.look(-1).data;
199		else if (ts.look(-1).is("VAR") && ts.look(-2).is("JSDOC")) doc = ts.look(-2).data;
200		name = name.replace(/\.prototype\.?/, "/");
201		
202		if (doc) { // we only keep these if they're documented
203			if (!/\/$/.test(name)) { // assigning to prototype of already existing symbol
204				this.symbols.push(new Symbol(name, [], isa, doc));
205			}
206			if (/@class\b/i.test(doc)) {
207				name = name +"/";
208			}
209		}
210		
211		// like foo = {
212		if (ts.look(2).is("LEFT_CURLY")) {
213			this._onObLiteral(name, new TokenStream(ts.balance("LEFT_CURLY")));
214		}
215		return true;
216	}
217	return false;
218}
219
220/**
221 * Handle sub-parsing of the content within an object literal.
222 * @private
223 * @param {String} nspace The name attached to this object.
224 * @param {TokenStream} ts The content of the object literal.
225 */
226JsParse.prototype._onObLiteral = function(nspace, ts) {
227	while (ts.next()) {
228		if (this._findDocComment(ts)) {
229		
230		}
231		else if (ts.look().is("NAME") && ts.look(1).is("COLON")) {
232			var name = nspace+((nspace.charAt(nspace.length-1)=="/")?"":".")+ts.look().data;
233			
234			// like foo: function
235			if (ts.look(2).is("FUNCTION")) {
236				var isa = SYM.FUNCTION;
237				var doc = "";
238				
239				if (ts.look(-1).is("JSDOC")) doc = ts.look(-1).data;
240				
241				var paramTokens = ts.balance("LEFT_PAREN");
242				var params = [];
243				for (var i = 0; i < paramTokens.length; i++) {
244					if (paramTokens[i].is("NAME"))
245						params.push(paramTokens[i].data);
246				}
247				
248				var body = ts.balance("LEFT_CURLY");
249
250				this.symbols.push(new Symbol(name, params, isa, doc));
251				
252				// find methods in the body of this function
253				this._onFnBody(name, new TokenStream(body));
254			}
255			// like foo: {...}
256			else if (ts.look(2).is("LEFT_CURLY")) { // another nested object literal
257				if (ts.look(-1).is("JSDOC")) {
258					var isa = SYM.OBJECT;
259					var doc = ts.look(-1).data;
260
261					this.symbols.push(new Symbol(name, [], isa, doc));
262				}
263				
264				this._onObLiteral(name, new TokenStream(ts.balance("LEFT_CURLY"))); // recursive
265			}
266			else { // like foo: 1, or foo: "one"
267				if (ts.look(-1).is("JSDOC")) { // we only grab these if they are documented
268					var isa = SYM.OBJECT;
269					var doc = ts.look(-1).data;
270					
271					this.symbols.push(new Symbol(name, [], isa, doc));
272				}
273				
274				while (!ts.look().is("COMMA")) { // skip to end of RH value ignoring things like bar({blah, blah})
275					if (ts.look().is("LEFT_PAREN")) ts.balance("LEFT_PAREN");
276					else if (ts.look().is("LEFT_CURLY")) ts.balance("LEFT_CURLY");
277					else if (!ts.next()) break;
278				}
279			}
280		}
281	}
282}
283
284/**
285 * Handle sub-parsing of the content within a function body.
286 * @private
287 * @param {String} nspace The name attached to this function.
288 * @param {TokenStream} fs The content of the function body.
289 */
290JsParse.prototype._onFnBody = function(nspace, fs) {
291	while (fs.look()) {
292		if (this._findDocComment(fs)) {
293		
294		}
295		else if (fs.look().is("NAME") && fs.look(1).is("ASSIGN")) {
296			var name = fs.look().data;
297			
298			// like this.foo =
299			if (name.indexOf("this.") == 0) {
300				// like this.foo = function
301				if (fs.look(2).is("FUNCTION")) {
302					var isa = SYM.FUNCTION;
303					var doc = (fs.look(-1).is("JSDOC"))? fs.look(-1).data : "";
304					name = name.replace(/^this\./, (nspace+"/").replace("//", "/"))
305					
306					var paramTokens = fs.balance("LEFT_PAREN");
307					var params = [];
308					for (var i = 0; i < paramTokens.length; i++) {
309						if (paramTokens[i].is("NAME")) params.push(paramTokens[i].data);
310					}
311					
312					body = fs.balance("LEFT_CURLY");
313
314					// like this.foo = function(n) {return n}(42)
315					if (fs.look(1).is("LEFT_PAREN")) { // false alarm, it's not really a named function definition
316						isa = SYM.OBJECT;
317						fs.balance("LEFT_PAREN");
318						if (doc) { // we only grab these if they are documented
319							this.symbols.push(
320								new Symbol(name, [], isa, doc)
321							);
322						}
323						break;
324					}
325
326					this.symbols.push(
327						new Symbol(name, params, isa, doc)
328					);
329					
330					if (body) {
331						this._onFnBody(name, new TokenStream(body)); // recursive
332					}
333				}
334				else {
335					var isa = SYM.OBJECT;
336					var doc = (fs.look(-1).is("JSDOC"))? fs.look(-1).data : "";
337					name = name.replace(/^this\./, (nspace+"/").replace("//", "/"))
338						
339					if (doc) {
340						this.symbols.push(
341							new Symbol(name, [], isa, doc)
342						);
343					}
344						
345					// like this.foo = { ... }
346					if (fs.look(2).is("LEFT_CURLY")) {
347						var literal = fs.balance("LEFT_CURLY");
348						this._onObLiteral(name, new TokenStream(literal));
349					}
350				}
351			}
352			// like <thisfunction>.prototype.foo =
353			else if (name.indexOf(nspace+".prototype.") == 0) {
354				this._findFunction(fs);
355			}
356		}
357		if (!fs.next()) break;
358	}
359}