Code coverage report for master/lib/jsdoc/src/handlers.js

Statements: 99.32% (145 / 146)      Branches: 96.7% (88 / 91)      Functions: 100% (16 / 16)      Lines: 99.32% (145 / 146)      Ignored: none     

All files » master/lib/jsdoc/src/ » handlers.js
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;
    });
};