| 1 | if (typeof JSDOC == "undefined") JSDOC = {}; |
| 2 | |
| 3 | /** |
| 4 | @constructor |
| 5 | */ |
| 6 | JSDOC.DocTag = function(src) { |
| 7 | this.init(); |
| 8 | if (typeof src != "undefined") { |
| 9 | this.parse(src); |
| 10 | } |
| 11 | } |
| 12 | |
| 13 | /** |
| 14 | Create and initialize the properties of this. |
| 15 | */ |
| 16 | JSDOC.DocTag.prototype.init = function() { |
| 17 | this.title = ""; |
| 18 | this.type = ""; |
| 19 | this.name = ""; |
| 20 | this.isOptional = false; |
| 21 | this.defaultValue = ""; |
| 22 | this.desc = ""; |
| 23 | |
| 24 | return this; |
| 25 | } |
| 26 | |
| 27 | /** |
| 28 | Populate the properties of this from the given tag src. |
| 29 | @param {string} src |
| 30 | */ |
| 31 | JSDOC.DocTag.prototype.parse = function(src) { |
| 32 | if (typeof src != "string") throw "src must be a string not "+(typeof src); |
| 33 | |
| 34 | try { |
| 35 | src = this.nibbleTitle(src); |
| 36 | if (JSDOC.PluginManager) { |
| 37 | JSDOC.PluginManager.run("onDocTagSynonym", this); |
| 38 | } |
| 39 | |
| 40 | src = this.nibbleType(src); |
| 41 | |
| 42 | // only some tags are allowed to have names. |
| 43 | if (this.title == "param" || this.title == "property" || this.title == "config") { // @config is deprecated |
| 44 | src = this.nibbleName(src); |
| 45 | } |
| 46 | } |
| 47 | catch(e) { |
| 48 | if (LOG) LOG.warn(e); |
| 49 | else throw e; |
| 50 | } |
| 51 | this.desc = src; // whatever is left |
| 52 | |
| 53 | // example tags need to have whitespace preserved |
| 54 | if (this.title != "example") this.desc = this.desc.trim(); |
| 55 | |
| 56 | if (JSDOC.PluginManager) { |
| 57 | JSDOC.PluginManager.run("onDocTag", this); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | /** |
| 62 | Automatically called when this is stringified. |
| 63 | */ |
| 64 | JSDOC.DocTag.prototype.toString = function() { |
| 65 | return this.desc; |
| 66 | } |
| 67 | |
| 68 | /*t: |
| 69 | plan(1, "testing JSDOC.DocTag#toString"); |
| 70 | |
| 71 | var tag = new JSDOC.DocTag("param {object} date A valid date."); |
| 72 | is(""+tag, "A valid date.", "stringifying a tag returns the desc."); |
| 73 | */ |
| 74 | |
| 75 | /** |
| 76 | Find and shift off the title of a tag. |
| 77 | @param {string} src |
| 78 | @return src |
| 79 | */ |
| 80 | JSDOC.DocTag.prototype.nibbleTitle = function(src) { |
| 81 | if (typeof src != "string") throw "src must be a string not "+(typeof src); |
| 82 | |
| 83 | var parts = src.match(/^\s*(\S+)(?:\s([\s\S]*))?$/); |
| 84 | |
| 85 | if (parts && parts[1]) this.title = parts[1]; |
| 86 | if (parts && parts[2]) src = parts[2]; |
| 87 | else src = ""; |
| 88 | |
| 89 | return src; |
| 90 | } |
| 91 | |
| 92 | /*t: |
| 93 | plan(8, "testing JSDOC.DocTag#nibbleTitle"); |
| 94 | |
| 95 | var tag = new JSDOC.DocTag(); |
| 96 | |
| 97 | tag.init().nibbleTitle("aTitleGoesHere"); |
| 98 | is(tag.title, "aTitleGoesHere", "a title can be found in a single-word string."); |
| 99 | |
| 100 | var src = tag.init().nibbleTitle("aTitleGoesHere and the rest"); |
| 101 | is(tag.title, "aTitleGoesHere", "a title can be found in a multi-word string."); |
| 102 | is(src, "and the rest", "the rest is returned when the title is nibbled off."); |
| 103 | |
| 104 | src = tag.init().nibbleTitle(""); |
| 105 | is(tag.title, "", "given an empty string the title is empty."); |
| 106 | is(src, "", "the rest is empty when the tag is empty."); |
| 107 | |
| 108 | var src = tag.init().nibbleTitle(" aTitleGoesHere\n a description"); |
| 109 | is(tag.title, "aTitleGoesHere", "leading and trailing spaces are not part of the title."); |
| 110 | is(src, " a description", "leading spaces (less one) are part of the description."); |
| 111 | |
| 112 | tag.init().nibbleTitle("a.Title::Goes_Here foo"); |
| 113 | is(tag.title, "a.Title::Goes_Here", "titles with punctuation are allowed."); |
| 114 | */ |
| 115 | |
| 116 | /** |
| 117 | Find and shift off the type of a tag. |
| 118 | @requires frame/String.js |
| 119 | @param {string} src |
| 120 | @return src |
| 121 | */ |
| 122 | JSDOC.DocTag.prototype.nibbleType = function(src) { |
| 123 | if (typeof src != "string") throw "src must be a string not "+(typeof src); |
| 124 | |
| 125 | if (src.match(/^\s*\{/)) { |
| 126 | var typeRange = src.balance("{", "}"); |
| 127 | if (typeRange[1] == -1) { |
| 128 | throw "Malformed comment tag ignored. Tag type requires an opening { and a closing }: "+src; |
| 129 | } |
| 130 | this.type = src.substring(typeRange[0]+1, typeRange[1]).trim(); |
| 131 | this.type = this.type.replace(/\s*,\s*/g, "|"); // multiples can be separated by , or | |
| 132 | src = src.substring(typeRange[1]+1); |
| 133 | } |
| 134 | |
| 135 | return src; |
| 136 | } |
| 137 | |
| 138 | /*t: |
| 139 | plan(5, "testing JSDOC.DocTag.parser.nibbleType"); |
| 140 | requires("../frame/String.js"); |
| 141 | |
| 142 | var tag = new JSDOC.DocTag(); |
| 143 | |
| 144 | tag.init().nibbleType("{String[]} aliases"); |
| 145 | is(tag.type, "String[]", "type can have non-alpha characters."); |
| 146 | |
| 147 | tag.init().nibbleType("{ aTypeGoesHere } etc etc"); |
| 148 | is(tag.type, "aTypeGoesHere", "type is trimmed."); |
| 149 | |
| 150 | tag.init().nibbleType("{ oneType, twoType ,\n threeType } etc etc"); |
| 151 | is(tag.type, "oneType|twoType|threeType", "multiple types can be separated by commas."); |
| 152 | |
| 153 | var error; |
| 154 | try { tag.init().nibbleType("{widget foo"); } |
| 155 | catch(e) { error = e; } |
| 156 | is(typeof error, "string", "malformed tag type throws error."); |
| 157 | isnt(error.indexOf("Malformed"), -1, "error message tells tag is malformed."); |
| 158 | */ |
| 159 | |
| 160 | /** |
| 161 | Find and shift off the name of a tag. |
| 162 | @requires frame/String.js |
| 163 | @param {string} src |
| 164 | @return src |
| 165 | */ |
| 166 | JSDOC.DocTag.prototype.nibbleName = function(src) { |
| 167 | if (typeof src != "string") throw "src must be a string not "+(typeof src); |
| 168 | |
| 169 | src = src.trim(); |
| 170 | |
| 171 | // is optional? |
| 172 | if (src.charAt(0) == "[") { |
| 173 | var nameRange = src.balance("[", "]"); |
| 174 | if (nameRange[1] == -1) { |
| 175 | throw "Malformed comment tag ignored. Tag optional name requires an opening [ and a closing ]: "+src; |
| 176 | } |
| 177 | this.name = src.substring(nameRange[0]+1, nameRange[1]).trim(); |
| 178 | this.isOptional = true; |
| 179 | |
| 180 | src = src.substring(nameRange[1]+1); |
| 181 | |
| 182 | // has default value? |
| 183 | var nameAndValue = this.name.split("="); |
| 184 | if (nameAndValue.length) { |
| 185 | this.name = nameAndValue.shift().trim(); |
| 186 | this.defaultValue = nameAndValue.join("="); |
| 187 | } |
| 188 | } |
| 189 | else { |
| 190 | var parts = src.match(/^(\S+)(?:\s([\s\S]*))?$/); |
| 191 | if (parts) { |
| 192 | if (parts[1]) this.name = parts[1]; |
| 193 | if (parts[2]) src = parts[2].trim(); |
| 194 | else src = ""; |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | return src; |
| 199 | } |
| 200 | |
| 201 | /*t: |
| 202 | requires("../frame/String.js"); |
| 203 | plan(9, "testing JSDOC.DocTag.parser.nibbleName"); |
| 204 | |
| 205 | var tag = new JSDOC.DocTag(); |
| 206 | |
| 207 | tag.init().nibbleName("[foo] This is a description."); |
| 208 | is(tag.isOptional, true, "isOptional syntax is detected."); |
| 209 | is(tag.name, "foo", "optional param name is found."); |
| 210 | |
| 211 | tag.init().nibbleName("[foo] This is a description."); |
| 212 | is(tag.isOptional, true, "isOptional syntax is detected when no type."); |
| 213 | is(tag.name, "foo", "optional param name is found when no type."); |
| 214 | |
| 215 | tag.init().nibbleName("[foo=7] This is a description."); |
| 216 | is(tag.name, "foo", "optional param name is found when default value."); |
| 217 | is(tag.defaultValue, 7, "optional param default value is found when default value."); |
| 218 | |
| 219 | //tag.init().nibbleName("[foo= a value] This is a description."); |
| 220 | //is(tag.defaultValue, " a value", "optional param default value is found when default value has spaces (issue #112)."); |
| 221 | |
| 222 | tag.init().nibbleName("[foo=[]] This is a description."); |
| 223 | is(tag.defaultValue, "[]", "optional param default value is found when default value is [] (issue #95)."); |
| 224 | |
| 225 | tag.init().nibbleName("[foo=a=b] This is a description."); |
| 226 | is(tag.name, "foo", "optional param name is found when default value is a=b."); |
| 227 | is(tag.defaultValue, "a=b", "optional param default value is found when default value is a=b.") |
| 228 | */ |
| 229 | |
| 230 | /*t: |
| 231 | plan(32, "Testing JSDOC.DocTag.parser."); |
| 232 | requires("../frame/String.js"); |
| 233 | |
| 234 | var tag = new JSDOC.DocTag(); |
| 235 | |
| 236 | is(typeof tag, "object", "JSDOC.DocTag.parser with an empty string returns an object."); |
| 237 | is(typeof tag.title, "string", "returned object has a string property 'title'."); |
| 238 | is(typeof tag.type, "string", "returned object has a string property 'type'."); |
| 239 | is(typeof tag.name, "string", "returned object has a string property 'name'."); |
| 240 | is(typeof tag.defaultValue, "string", "returned object has a string property 'defaultValue'."); |
| 241 | is(typeof tag.isOptional, "boolean", "returned object has a boolean property 'isOptional'."); |
| 242 | is(typeof tag.desc, "string", "returned object has a string property 'desc'."); |
| 243 | |
| 244 | tag = new JSDOC.DocTag("param {widget} foo"); |
| 245 | is(tag.title, "param", "param title is found."); |
| 246 | is(tag.name, "foo", "param name is found when desc is missing."); |
| 247 | is(tag.desc, "", "param desc is empty when missing."); |
| 248 | |
| 249 | tag = new JSDOC.DocTag("param {object} date A valid date."); |
| 250 | is(tag.name, "date", "param name is found with a type."); |
| 251 | is(tag.type, "object", "param type is found."); |
| 252 | is(tag.desc, "A valid date.", "param desc is found with a type."); |
| 253 | |
| 254 | tag = new JSDOC.DocTag("param aName a description goes\n here."); |
| 255 | is(tag.name, "aName", "param name is found without a type."); |
| 256 | is(tag.desc, "a description goes\n here.", "param desc is found without a type."); |
| 257 | |
| 258 | tag = new JSDOC.DocTag("param {widget}"); |
| 259 | is(tag.name, "", "param name is empty when it is not given."); |
| 260 | |
| 261 | tag = new JSDOC.DocTag("param {widget} [foo] This is a description."); |
| 262 | is(tag.name, "foo", "optional param name is found."); |
| 263 | |
| 264 | tag = new JSDOC.DocTag("return {aType} This is a description."); |
| 265 | is(tag.type, "aType", "when return tag has no name, type is found."); |
| 266 | is(tag.desc, "This is a description.", "when return tag has no name, desc is found."); |
| 267 | |
| 268 | tag = new JSDOC.DocTag("author Joe Coder <jcoder@example.com>"); |
| 269 | is(tag.title, "author", "author tag has a title."); |
| 270 | is(tag.type, "", "the author tag has no type."); |
| 271 | is(tag.name, "", "the author tag has no name."); |
| 272 | is(tag.desc, "Joe Coder <jcoder@example.com>", "author tag has desc."); |
| 273 | |
| 274 | tag = new JSDOC.DocTag("private \t\n "); |
| 275 | is(tag.title, "private", "private tag has a title."); |
| 276 | is(tag.type, "", "the private tag has no type."); |
| 277 | is(tag.name, "", "the private tag has no name."); |
| 278 | is(tag.desc, "", "private tag has no desc."); |
| 279 | |
| 280 | tag = new JSDOC.DocTag("example\n example(code);\n more();"); |
| 281 | is(tag.desc, " example(code);\n more();", "leading whitespace (less one) in examples code is preserved."); |
| 282 | |
| 283 | tag = new JSDOC.DocTag("param theName \n"); |
| 284 | is(tag.name, "theName", "name only is found."); |
| 285 | |
| 286 | tag = new JSDOC.DocTag("type theDesc \n"); |
| 287 | is(tag.desc, "theDesc", "desc only is found."); |
| 288 | |
| 289 | tag = new JSDOC.DocTag("type {theType} \n"); |
| 290 | is(tag.type, "theType", "type only is found."); |
| 291 | |
| 292 | tag = new JSDOC.DocTag(""); |
| 293 | is(tag.title, "", "title is empty when tag is empty."); |
| 294 | */ |