0
# Plugin System
1
2
JSDoc's plugin system provides extensive customization through event handlers, custom JSDoc tags, and AST node visitors. It includes built-in plugins for common tasks and supports custom plugin development.
3
4
## Capabilities
5
6
### Plugin Installation
7
8
Functions for loading and installing plugins into the JSDoc parser.
9
10
```javascript { .api }
11
/**
12
* Install an array of plugins into a parser instance
13
* @param plugins - Array of plugin module paths
14
* @param parser - Parser instance to install plugins into
15
*/
16
function installPlugins(plugins: string[], parser: Parser): void;
17
```
18
19
### Plugin Interface
20
21
Standard interface that all JSDoc plugins must implement.
22
23
```javascript { .api }
24
interface Plugin {
25
/**
26
* Event handlers for parser events
27
* Maps event names to handler functions
28
*/
29
handlers?: {
30
[eventName: string]: (e: any) => void;
31
};
32
33
/**
34
* Define custom JSDoc tags
35
* @param dictionary - Tag dictionary to add definitions to
36
*/
37
defineTags?(dictionary: TagDictionary): void;
38
39
/**
40
* AST node visitor function
41
* Called for each AST node during parsing
42
*/
43
astNodeVisitor?: (
44
node: ASTNode,
45
e: any,
46
parser: Parser,
47
currentSourceName: string
48
) => void;
49
}
50
```
51
52
## Built-in Plugins
53
54
### Markdown Plugin
55
56
Processes Markdown syntax in JSDoc comments and converts to HTML.
57
58
```javascript
59
// Plugin: plugins/markdown.js
60
const plugin = {
61
handlers: {
62
newDoclet: function(e) {
63
// Process Markdown in description
64
if (e.doclet.description) {
65
e.doclet.description = markdown.parse(e.doclet.description);
66
}
67
}
68
}
69
};
70
```
71
72
**Usage in configuration:**
73
```json
74
{
75
"plugins": ["plugins/markdown"]
76
}
77
```
78
79
### Comment Convert Plugin
80
81
Converts certain comment types to JSDoc comments.
82
83
```javascript
84
// Plugin: plugins/commentConvert.js
85
const plugin = {
86
handlers: {
87
beforeParse: function(e) {
88
// Convert // comments to /** */ format in certain cases
89
e.source = convertComments(e.source);
90
}
91
}
92
};
93
```
94
95
### Escape HTML Plugin
96
97
Escapes HTML content in JSDoc comments to prevent XSS.
98
99
```javascript
100
// Plugin: plugins/escapeHtml.js
101
const plugin = {
102
handlers: {
103
newDoclet: function(e) {
104
if (e.doclet.description) {
105
e.doclet.description = escapeHtml(e.doclet.description);
106
}
107
}
108
}
109
};
110
```
111
112
### Overload Helper Plugin
113
114
Helps document function overloads by grouping related doclets.
115
116
```javascript
117
// Plugin: plugins/overloadHelper.js
118
const plugin = {
119
handlers: {
120
parseComplete: function(e) {
121
// Group overloaded functions
122
groupOverloads(e.doclets);
123
}
124
}
125
};
126
```
127
128
### Source Tag Plugin
129
130
Adds source code information to doclets.
131
132
```javascript
133
// Plugin: plugins/sourcetag.js
134
const plugin = {
135
defineTags: function(dictionary) {
136
dictionary.defineTag('source', {
137
onTagged: function(doclet, tag) {
138
doclet.source = tag.value;
139
}
140
});
141
}
142
};
143
```
144
145
### Summarize Plugin
146
147
Creates summary descriptions from the first sentence of descriptions.
148
149
```javascript
150
// Plugin: plugins/summarize.js
151
const plugin = {
152
handlers: {
153
newDoclet: function(e) {
154
if (e.doclet.description && !e.doclet.summary) {
155
e.doclet.summary = extractFirstSentence(e.doclet.description);
156
}
157
}
158
}
159
};
160
```
161
162
### Event Dumper Plugin
163
164
Debug plugin that logs all parser events to console.
165
166
```javascript
167
// Plugin: plugins/eventDumper.js
168
const plugin = {
169
handlers: {
170
parseBegin: (e) => console.log('parseBegin:', e),
171
fileBegin: (e) => console.log('fileBegin:', e),
172
jsdocCommentFound: (e) => console.log('jsdocCommentFound:', e),
173
newDoclet: (e) => console.log('newDoclet:', e),
174
parseComplete: (e) => console.log('parseComplete:', e)
175
}
176
};
177
```
178
179
### Escape HTML Plugin
180
181
Escapes HTML content in JSDoc comments to prevent XSS attacks.
182
183
```javascript
184
// Plugin: plugins/escapeHtml.js
185
const plugin = {
186
handlers: {
187
newDoclet: function(e) {
188
if (e.doclet.description) {
189
e.doclet.description = escapeHtml(e.doclet.description);
190
}
191
if (e.doclet.summary) {
192
e.doclet.summary = escapeHtml(e.doclet.summary);
193
}
194
}
195
}
196
};
197
```
198
199
### Comments Only Plugin
200
201
Processes only comment content, filtering out code elements.
202
203
```javascript
204
// Plugin: plugins/commentsOnly.js
205
const plugin = {
206
handlers: {
207
beforeParse: function(e) {
208
// Filter source to include only comments
209
e.source = extractCommentsOnly(e.source);
210
}
211
}
212
};
213
```
214
215
### Template Helper Plugins
216
217
Plugins that enhance template functionality:
218
219
#### Rails Template Plugin
220
Rails-style template syntax support for JSDoc templates.
221
222
```javascript
223
// Plugin: plugins/railsTemplate.js
224
const plugin = {
225
handlers: {
226
parseBegin: function(e) {
227
// Configure Rails-style template syntax
228
configureRailsTemplates();
229
}
230
}
231
};
232
```
233
234
#### Underscore Template Plugin
235
Underscore.js template integration for JSDoc.
236
237
```javascript
238
// Plugin: plugins/underscore.js
239
const plugin = {
240
handlers: {
241
parseBegin: function(e) {
242
// Set up Underscore.js template engine
243
configureUnderscoreTemplates();
244
}
245
}
246
};
247
```
248
249
#### Partial Template Plugin
250
Partial template support for modular template organization.
251
252
```javascript
253
// Plugin: plugins/partial.js
254
const plugin = {
255
handlers: {
256
processingComplete: function(e) {
257
// Process partial templates
258
processPartialTemplates(e.doclets);
259
}
260
}
261
};
262
```
263
264
### Text Processing Plugins
265
266
Plugins for text manipulation and formatting:
267
268
#### Shout Plugin
269
Converts specific text elements to uppercase for emphasis.
270
271
```javascript
272
// Plugin: plugins/shout.js
273
const plugin = {
274
handlers: {
275
newDoclet: function(e) {
276
if (e.doclet.description) {
277
e.doclet.description = processShoutText(e.doclet.description);
278
}
279
}
280
}
281
};
282
```
283
284
## Custom Plugin Development
285
286
### Basic Plugin Structure
287
288
```javascript
289
// my-custom-plugin.js
290
exports.handlers = {
291
newDoclet: function(e) {
292
if (e.doclet.kind === 'function') {
293
// Custom processing for functions
294
e.doclet.customProperty = 'processed';
295
}
296
}
297
};
298
299
exports.defineTags = function(dictionary) {
300
dictionary.defineTag('custom', {
301
mustHaveValue: true,
302
onTagged: function(doclet, tag) {
303
doclet.customTag = tag.value;
304
}
305
});
306
};
307
```
308
309
### Event Handler Plugin
310
311
```javascript
312
// event-logger-plugin.js
313
const fs = require('fs');
314
315
exports.handlers = {
316
parseBegin: function(e) {
317
console.log('Starting parse of:', e.sourcefiles.length, 'files');
318
},
319
320
newDoclet: function(e) {
321
if (e.doclet.kind === 'function' && !e.doclet.description) {
322
console.warn(`Undocumented function: ${e.doclet.longname}`);
323
}
324
},
325
326
parseComplete: function(e) {
327
const stats = {
328
total: e.doclets.length,
329
documented: e.doclets.filter(d => !d.undocumented).length,
330
functions: e.doclets.filter(d => d.kind === 'function').length,
331
classes: e.doclets.filter(d => d.kind === 'class').length
332
};
333
334
fs.writeFileSync('doc-stats.json', JSON.stringify(stats, null, 2));
335
}
336
};
337
```
338
339
### Custom Tag Plugin
340
341
```javascript
342
// version-tag-plugin.js
343
exports.defineTags = function(dictionary) {
344
dictionary.defineTag('version', {
345
mustHaveValue: true,
346
canHaveType: false,
347
canHaveName: false,
348
onTagged: function(doclet, tag) {
349
doclet.version = tag.value;
350
}
351
});
352
353
dictionary.defineTag('since', {
354
mustHaveValue: true,
355
onTagged: function(doclet, tag) {
356
doclet.since = tag.value;
357
}
358
});
359
};
360
```
361
362
### AST Node Visitor Plugin
363
364
```javascript
365
// ast-visitor-plugin.js
366
exports.astNodeVisitor = function(node, e, parser, currentSourceName) {
367
// Process specific node types
368
if (node.type === 'CallExpression') {
369
// Track function calls
370
if (node.callee && node.callee.name === 'deprecated') {
371
// Mark enclosing function as deprecated
372
const doclet = parser._getDocletById(node.parent.nodeId);
373
if (doclet) {
374
doclet.deprecated = true;
375
}
376
}
377
}
378
379
if (node.type === 'ClassDeclaration') {
380
// Process class declarations
381
console.log(`Found class: ${node.id ? node.id.name : 'anonymous'}`);
382
}
383
};
384
```
385
386
### Advanced Plugin with Configuration
387
388
```javascript
389
// configurable-plugin.js
390
let pluginConfig = {
391
logLevel: 'info',
392
outputFile: 'plugin-output.json',
393
processPrivate: false
394
};
395
396
exports.handlers = {
397
parseBegin: function(e) {
398
// Load configuration from JSDoc config
399
const jsdocConfig = require('jsdoc/env').conf;
400
if (jsdocConfig.plugins && jsdocConfig.plugins.configurablePlugin) {
401
pluginConfig = { ...pluginConfig, ...jsdocConfig.plugins.configurablePlugin };
402
}
403
},
404
405
newDoclet: function(e) {
406
if (!pluginConfig.processPrivate && e.doclet.access === 'private') {
407
return;
408
}
409
410
if (pluginConfig.logLevel === 'debug') {
411
console.log('Processing doclet:', e.doclet.longname);
412
}
413
}
414
};
415
```
416
417
## Plugin Configuration
418
419
### In JSDoc Configuration File
420
421
```json
422
{
423
"plugins": [
424
"plugins/markdown",
425
"plugins/overloadHelper",
426
"./custom-plugins/my-plugin"
427
],
428
"custom-plugin-config": {
429
"logLevel": "debug",
430
"outputFile": "custom-output.json"
431
}
432
}
433
```
434
435
### Plugin Loading Order
436
437
Plugins are loaded in the order specified in the configuration:
438
439
1. Plugin handlers are registered with the parser
440
2. Custom tags are defined in the tag dictionary
441
3. AST node visitors are added to the parser
442
4. Parser events fire handlers in registration order
443
444
## Plugin Event Reference
445
446
### Available Events
447
448
```javascript { .api }
449
// Parser lifecycle events
450
interface ParserEvents {
451
parseBegin: { sourcefiles: string[] };
452
fileBegin: { filename: string };
453
beforeParse: { filename: string; source: string };
454
jsdocCommentFound: {
455
comment: string;
456
lineno: number;
457
filename: string;
458
};
459
symbolFound: any;
460
newDoclet: { doclet: Doclet };
461
fileComplete: { filename: string };
462
parseComplete: {
463
sourcefiles: string[];
464
doclets: Doclet[];
465
};
466
processingComplete: { doclets: Doclet[] };
467
}
468
```
469
470
### Event Handler Examples
471
472
```javascript
473
// Comprehensive event handling
474
exports.handlers = {
475
parseBegin: function(e) {
476
this.startTime = Date.now();
477
console.log(`Starting parse of ${e.sourcefiles.length} files`);
478
},
479
480
fileBegin: function(e) {
481
console.log(`Processing file: ${e.filename}`);
482
},
483
484
jsdocCommentFound: function(e) {
485
// Validate comment format
486
if (!e.comment.includes('@')) {
487
console.warn(`Minimal JSDoc at ${e.filename}:${e.lineno}`);
488
}
489
},
490
491
newDoclet: function(e) {
492
// Enhance doclets
493
if (e.doclet.kind === 'function') {
494
e.doclet.isFunction = true;
495
}
496
},
497
498
parseComplete: function(e) {
499
const duration = Date.now() - this.startTime;
500
console.log(`Parse complete in ${duration}ms: ${e.doclets.length} doclets`);
501
}
502
};
503
```
504
505
## Usage Examples
506
507
### Loading Plugins
508
509
```javascript
510
const plugins = require('jsdoc/plugins');
511
const parser = require('jsdoc/src/parser').createParser();
512
513
// Install plugins
514
const pluginPaths = [
515
'plugins/markdown',
516
'plugins/overloadHelper',
517
'./my-custom-plugin'
518
];
519
520
plugins.installPlugins(pluginPaths, parser);
521
```
522
523
### Plugin Testing
524
525
```javascript
526
// test-plugin.js
527
const testPlugin = {
528
handlers: {
529
newDoclet: function(e) {
530
e.doclet.tested = true;
531
}
532
}
533
};
534
535
// Install and test
536
const parser = require('jsdoc/src/parser').createParser();
537
parser.on('newDoclet', testPlugin.handlers.newDoclet);
538
539
const doclets = parser.parse(['test-file.js']);
540
console.log('All doclets tested:', doclets.every(d => d.tested));
541
```
542
543
### Plugin Development Best Practices
544
545
```javascript
546
// good-plugin.js
547
exports.handlers = {
548
newDoclet: function(e) {
549
// Always check for existence
550
if (e.doclet && e.doclet.description) {
551
// Avoid modifying original strings
552
e.doclet.processedDescription = processDescription(e.doclet.description);
553
}
554
}
555
};
556
557
exports.defineTags = function(dictionary) {
558
// Provide complete tag definitions
559
dictionary.defineTag('customTag', {
560
mustHaveValue: true,
561
canHaveType: false,
562
canHaveName: false,
563
onTagged: function(doclet, tag) {
564
// Validate tag value
565
if (typeof tag.value === 'string' && tag.value.length > 0) {
566
doclet.customTag = tag.value;
567
}
568
}
569
});
570
};
571
572
function processDescription(description) {
573
// Safe processing logic
574
try {
575
return description.replace(/\[note\]/g, '<strong>Note:</strong>');
576
} catch (error) {
577
console.warn('Description processing failed:', error.message);
578
return description;
579
}
580
}
581
```