1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 | 1 1 1 1 1 1 1 1 44 44 44 1 869 18 851 1 1791 1791 1791 1791 2 2 2 1791 1 1084 1084 29 29 1084 1 869 44 1 515 48 34 48 39 1 110 44 1 869 869 869 869 869 869 851 1 46 46 7 7 6 7 46 46 1 129 129 129 129 1 129 129 119 129 42 42 87 8 8 79 79 1 1 78 129 1 697 697 697 697 697 697 697 697 697 134 697 697 697 129 129 129 129 111 568 568 377 377 191 697 292 292 15 405 1 1084 1084 1084 46 1038 713 713 697 713 325 759 304 759 7 759 759 1 200 667 667 667 678 678 568 110 110 110 110 110 200 1073 1073 1084 200 198 | /** * @module jsdoc/src/handlers */ 'use strict'; var escape = require('escape-string-regexp'); var jsdoc = { doclet: require('jsdoc/doclet'), name: require('jsdoc/name'), util: { logger: require('jsdoc/util/logger') } }; var util = require('util'); var currentModule = null; var SCOPE_NAMES = jsdoc.name.SCOPE.NAMES; var SCOPE_PUNC = jsdoc.name.SCOPE.PUNC; var unresolvedName = /^((?:module.)?exports|this)(\.|$)/; function CurrentModule(doclet) { this.doclet = doclet; this.longname = doclet.longname; this.originalName = doclet.meta.code.name || ''; } function filterByLongname(doclet) { // you can't document prototypes if ( /#$/.test(doclet.longname) ) { return true; } return false; } function createDoclet(comment, e) { var doclet; var err; try { doclet = new jsdoc.doclet.Doclet(comment, e); } catch (error) { err = new Error( util.format('cannot create a doclet for the comment "%s": %s', comment.replace(/[\r\n]/g, ''), error.message) ); jsdoc.util.logger.error(err); doclet = new jsdoc.doclet.Doclet('', e); } return doclet; } /** * Create a doclet for a `symbolFound` event. The doclet represents an actual symbol that is defined * in the code. * * Here's why this function is useful. A JSDoc comment can define a symbol name by including: * * + A `@name` tag * + Another tag that accepts a name, such as `@function` * * When the JSDoc comment defines a symbol name, we treat it as a "virtual comment" for a symbol * that isn't actually present in the code. And if a virtual comment is attached to a symbol, it's * possible that the comment and symbol have nothing to do with one another. * * To handle this case, this function checks the new doclet to see if we've already added a name * property by parsing the JSDoc comment. If so, this method creates a replacement doclet that * ignores the attached JSDoc comment and only looks at the code. * * @private */ function createSymbolDoclet(comment, e) { var doclet = createDoclet(comment, e); if (doclet.name) { // try again, without the comment e.comment = '@undocumented'; doclet = createDoclet(e.comment, e); } return doclet; } function setCurrentModule(doclet) { if (doclet.kind === 'module') { currentModule = new CurrentModule(doclet); } } function setModuleScopeMemberOf(doclet) { // handle module symbols that are _not_ assigned to module.exports if (currentModule && currentModule.longname !== doclet.name) { // if we don't already know the scope, it must be an inner member if (!doclet.scope) { doclet.addTag('inner'); } // if the doclet isn't a memberof anything yet, and it's not a global, it must be a memberof // the current module if (!doclet.memberof && doclet.scope !== SCOPE_NAMES.GLOBAL) { doclet.addTag('memberof', currentModule.longname); } } } function setDefaultScope(doclet) { // module doclets don't get a default scope if (!doclet.scope && doclet.kind !== 'module') { doclet.setScope(SCOPE_NAMES.GLOBAL); } } function addDoclet(parser, newDoclet) { var e; Eif (newDoclet) { setCurrentModule(newDoclet); e = { doclet: newDoclet }; parser.emit('newDoclet', e); if ( !e.defaultPrevented && !filterByLongname(e.doclet) ) { parser.addResult(e.doclet); } } } function processAlias(parser, doclet, astNode) { var memberofName; if (doclet.alias === '{@thisClass}') { memberofName = parser.resolveThis(astNode); // "class" refers to the owner of the prototype, not the prototype itself if ( /^(.+?)(\.prototype|#)$/.test(memberofName) ) { memberofName = RegExp.$1; } doclet.alias = memberofName; } doclet.addTag('name', doclet.alias); doclet.postProcess(); } // TODO: separate code that resolves `this` from code that resolves the module object function findSymbolMemberof(parser, doclet, astNode, nameStartsWith, trailingPunc) { var memberof = ''; var nameAndPunc; var scopePunc = ''; // handle computed properties like foo['bar'] if (trailingPunc === '[') { // we don't know yet whether the symbol is a static or instance member trailingPunc = null; } nameAndPunc = nameStartsWith + (trailingPunc || ''); // remove stuff that indicates module membership (but don't touch the name `module.exports`, // which identifies the module object itself) if (doclet.name !== 'module.exports') { doclet.name = doclet.name.replace(nameAndPunc, ''); } // like `bar` in: // exports.bar = 1; // module.exports.bar = 1; // module.exports = MyModuleObject; MyModuleObject.bar = 1; if (nameStartsWith !== 'this' && currentModule && doclet.name !== 'module.exports') { memberof = currentModule.longname; scopePunc = SCOPE_PUNC.STATIC; } // like: module.exports = 1; else if (doclet.name === 'module.exports' && currentModule) { doclet.addTag('name', currentModule.longname); doclet.postProcess(); } else { memberof = parser.resolveThis(astNode); // like the following at the top level of a module: // this.foo = 1; if (nameStartsWith === 'this' && currentModule && !memberof) { memberof = currentModule.longname; scopePunc = SCOPE_PUNC.STATIC; } else { scopePunc = SCOPE_PUNC.INSTANCE; } } return { memberof: memberof, scopePunc: scopePunc }; } function addSymbolMemberof(parser, doclet, astNode) { var basename; var memberof; var memberofInfo; var moduleOriginalName = ''; var resolveTargetRegExp; var scopePunc; var unresolved; Iif (!astNode) { return; } // check to see if the doclet name is an unresolved reference to the module object, or to `this` // TODO: handle cases where the module object is shadowed in the current scope if (currentModule) { moduleOriginalName = '|' + currentModule.originalName; } resolveTargetRegExp = new RegExp('^((?:module.)?exports|this' + moduleOriginalName + ')(\\.|\\[|$)'); unresolved = resolveTargetRegExp.exec(doclet.name); if (unresolved) { memberofInfo = findSymbolMemberof(parser, doclet, astNode, unresolved[1], unresolved[2]); memberof = memberofInfo.memberof; scopePunc = memberofInfo.scopePunc; if (memberof) { doclet.name = doclet.name ? memberof + scopePunc + doclet.name : memberof; } } else { memberofInfo = parser.astnodeToMemberof(astNode); if ( Array.isArray(memberofInfo) ) { basename = memberofInfo[1]; memberof = memberofInfo[0]; } else { memberof = memberofInfo; } } // if we found a memberof name, apply it to the doclet if (memberof) { doclet.addTag('memberof', memberof); if (basename) { doclet.name = (doclet.name || '') .replace(new RegExp('^' + escape(basename) + '.'), ''); } } // otherwise, add the defaults for a module (if we're currently in a module) else { setModuleScopeMemberOf(doclet); } } function newSymbolDoclet(parser, docletSrc, e) { var memberofName = null; var newDoclet = createSymbolDoclet(docletSrc, e); // if there's an alias, use that as the symbol name if (newDoclet.alias) { processAlias(parser, newDoclet, e.astnode); } // otherwise, get the symbol name from the code else if (e.code && typeof e.code.name !== 'undefined' && e.code.name !== '') { newDoclet.addTag('name', e.code.name); if (!newDoclet.memberof) { addSymbolMemberof(parser, newDoclet, e.astnode); } newDoclet.postProcess(); } else { return false; } // set the scope to global unless any of the following are true: // a) the doclet is a memberof something // b) the doclet represents a module // c) we're in a module that exports only this symbol if ( !newDoclet.memberof && newDoclet.kind !== 'module' && (!currentModule || currentModule.longname !== newDoclet.name) ) { newDoclet.scope = SCOPE_NAMES.GLOBAL; } // handle cases where the doclet kind is auto-detected from the node type if (e.code.kind && newDoclet.kind === 'member') { newDoclet.kind = e.code.kind; } addDoclet(parser, newDoclet); e.doclet = newDoclet; } /** * Attach these event handlers to a particular instance of a parser. * @param parser */ exports.attachTo = function(parser) { // Handle JSDoc "virtual comments" that include one of the following: // + A `@name` tag // + Another tag that accepts a name, such as `@function` parser.on('jsdocCommentFound', function(e) { var comments = e.comment.split(/@also\b/g); var newDoclet; for (var i = 0, l = comments.length; i < l; i++) { newDoclet = createDoclet(comments[i], e); // we're only interested in virtual comments here if (!newDoclet.name) { continue; } // add the default scope/memberof for a module (if we're in a module) setModuleScopeMemberOf(newDoclet); newDoclet.postProcess(); // if we _still_ don't have a scope, use the default setDefaultScope(newDoclet); addDoclet(parser, newDoclet); e.doclet = newDoclet; } }); // Handle named symbols in the code. May or may not have a JSDoc comment attached. parser.on('symbolFound', function(e) { var comments = e.comment.split(/@also\b/g); for (var i = 0, l = comments.length; i < l; i++) { newSymbolDoclet(parser, comments[i], e); } }); parser.on('fileComplete', function(e) { currentModule = null; }); }; |