Code coverage report for master/lib/jsdoc/tag.js

Statements: 100% (62 / 62)      Branches: 97.83% (45 / 46)      Functions: 100% (8 / 8)      Lines: 100% (61 / 61)      Ignored: none     

All files » master/lib/jsdoc/ » tag.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                              1                     1 1       1 11846     1 11846 11846   11846 11846   11846 4   11842 49 49 49 49 19 19         11793     11846     1 382               1 513 513     3             1       1 4901   4901 128     4901   513   513         511 510 382     382     510 2040 139         511 235     511   310       4388                         1 26                     1 5923 5923   5923   5923     5923   5923 5923                                             5923   5923 4901     5921    
/**
    @overview
    @author Michael Mathews <micmath@gmail.com>
    @license Apache License 2.0 - See file 'LICENSE.md' in this project.
 */
 
/**
    Functionality related to JSDoc tags.
    @module jsdoc/tag
    @requires jsdoc/tag/dictionary
    @requires jsdoc/tag/validator
    @requires jsdoc/tag/type
 */
'use strict';
 
var jsdoc = {
    env: require('jsdoc/env'),
    tag: {
        dictionary: require('jsdoc/tag/dictionary'),
        validator: require('jsdoc/tag/validator'),
        type: require('jsdoc/tag/type')
    },
    util: {
        logger: require('jsdoc/util/logger')
    }
};
var path = require('jsdoc/path');
var util = require('util');
 
// Check whether the text is the same as a symbol name with leading or trailing whitespace. If so,
// the whitespace must be preserved, and the text cannot be trimmed.
function mustPreserveWhitespace(text, meta) {
    return meta && meta.code && meta.code.name === text && text.match(/(?:^\s+)|(?:\s+$)/);
}
 
function trim(text, opts, meta) {
    var indentMatcher;
    var match;
 
    opts = opts || {};
    text = String(typeof text !== 'undefined' ? text : '');
 
    if ( mustPreserveWhitespace(text, meta) ) {
        text = util.format('"%s"', text);
    }
    else if (opts.keepsWhitespace) {
        text = text.replace(/^[\n\r\f]+|[\n\r\f]+$/g, '');
        Eif (opts.removesIndent) {
            match = text.match(/^([ \t]+)/);
            if (match && match[1]) {
                indentMatcher = new RegExp('^' + match[1], 'gm');
                text = text.replace(indentMatcher, '');
            }
        }
    }
    else {
        text = text.replace(/^\s+|\s+$/g, '');
    }
 
    return text;
}
 
function addHiddenProperty(obj, propName, propValue) {
    Object.defineProperty(obj, propName, {
        value: propValue,
        writable: true,
        enumerable: !!jsdoc.env.opts.debug,
        configurable: true
    });
}
 
function parseType(tag, tagDef, meta) {
    try {
        return jsdoc.tag.type.parse(tag.text, tagDef.canHaveName, tagDef.canHaveType);
    }
    catch (e) {
        jsdoc.util.logger.error(
            'Unable to parse a tag\'s type expression%s with tag title "%s" and text "%s": %s',
            meta.filename ? ( ' for source file ' + path.join(meta.path, meta.filename) ) : '',
            tag.originalTitle,
            tag.text,
            e.message
        );
        return {};
    }
}
 
function processTagText(tag, tagDef, meta) {
    var tagType;
 
    if (tagDef.onTagText) {
        tag.text = tagDef.onTagText(tag.text);
    }
 
    if (tagDef.canHaveType || tagDef.canHaveName) {
        /** The value property represents the result of parsing the tag text. */
        tag.value = {};
 
        tagType = parseType(tag, tagDef, meta);
 
        // It is possible for a tag to *not* have a type but still have
        // optional or defaultvalue, e.g. '@param [foo]'.
        // Although tagType.type.length == 0 we should still copy the other properties.
        if (tagType.type) {
            if (tagType.type.length) {
                tag.value.type = {
                    names: tagType.type
                };
                addHiddenProperty(tag.value.type, 'parsedType', tagType.parsedType);
            }
 
            ['optional', 'nullable', 'variable', 'defaultvalue'].forEach(function(prop) {
                if (typeof tagType[prop] !== 'undefined') {
                    tag.value[prop] = tagType[prop];
                }
            });
        }
 
        if (tagType.text && tagType.text.length) {
            tag.value.description = tagType.text;
        }
 
        if (tagDef.canHaveName) {
            // note the dash is a special case: as a param name it means "no name"
            if (tagType.name && tagType.name !== '-') { tag.value.name = tagType.name; }
        }
    }
    else {
        tag.value = tag.text;
    }
}
 
/**
 * Replace the existing tag dictionary with a new tag dictionary.
 *
 * Used for testing only. Do not call this method directly. Instead, call
 * {@link module:jsdoc/doclet._replaceDictionary}, which also updates this module's tag dictionary.
 *
 * @private
 * @param {module:jsdoc/tag/dictionary.Dictionary} dict - The new tag dictionary.
 */
exports._replaceDictionary = function _replaceDictionary(dict) {
    jsdoc.tag.dictionary = dict;
};
 
/**
    Constructs a new tag object. Calls the tag validator.
    @class
    @classdesc Represents a single doclet tag.
    @param {string} tagTitle
    @param {string=} tagBody
    @param {object=} meta
 */
var Tag = exports.Tag = function(tagTitle, tagBody, meta) {
    var tagDef;
    var trimOpts;
 
    meta = meta || {};
 
    this.originalTitle = trim(tagTitle);
 
    /** The title of the tag (for example, `title` in `@title text`). */
    this.title = jsdoc.tag.dictionary.normalise(this.originalTitle);
 
    tagDef = jsdoc.tag.dictionary.lookUp(this.title);
    trimOpts = {
        keepsWhitespace: tagDef.keepsWhitespace,
        removesIndent: tagDef.removesIndent
    };
 
    /**
     * The text following the tag (for example, `text` in `@title text`).
     *
     * Whitespace is trimmed from the tag text as follows:
     *
     * + If the tag's `keepsWhitespace` option is falsy, all leading and trailing whitespace are
     * removed.
     * + If the tag's `keepsWhitespace` option is set to `true`, leading and trailing whitespace are
     * not trimmed, unless the `removesIndent` option is also enabled.
     * + If the tag's `removesIndent` option is set to `true`, any indentation that is shared by
     * every line in the string is removed. This option is ignored unless `keepsWhitespace` is set
     * to `true`.
     *
     * **Note**: If the tag text is the name of a symbol, and the symbol's name includes leading or
     * trailing whitespace (for example, the property names in `{ ' ': true, ' foo ': false }`),
     * the tag text is not trimmed. Instead, the tag text is wrapped in double quotes to prevent the
     * whitespace from being trimmed.
     */
    this.text = trim(tagBody, trimOpts, meta);
 
    if (this.text) {
        processTagText(this, tagDef, meta);
    }
 
    jsdoc.tag.validator.validate(this, tagDef, meta);
};