Spaces:
Sleeping
Sleeping
| 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; | |
| } | |