thirdparty/jsdoctoolkit/app/lib/JSDOC/Walker.js
changeset 3041 c8f47f0b6697
equal deleted inserted replaced
3040:8f9580309846 3041:c8f47f0b6697
       
     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 			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")) { // it's the name of something
       
   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() {} or foo = (function() {}
       
   200 			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")) {
       
   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 				this.ts.next(3); // advance past the "new" or "("
       
   211 				
       
   212 				if (this.lastDoc) doc = this.lastDoc;
       
   213 				params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
       
   214 				
       
   215 				symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
       
   216 				if (isInner) symbol.isInner = true;
       
   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 				symbol.scopeType = "INSTANCE";
       
   227 				this.namescope.push(symbol);
       
   228 				
       
   229 				var matching = this.ts.getMatchingToken("LEFT_CURLY");
       
   230 				if (matching) matching.popNamescope = name;
       
   231 				else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
       
   232 			}
       
   233 			// foo: function() {}
       
   234 			else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("FUNCTION")) {
       
   235 				name = (this.namescope.last().alias+"."+name).replace("#.", "#");
       
   236 				
       
   237 				if (this.lastDoc) doc = this.lastDoc;
       
   238 				params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
       
   239 				
       
   240 				if (doc && doc.getTag("constructs").length) {
       
   241 					name = name.replace(/\.prototype(\.|$)/, "#");
       
   242 					
       
   243 					if (name.indexOf("#") > -1) name = name.match(/(^[^#]+)/)[0];
       
   244 					else name = this.namescope.last().alias;
       
   245 
       
   246 					symbol = new JSDOC.Symbol(name, params, "CONSTRUCTOR", doc);
       
   247 				}
       
   248 				else {
       
   249 					symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
       
   250 				}
       
   251 				
       
   252 				if (this.ts.look(1).is("JSDOC")) {
       
   253 					var inlineReturn = ""+this.ts.look(1).data;
       
   254 					inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
       
   255 					symbol.type = inlineReturn;
       
   256 				}
       
   257 				
       
   258 				JSDOC.Parser.addSymbol(symbol);
       
   259 				
       
   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 = {}
       
   267 			else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("LEFT_CURLY")) {
       
   268 				var isInner;
       
   269 				if (this.ts.look(-1).is("VAR") || this.isInner) {
       
   270 					name = this.namescope.last().alias+"-"+name
       
   271 					if (!this.namescope.last().is("GLOBAL")) isInner = true;
       
   272 				}
       
   273 				else if (name.indexOf("this.") == 0) {
       
   274 					name = this.resolveThis(name);
       
   275 				}
       
   276 				
       
   277 				if (this.lastDoc) doc = this.lastDoc;
       
   278 				
       
   279 				symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
       
   280 				if (isInner) symbol.isInner = true;
       
   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 in file " + symbol.srcFile + ".");
       
   290 			}
       
   291 			// var foo;
       
   292 			else if (this.ts.look(1).is("SEMICOLON")) {
       
   293 				var isInner;
       
   294 
       
   295 				if (this.ts.look(-1).is("VAR") || this.isInner) {
       
   296 					name = this.namescope.last().alias+"-"+name
       
   297 					if (!this.namescope.last().is("GLOBAL")) isInner = true;
       
   298 					
       
   299 					if (this.lastDoc) doc = this.lastDoc;
       
   300 				
       
   301 					symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
       
   302 					if (isInner) symbol.isInner = true;
       
   303 					
       
   304 				
       
   305 					if (doc) JSDOC.Parser.addSymbol(symbol);
       
   306 				}
       
   307 			}
       
   308 			// foo = x
       
   309 			else if (this.ts.look(1).is("ASSIGN")) {				
       
   310 				var isInner;
       
   311 				if (this.ts.look(-1).is("VAR") || this.isInner) {
       
   312 					name = this.namescope.last().alias+"-"+name
       
   313 					if (!this.namescope.last().is("GLOBAL")) isInner = true;
       
   314 				}
       
   315 				else if (name.indexOf("this.") == 0) {
       
   316 					name = this.resolveThis(name);
       
   317 				}
       
   318 				
       
   319 				if (this.lastDoc) doc = this.lastDoc;
       
   320 				
       
   321 				symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
       
   322 				if (isInner) symbol.isInner = true;
       
   323 				
       
   324 			
       
   325 				if (doc) JSDOC.Parser.addSymbol(symbol);
       
   326 			}
       
   327 			// foo: {}
       
   328 			else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("LEFT_CURLY")) {
       
   329 				name = (this.namescope.last().alias+"."+name).replace("#.", "#");
       
   330 				
       
   331 				if (this.lastDoc) doc = this.lastDoc;
       
   332 				
       
   333 				symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
       
   334 				
       
   335 			
       
   336 				if (doc) JSDOC.Parser.addSymbol(symbol);
       
   337 				
       
   338 				this.namescope.push(symbol);
       
   339 				
       
   340 				var matching = this.ts.getMatchingToken("LEFT_CURLY");
       
   341 				if (matching) matching.popNamescope = name;
       
   342 				else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
       
   343 			}
       
   344 			// foo: x
       
   345 			else if (this.ts.look(1).is("COLON")) {
       
   346 				name = (this.namescope.last().alias+"."+name).replace("#.", "#");;
       
   347 				
       
   348 				if (this.lastDoc) doc = this.lastDoc;
       
   349 				
       
   350 				symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
       
   351 				
       
   352 			
       
   353 				if (doc) JSDOC.Parser.addSymbol(symbol);
       
   354 			}
       
   355 			// foo(...)
       
   356 			else if (this.ts.look(1).is("LEFT_PAREN")) {
       
   357 				if (typeof JSDOC.PluginManager != "undefined") {
       
   358 					var functionCall = {name: name};
       
   359 				
       
   360 					var cursor = this.ts.cursor;
       
   361 					params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
       
   362 					this.ts.cursor = cursor;
       
   363 					
       
   364 					for (var i = 0; i < params.length; i++)
       
   365 						functionCall["arg" + (i + 1)] = params[i].name;
       
   366 				
       
   367 					JSDOC.PluginManager.run("onFunctionCall", functionCall);
       
   368 					if (functionCall.doc) {
       
   369 						this.ts.insertAhead(new JSDOC.Token(functionCall.doc, "COMM", "JSDOC"));
       
   370 					}
       
   371 				}
       
   372 			}
       
   373 			this.lastDoc = null;
       
   374 		}
       
   375 		else if (this.token.is("FUNCTION")) { // it's an anonymous function
       
   376 			if (
       
   377 				(!this.ts.look(-1).is("COLON") || !this.ts.look(-1).is("ASSIGN"))
       
   378 				&& !this.ts.look(1).is("NAME")
       
   379 			) {
       
   380 				if (this.lastDoc) doc = this.lastDoc;
       
   381 				
       
   382 				name = "$anonymous";
       
   383 				name = this.namescope.last().alias+"-"+name
       
   384 				
       
   385 				params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
       
   386 				
       
   387 				symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
       
   388 				
       
   389 				JSDOC.Parser.addSymbol(symbol);
       
   390 				
       
   391 				this.namescope.push(symbol);
       
   392 				
       
   393 				var matching = this.ts.getMatchingToken("LEFT_CURLY");
       
   394 				if (matching) matching.popNamescope = name;
       
   395 				else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
       
   396 			}
       
   397 		}
       
   398 	}
       
   399 	return true;
       
   400 }
       
   401 
       
   402 /**
       
   403 	Resolves what "this." means when it appears in a name.
       
   404 	@param name The name that starts with "this.".
       
   405 	@returns The name with "this." resolved.
       
   406  */
       
   407 JSDOC.Walker.prototype.resolveThis = function(name) {
       
   408 	name.match(/^this\.(.+)$/)
       
   409 	var nameFragment = RegExp.$1;
       
   410 	if (!nameFragment) return name;
       
   411 	
       
   412 	var symbol = this.namescope.last();
       
   413 	var scopeType = symbol.scopeType || symbol.isa;
       
   414 
       
   415 	// if we are in a constructor function, `this` means the instance
       
   416 	if (scopeType == "CONSTRUCTOR") {
       
   417 		name = symbol.alias+"#"+nameFragment;
       
   418 	}
       
   419 	
       
   420 	// if we are in an anonymous constructor function, `this` means the instance
       
   421 	else if (scopeType == "INSTANCE") {
       
   422 		name = symbol.alias+"."+nameFragment;
       
   423 	}
       
   424 	
       
   425 	// if we are in a function, `this` means the container (possibly the global)
       
   426 	else if (scopeType == "FUNCTION") {
       
   427 		// in a method of a prototype, so `this` means the constructor
       
   428 		if (symbol.alias.match(/(^.*)[#.-][^#.-]+/)) {
       
   429 			var parentName = RegExp.$1;
       
   430 			var parent = JSDOC.Parser.symbols.getSymbol(parentName);
       
   431 
       
   432 			if (!parent) {
       
   433 				if (JSDOC.Lang.isBuiltin(parentName)) parent = JSDOC.Parser.addBuiltin(parentName);
       
   434 				else {
       
   435 					if (symbol.alias.indexOf("$anonymous") < 0) // these will be ignored eventually
       
   436 						LOG.warn("Trying to document "+symbol.alias+" without first documenting "+parentName+".");
       
   437 				}
       
   438 			}
       
   439 			if (parent) name = parentName+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
       
   440 		}
       
   441 		else {
       
   442 			parent = this.namescope.last(1);
       
   443 			name = parent.alias+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
       
   444 		}
       
   445 	}
       
   446 	// otherwise it means the global
       
   447 	else {
       
   448 		name = nameFragment;
       
   449 	}
       
   450 	
       
   451 	return name;
       
   452 }
       
   453 
       
   454 JSDOC.Walker.onParamList = function(/**Array*/paramTokens) {
       
   455 	if (!paramTokens) {
       
   456 		LOG.warn("Malformed parameter list. Can't parse code.");
       
   457 		return [];
       
   458 	}
       
   459 	var params = [];
       
   460 	for (var i = 0, l = paramTokens.length; i < l; i++) {
       
   461 		if (paramTokens[i].is("JSDOC")) {
       
   462 			var paramType = paramTokens[i].data.replace(/(^\/\*\* *| *\*\/$)/g, "");
       
   463 			
       
   464 			if (paramTokens[i+1] && paramTokens[i+1].is("NAME")) {
       
   465 				i++;
       
   466 				params.push({type: paramType, name: paramTokens[i].data});
       
   467 			}
       
   468 		}
       
   469 		else if (paramTokens[i].is("NAME")) {
       
   470 			params.push({name: paramTokens[i].data});
       
   471 		}
       
   472 	}
       
   473 	return params;
       
   474 }