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