can generate jsdoc; private methods marked as such
[dygraphs.git] / jsdoc-toolkit / app / lib / JSDOC / Walker.js
diff --git a/jsdoc-toolkit/app/lib/JSDOC/Walker.js b/jsdoc-toolkit/app/lib/JSDOC/Walker.js
new file mode 100644 (file)
index 0000000..6ecaea8
--- /dev/null
@@ -0,0 +1,507 @@
+if (typeof JSDOC == "undefined") JSDOC = {};
+
+/** @constructor */
+JSDOC.Walker = function(/**JSDOC.TokenStream*/ts) {
+       this.init();
+       if (typeof ts != "undefined") {
+               this.walk(ts);
+       }
+}
+
+JSDOC.Walker.prototype.init = function() {
+       this.ts = null;
+
+       var globalSymbol = new JSDOC.Symbol("_global_", [], "GLOBAL", new JSDOC.DocComment(""));
+       globalSymbol.isNamespace = true;
+       globalSymbol.srcFile = "";
+       globalSymbol.isPrivate = false;
+       JSDOC.Parser.addSymbol(globalSymbol);
+       this.lastDoc = null;
+       this.token = null;
+       
+       /**
+               The chain of symbols under which we are currently nested.
+               @type Array
+       */
+       this.namescope = [globalSymbol];
+       this.namescope.last = function(n){ if (!n) n = 0; return this[this.length-(1+n)] || "" };
+}
+
+JSDOC.Walker.prototype.walk = function(/**JSDOC.TokenStream*/ts) {
+       this.ts = ts;
+       while (this.token = this.ts.look()) {
+               if (this.token.popNamescope) {
+                       
+                       var symbol = this.namescope.pop();
+                       if (symbol.is("FUNCTION")) {
+                               if (this.ts.look(1).is("LEFT_PAREN") && symbol.comment.getTag("function").length == 0) {
+                                       symbol.isa = "OBJECT";
+                               }
+                       }
+               }
+               this.step();
+               if (!this.ts.next()) break;
+       }
+}
+
+JSDOC.Walker.prototype.step = function() {
+       if (this.token.is("JSDOC")) { // it's a doc comment
+       
+               var doc = new JSDOC.DocComment(this.token.data);
+               
+                               
+               if (doc.getTag("exports").length > 0) {
+                       var exports = doc.getTag("exports")[0];
+
+                       exports.desc.match(/(\S+) as (\S+)/i);
+                       var n1 = RegExp.$1;
+                       var n2 = RegExp.$2;
+                       
+                       if (!n1 && n2) throw "@exports tag requires a value like: 'name as ns.name'";
+                       
+                       JSDOC.Parser.rename = (JSDOC.Parser.rename || {});      
+                       JSDOC.Parser.rename[n1] = n2
+               }
+               
+               if (doc.getTag("lends").length > 0) {
+                       var lends = doc.getTag("lends")[0];
+
+                       var name = lends.desc
+                       if (!name) throw "@lends tag requires a value.";
+                       
+                       var symbol = new JSDOC.Symbol(name, [], "OBJECT", doc);
+                       
+                       this.namescope.push(symbol);
+                       
+                       var matching = this.ts.getMatchingToken("LEFT_CURLY");
+                       if (matching) matching.popNamescope = name;
+                       else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+                       
+                       this.lastDoc = null;
+                       return true;
+               }
+               else if (doc.getTag("name").length > 0 && doc.getTag("overview").length == 0) { // it's a virtual symbol
+                       var virtualName = doc.getTag("name")[0].desc;
+                       if (!virtualName) throw "@name tag requires a value.";
+                       
+                       if (doc.getTag("memberOf").length > 0) {
+                               virtualName = (doc.getTag("memberOf")[0] + "." + virtualName)
+                                       .replace(/([#.])\./, "$1");
+                               doc.deleteTag("memberOf");
+                       }
+
+                       var symbol = new JSDOC.Symbol(virtualName, [], "VIRTUAL", doc);
+                       
+                       JSDOC.Parser.addSymbol(symbol);
+                       
+                       this.lastDoc = null;
+                       return true;
+               }
+               else if (doc.meta) { // it's a meta doclet
+                       if (doc.meta == "@+") JSDOC.DocComment.shared = doc.src;
+                       else if (doc.meta == "@-") JSDOC.DocComment.shared = "";
+                       else if (doc.meta == "nocode+") JSDOC.Parser.conf.ignoreCode = true;
+                       else if (doc.meta == "nocode-") JSDOC.Parser.conf.ignoreCode = JSDOC.opt.n;
+                       else throw "Unrecognized meta comment: "+doc.meta;
+                       
+                       this.lastDoc = null;
+                       return true;
+               }
+               else if (doc.getTag("overview").length > 0) { // it's a file overview
+                       symbol = new JSDOC.Symbol("", [], "FILE", doc);
+                       
+                       JSDOC.Parser.addSymbol(symbol);
+                       
+                       this.lastDoc = null;
+                       return true;
+               }
+               else {
+                       this.lastDoc = doc;
+                       return false;
+               }
+       }
+       else if (!JSDOC.Parser.conf.ignoreCode) { // it's code
+               if (this.token.is("NAME")) { // it's the name of something
+                       var symbol;
+                       var name = this.token.data;
+                       var doc = null; if (this.lastDoc) doc = this.lastDoc;
+                       var params = [];
+               
+                       // it's inside an anonymous object
+                       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"))) {
+                               name = "$anonymous";
+                               name = this.namescope.last().alias+"-"+name
+                                       
+                               params = [];
+                               
+                               symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+
+                               JSDOC.Parser.addSymbol(symbol);
+                               
+                               this.namescope.push(symbol);
+                               
+                               var matching = this.ts.getMatchingToken(null, "RIGHT_CURLY");
+                               if (matching) matching.popNamescope = name;
+                               else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+                       }
+                       // function foo() {}
+                       else if (this.ts.look(-1).is("FUNCTION") && this.ts.look(1).is("LEFT_PAREN")) {
+                               var isInner;
+                               
+                               if (this.lastDoc) doc = this.lastDoc;
+                               
+                               if (doc && doc.getTag("memberOf").length > 0) {
+                                       name = (doc.getTag("memberOf")[0]+"."+name).replace("#.", "#");
+                                       doc.deleteTag("memberOf");
+                               }
+                               else {
+                                       name = this.namescope.last().alias+"-"+name;
+                                       if (!this.namescope.last().is("GLOBAL")) isInner = true;
+                               }
+                               
+                               if (!this.namescope.last().is("GLOBAL")) isInner = true;
+                               
+                               params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+
+                               symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
+                               if (isInner) symbol.isInner = true;
+
+                               if (this.ts.look(1).is("JSDOC")) {
+                                       var inlineReturn = ""+this.ts.look(1).data;
+                                       inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
+                                       symbol.type = inlineReturn;
+                               }
+                               
+                               JSDOC.Parser.addSymbol(symbol);
+                               
+                               this.namescope.push(symbol);
+                               
+                               var matching = this.ts.getMatchingToken("LEFT_CURLY");
+                               if (matching) matching.popNamescope = name;
+                               else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+                       }
+                       // foo = function() {}
+                       else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("FUNCTION")) {
+                               var constructs;
+                               var isConstructor = false;
+                               if (doc && (constructs = doc.getTag("constructs")) && constructs.length) {
+                                       if (constructs[0].desc) {
+                                               name = constructs[0].desc;
+                                               isConstructor = true;
+                                       }
+                               }
+                                       
+                               var isInner;
+                               if (this.ts.look(-1).is("VAR") || this.isInner) {
+                                       if (doc && doc.getTag("memberOf").length > 0) {
+                                               name = (doc.getTag("memberOf")[0]+"."+name).replace("#.", "#");
+                                               doc.deleteTag("memberOf");
+                                       }
+                                       else {
+                                               name = this.namescope.last().alias+"-"+name;
+                                               if (!this.namescope.last().is("GLOBAL")) isInner = true;
+                                       }
+                                       if (!this.namescope.last().is("GLOBAL")) isInner = true;
+                               }
+                               else if (name.indexOf("this.") == 0) {
+                                       name = this.resolveThis(name);
+                               }
+
+                               if (this.lastDoc) doc = this.lastDoc;
+                               params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+                               
+                               symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
+
+                               if (isInner) symbol.isInner = true;
+                               if (isConstructor) symbol.isa = "CONSTRUCTOR";
+                               
+                               if (this.ts.look(1).is("JSDOC")) {
+                                       var inlineReturn = ""+this.ts.look(1).data;
+                                       inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
+                                       symbol.type = inlineReturn;
+                               }
+
+                               JSDOC.Parser.addSymbol(symbol);
+                               
+                               this.namescope.push(symbol);
+                               
+                               var matching = this.ts.getMatchingToken("LEFT_CURLY");
+                               if (matching) matching.popNamescope = name;
+                               else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+                       }
+                       // foo = new function() {} or foo = (function() {}
+                       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")) {
+                               var isInner;
+                               if (this.ts.look(-1).is("VAR") || this.isInner) {
+                                       name = this.namescope.last().alias+"-"+name
+                                       if (!this.namescope.last().is("GLOBAL")) isInner = true;
+                               }
+                               else if (name.indexOf("this.") == 0) {
+                                       name = this.resolveThis(name);
+                               }
+
+                               this.ts.next(3); // advance past the "new" or "("
+                               
+                               if (this.lastDoc) doc = this.lastDoc;
+                               params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+                               
+                               symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+                               if (isInner) symbol.isInner = true;
+                               
+                               if (this.ts.look(1).is("JSDOC")) {
+                                       var inlineReturn = ""+this.ts.look(1).data;
+                                       inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
+                                       symbol.type = inlineReturn;
+                               }
+                               
+                               JSDOC.Parser.addSymbol(symbol);
+                               
+                               symbol.scopeType = "INSTANCE";
+                               this.namescope.push(symbol);
+                               
+                               var matching = this.ts.getMatchingToken("LEFT_CURLY");
+                               if (matching) matching.popNamescope = name;
+                               else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+                       }
+                       // foo: function() {}
+                       else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("FUNCTION")) {
+                               name = (this.namescope.last().alias+"."+name).replace("#.", "#");
+                               
+                               if (this.lastDoc) doc = this.lastDoc;
+                               params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+                               
+                               if (doc && doc.getTag("constructs").length) {
+                                       name = name.replace(/\.prototype(\.|$)/, "#");
+                                       
+                                       if (name.indexOf("#") > -1) name = name.match(/(^[^#]+)/)[0];
+                                       else name = this.namescope.last().alias;
+
+                                       symbol = new JSDOC.Symbol(name, params, "CONSTRUCTOR", doc);
+                               }
+                               else {
+                                       symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
+                               }
+                               
+                               if (this.ts.look(1).is("JSDOC")) {
+                                       var inlineReturn = ""+this.ts.look(1).data;
+                                       inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
+                                       symbol.type = inlineReturn;
+                               }
+                               
+                               JSDOC.Parser.addSymbol(symbol);
+                               
+                               this.namescope.push(symbol);
+                               
+                               var matching = this.ts.getMatchingToken("LEFT_CURLY");
+                               if (matching) matching.popNamescope = name;
+                               else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+                       }
+                       // foo = {}
+                       else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("LEFT_CURLY")) {
+                               var isInner;
+                               if (this.ts.look(-1).is("VAR") || this.isInner) {
+                                       name = this.namescope.last().alias+"-"+name
+                                       if (!this.namescope.last().is("GLOBAL")) isInner = true;
+                               }
+                               else if (name.indexOf("this.") == 0) {
+                                       name = this.resolveThis(name);
+                               }
+                               
+                               if (this.lastDoc) doc = this.lastDoc;
+                               
+                               symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+                               if (isInner) symbol.isInner = true;
+                               
+                       
+                               if (doc) JSDOC.Parser.addSymbol(symbol);
+
+                               this.namescope.push(symbol);
+                               
+                               var matching = this.ts.getMatchingToken("LEFT_CURLY");
+                               if (matching) matching.popNamescope = name;
+                               else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+                       }
+                       // var foo;
+                       else if (this.ts.look(1).is("SEMICOLON")) {
+                               var isInner;
+
+                               if (this.ts.look(-1).is("VAR") || this.isInner) {
+                                       name = this.namescope.last().alias+"-"+name
+                                       if (!this.namescope.last().is("GLOBAL")) isInner = true;
+                                       
+                                       if (this.lastDoc) doc = this.lastDoc;
+                               
+                                       symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+                                       if (isInner) symbol.isInner = true;
+                                       
+                               
+                                       if (doc) JSDOC.Parser.addSymbol(symbol);
+                               }
+                       }
+                       // foo = x
+                       else if (this.ts.look(1).is("ASSIGN")) {                                
+                               var isInner;
+                               if (this.ts.look(-1).is("VAR") || this.isInner) {
+                                       name = this.namescope.last().alias+"-"+name
+                                       if (!this.namescope.last().is("GLOBAL")) isInner = true;
+                               }
+                               else if (name.indexOf("this.") == 0) {
+                                       name = this.resolveThis(name);
+                               }
+                               
+                               if (this.lastDoc) doc = this.lastDoc;
+                               
+                               symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+                               if (isInner) symbol.isInner = true;
+                               
+                       
+                               if (doc) JSDOC.Parser.addSymbol(symbol);
+                       }
+                       // foo: {}
+                       else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("LEFT_CURLY")) {
+                               name = (this.namescope.last().alias+"."+name).replace("#.", "#");
+                               
+                               if (this.lastDoc) doc = this.lastDoc;
+                               
+                               symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+                               
+                       
+                               if (doc) JSDOC.Parser.addSymbol(symbol);
+                               
+                               this.namescope.push(symbol);
+                               
+                               var matching = this.ts.getMatchingToken("LEFT_CURLY");
+                               if (matching) matching.popNamescope = name;
+                               else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+                       }
+                       // foo: x
+                       else if (this.ts.look(1).is("COLON")) {
+                               name = (this.namescope.last().alias+"."+name).replace("#.", "#");;
+                               
+                               if (this.lastDoc) doc = this.lastDoc;
+                               
+                               symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+                               
+                       
+                               if (doc) JSDOC.Parser.addSymbol(symbol);
+                       }
+                       // foo(...)
+                       else if (this.ts.look(1).is("LEFT_PAREN")) {
+                               if (typeof JSDOC.PluginManager != "undefined") {
+                                       var functionCall = {name: name};
+                               
+                                       var cursor = this.ts.cursor;
+                                       params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+                                       this.ts.cursor = cursor;
+                                       
+                                       for (var i = 0; i < params.length; i++)
+                                               functionCall["arg" + (i + 1)] = params[i].name;
+                               
+                                       JSDOC.PluginManager.run("onFunctionCall", functionCall);
+                                       if (functionCall.doc) {
+                                               this.ts.insertAhead(new JSDOC.Token(functionCall.doc, "COMM", "JSDOC"));
+                                       }
+                               }
+                       }
+                       this.lastDoc = null;
+               }
+               else if (this.token.is("FUNCTION")) { // it's an anonymous function
+                       if (
+                               (!this.ts.look(-1).is("COLON") || !this.ts.look(-1).is("ASSIGN"))
+                               && !this.ts.look(1).is("NAME")
+                       ) {
+                               if (this.lastDoc) doc = this.lastDoc;
+                               
+                               name = "$anonymous";
+                               name = this.namescope.last().alias+"-"+name
+                               
+                               params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+                               
+                               symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
+                               
+                               JSDOC.Parser.addSymbol(symbol);
+                               
+                               this.namescope.push(symbol);
+                               
+                               var matching = this.ts.getMatchingToken("LEFT_CURLY");
+                               if (matching) matching.popNamescope = name;
+                               else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+                       }
+               }
+       }
+       return true;
+}
+
+/**
+       Resolves what "this." means when it appears in a name.
+       @param name The name that starts with "this.".
+       @returns The name with "this." resolved.
+ */
+JSDOC.Walker.prototype.resolveThis = function(name) {
+       name.match(/^this\.(.+)$/)
+       var nameFragment = RegExp.$1;
+       if (!nameFragment) return name;
+       
+       var symbol = this.namescope.last();
+       var scopeType = symbol.scopeType || symbol.isa;
+
+       // if we are in a constructor function, `this` means the instance
+       if (scopeType == "CONSTRUCTOR") {
+               name = symbol.alias+"#"+nameFragment;
+       }
+       
+       // if we are in an anonymous constructor function, `this` means the instance
+       else if (scopeType == "INSTANCE") {
+               name = symbol.alias+"."+nameFragment;
+       }
+       
+       // if we are in a function, `this` means the container (possibly the global)
+       else if (scopeType == "FUNCTION") {
+               // in a method of a prototype, so `this` means the constructor
+               if (symbol.alias.match(/(^.*)[#.-][^#.-]+/)) {
+                       var parentName = RegExp.$1;
+                       var parent = JSDOC.Parser.symbols.getSymbol(parentName);
+
+                       if (!parent) {
+                               if (JSDOC.Lang.isBuiltin(parentName)) parent = JSDOC.Parser.addBuiltin(parentName);
+                               else {
+                                       if (symbol.alias.indexOf("$anonymous") < 0) // these will be ignored eventually
+                                               LOG.warn("Trying to document "+symbol.alias+" without first documenting "+parentName+".");
+                               }
+                       }
+                       if (parent) name = parentName+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
+               }
+               else {
+                       parent = this.namescope.last(1);
+                       name = parent.alias+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
+               }
+       }
+       // otherwise it means the global
+       else {
+               name = nameFragment;
+       }
+       
+       return name;
+}
+
+JSDOC.Walker.onParamList = function(/**Array*/paramTokens) {
+       if (!paramTokens) {
+               LOG.warn("Malformed parameter list. Can't parse code.");
+               return [];
+       }
+       var params = [];
+       for (var i = 0, l = paramTokens.length; i < l; i++) {
+               if (paramTokens[i].is("JSDOC")) {
+                       var paramType = paramTokens[i].data.replace(/(^\/\*\* *| *\*\/$)/g, "");
+                       
+                       if (paramTokens[i+1] && paramTokens[i+1].is("NAME")) {
+                               i++;
+                               params.push({type: paramType, name: paramTokens[i].data});
+                       }
+               }
+               else if (paramTokens[i].is("NAME")) {
+                       params.push({name: paramTokens[i].data});
+               }
+       }
+       return params;
+}