mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
197 lines
6.2 KiB
JavaScript
197 lines
6.2 KiB
JavaScript
/**
|
|
* Functionality related to JSDoc tags.
|
|
* @module jsdoc/tag
|
|
* @requires module:jsdoc/env
|
|
* @requires module:jsdoc/path
|
|
* @requires module:jsdoc/tag/dictionary
|
|
* @requires module:jsdoc/tag/validator
|
|
* @requires module:jsdoc/tag/type
|
|
* @requires module:jsdoc/util/logger
|
|
* @requires module:util
|
|
*/
|
|
const jsdoc = {
|
|
env: require('jsdoc/env'),
|
|
tag: {
|
|
dictionary: require('jsdoc/tag/dictionary'),
|
|
validator: require('jsdoc/tag/validator'),
|
|
type: require('jsdoc/tag/type')
|
|
}
|
|
};
|
|
const logger = require('@jsdoc/logger');
|
|
const path = require('jsdoc/path');
|
|
const 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) {
|
|
let indentMatcher;
|
|
let 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, '');
|
|
if (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: Boolean(jsdoc.env.opts.debug),
|
|
configurable: true
|
|
});
|
|
}
|
|
|
|
function parseType({text, originalTitle}, {canHaveName, canHaveType}, meta) {
|
|
try {
|
|
return jsdoc.tag.type.parse(text, canHaveName, canHaveType);
|
|
}
|
|
catch (e) {
|
|
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)}${meta.lineno ? (` in line ${meta.lineno}`) : ''}` ) : '',
|
|
originalTitle,
|
|
text,
|
|
e.message
|
|
);
|
|
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function processTagText(tag, tagDef, meta) {
|
|
let 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(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;
|
|
};
|
|
|
|
/**
|
|
* Represents a single doclet tag.
|
|
*/
|
|
class Tag {
|
|
/**
|
|
* Constructs a new tag object. Calls the tag validator.
|
|
*
|
|
* @param {string} tagTitle
|
|
* @param {string=} tagBody
|
|
* @param {object=} meta
|
|
*/
|
|
constructor(tagTitle, tagBody, meta) {
|
|
let tagDef;
|
|
let 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);
|
|
}
|
|
}
|
|
exports.Tag = Tag;
|