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);
};
|