Skip to content

Latest commit

 

History

History
2158 lines (1920 loc) · 56.3 KB

File metadata and controls

2158 lines (1920 loc) · 56.3 KB
 
Jul 27, 2023
Jul 27, 2023
1
import getDefaultTagStructureForMode from './getDefaultTagStructureForMode.js';
Aug 4, 2020
Aug 4, 2020
2
import {
Dec 21, 2021
Dec 21, 2021
3
closureTags,
May 15, 2023
May 15, 2023
4
jsdocTags,
Dec 21, 2021
Dec 21, 2021
5
typeScriptTags,
Jul 27, 2023
Jul 27, 2023
6
} from './tagNames.js';
7
import WarnSettings from './WarnSettings.js';
May 15, 2023
May 15, 2023
8
import {
Oct 9, 2025
Oct 9, 2025
9
stringify,
May 15, 2023
May 15, 2023
10
tryParse,
11
} from '@es-joy/jsdoccomment';
Oct 12, 2019
Oct 12, 2019
12
Feb 13, 2022
Feb 13, 2022
13
/**
May 11, 2023
May 11, 2023
14
* @typedef {number} Integer
15
*/
16
/**
17
* @typedef {import('./utils/hasReturnValue.js').ESTreeOrTypeScriptNode} ESTreeOrTypeScriptNode
18
*/
19
20
/**
21
* @typedef {"jsdoc"|"typescript"|"closure"|"permissive"} ParserMode
Feb 13, 2022
Feb 13, 2022
22
*/
Dec 28, 2015
Dec 28, 2015
23
May 11, 2023
May 11, 2023
24
/**
25
* @type {import('./getDefaultTagStructureForMode.js').TagStructure}
26
*/
Jul 19, 2020
Jul 19, 2020
27
let tagStructure;
28
May 11, 2023
May 11, 2023
29
/**
May 11, 2023
May 11, 2023
30
* @param {ParserMode} mode
May 11, 2023
May 11, 2023
31
* @returns {void}
32
*/
Jul 19, 2020
Jul 19, 2020
33
const setTagStructure = (mode) => {
34
tagStructure = getDefaultTagStructureForMode(mode);
35
};
36
May 13, 2023
May 13, 2023
37
/**
May 14, 2023
May 14, 2023
38
* @typedef {undefined|string|{
May 16, 2023
May 16, 2023
39
* name: Integer,
40
* restElement: boolean
41
* }|{
May 14, 2023
May 14, 2023
42
* isRestProperty: boolean|undefined,
43
* name: string,
May 16, 2023
May 16, 2023
44
* restElement: boolean
May 14, 2023
May 14, 2023
45
* }|{
May 16, 2023
May 16, 2023
46
* name: string,
May 14, 2023
May 14, 2023
47
* restElement: boolean
May 16, 2023
May 16, 2023
48
* }} ParamCommon
49
*/
50
/**
51
* @typedef {ParamCommon|[string|undefined, (FlattendRootInfo & {
52
* annotationParamName?: string,
53
* })]|NestedParamInfo} ParamNameInfo
May 14, 2023
May 14, 2023
54
*/
55
56
/**
May 16, 2023
May 16, 2023
57
* @typedef {{
May 13, 2023
May 13, 2023
58
* hasPropertyRest: boolean,
59
* hasRestElement: boolean,
60
* names: string[],
61
* rests: boolean[],
May 16, 2023
May 16, 2023
62
* }} FlattendRootInfo
63
*/
64
/**
65
* @typedef {[string, (string[]|ParamInfo[])]} NestedParamInfo
66
*/
67
/**
68
* @typedef {ParamCommon|
69
* [string|undefined, (FlattendRootInfo & {
70
* annotationParamName?: string
71
* })]|
72
* NestedParamInfo} ParamInfo
May 13, 2023
May 13, 2023
73
*/
74
May 11, 2023
May 11, 2023
75
/**
76
* Given a nested array of property names, reduce them to a single array,
May 15, 2023
May 15, 2023
77
* appending the name of the root element along the way if present.
May 13, 2023
May 13, 2023
78
* @callback FlattenRoots
79
* @param {ParamInfo[]} params
80
* @param {string} [root]
May 11, 2023
May 11, 2023
81
* @returns {FlattendRootInfo}
May 11, 2023
May 11, 2023
82
*/
May 13, 2023
May 13, 2023
83
84
/** @type {FlattenRoots} */
Mar 9, 2020
Mar 9, 2020
85
const flattenRoots = (params, root = '') => {
May 8, 2020
May 8, 2020
86
let hasRestElement = false;
87
let hasPropertyRest = false;
May 11, 2023
May 11, 2023
88
89
/**
90
* @type {boolean[]}
91
*/
May 8, 2020
May 8, 2020
92
const rests = [];
May 17, 2020
May 17, 2020
93
May 13, 2023
May 13, 2023
94
const names = params.reduce(
95
/**
96
* @param {string[]} acc
97
* @param {ParamInfo} cur
98
* @returns {string[]}
99
*/
100
(acc, cur) => {
101
if (Array.isArray(cur)) {
102
let nms;
103
if (Array.isArray(cur[1])) {
104
nms = cur[1];
105
} else {
106
if (cur[1].hasRestElement) {
107
hasRestElement = true;
108
}
109
110
if (cur[1].hasPropertyRest) {
111
hasPropertyRest = true;
112
}
113
114
nms = cur[1].names;
115
}
116
117
const flattened = flattenRoots(nms, root ? `${root}.${cur[0]}` : cur[0]);
118
if (flattened.hasRestElement) {
May 8, 2020
May 8, 2020
119
hasRestElement = true;
120
}
Oct 26, 2021
Oct 26, 2021
121
May 13, 2023
May 13, 2023
122
if (flattened.hasPropertyRest) {
May 8, 2020
May 8, 2020
123
hasPropertyRest = true;
124
}
Oct 26, 2021
Oct 26, 2021
125
May 16, 2023
May 16, 2023
126
const inner = /** @type {string[]} */ ([
May 13, 2023
May 13, 2023
127
root ? `${root}.${cur[0]}` : cur[0],
128
...flattened.names,
May 16, 2023
May 16, 2023
129
].filter(Boolean));
May 13, 2023
May 13, 2023
130
rests.push(false, ...flattened.rests);
May 8, 2020
May 8, 2020
131
May 13, 2023
May 13, 2023
132
return acc.concat(inner);
May 8, 2020
May 8, 2020
133
}
Oct 26, 2021
Oct 26, 2021
134
May 13, 2023
May 13, 2023
135
if (typeof cur === 'object') {
May 16, 2023
May 16, 2023
136
if ('isRestProperty' in cur && cur.isRestProperty) {
May 13, 2023
May 13, 2023
137
hasPropertyRest = true;
138
rests.push(true);
139
} else {
140
rests.push(false);
141
}
Mar 10, 2020
Mar 10, 2020
142
May 16, 2023
May 16, 2023
143
if ('restElement' in cur && cur.restElement) {
May 13, 2023
May 13, 2023
144
hasRestElement = true;
145
}
Oct 26, 2021
Oct 26, 2021
146
May 16, 2023
May 16, 2023
147
acc.push(root ? `${root}.${String(cur.name)}` : String(cur.name));
May 13, 2023
May 13, 2023
148
} else if (typeof cur !== 'undefined') {
May 8, 2020
May 8, 2020
149
rests.push(false);
May 13, 2023
May 13, 2023
150
acc.push(root ? `${root}.${cur}` : cur);
May 8, 2020
May 8, 2020
151
}
Oct 26, 2021
Oct 26, 2021
152
May 13, 2023
May 13, 2023
153
return acc;
154
}, [],
155
);
May 8, 2020
May 8, 2020
156
157
return {
158
hasPropertyRest,
159
hasRestElement,
160
names,
May 8, 2020
May 8, 2020
161
rests,
May 8, 2020
May 8, 2020
162
};
Mar 9, 2020
Mar 9, 2020
163
};
164
Feb 13, 2022
Feb 13, 2022
165
/**
May 13, 2023
May 13, 2023
166
* @param {import('@typescript-eslint/types').TSESTree.TSIndexSignature|
167
* import('@typescript-eslint/types').TSESTree.TSConstructSignatureDeclaration|
168
* import('@typescript-eslint/types').TSESTree.TSCallSignatureDeclaration|
169
* import('@typescript-eslint/types').TSESTree.TSPropertySignature} propSignature
170
* @returns {undefined|string|[string, string[]]}
Feb 13, 2022
Feb 13, 2022
171
*/
172
const getPropertiesFromPropertySignature = (propSignature) => {
Aug 17, 2020
Aug 17, 2020
173
if (
174
propSignature.type === 'TSIndexSignature' ||
175
propSignature.type === 'TSConstructSignatureDeclaration' ||
176
propSignature.type === 'TSCallSignatureDeclaration'
177
) {
Jun 8, 2020
Jun 8, 2020
178
return undefined;
179
}
Oct 26, 2021
Oct 26, 2021
180
Mar 9, 2020
Mar 9, 2020
181
if (propSignature.typeAnnotation && propSignature.typeAnnotation.typeAnnotation.type === 'TSTypeLiteral') {
Dec 21, 2021
Dec 21, 2021
182
return [
May 13, 2023
May 13, 2023
183
/** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
184
propSignature.key
185
).name,
186
propSignature.typeAnnotation.typeAnnotation.members.map((member) => {
187
return /** @type {string} */ (
188
getPropertiesFromPropertySignature(
189
/** @type {import('@typescript-eslint/types').TSESTree.TSPropertySignature} */ (
190
member
191
),
192
)
193
);
Dec 21, 2021
Dec 21, 2021
194
}),
195
];
Mar 9, 2020
Mar 9, 2020
196
}
197
May 13, 2023
May 13, 2023
198
return /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
199
propSignature.key
200
).name;
Mar 9, 2020
Mar 9, 2020
201
};
202
Feb 13, 2022
Feb 13, 2022
203
/**
May 15, 2023
May 15, 2023
204
* @param {ESTreeOrTypeScriptNode|null} functionNode
May 11, 2023
May 11, 2023
205
* @param {boolean} [checkDefaultObjects]
Nov 13, 2025
Nov 13, 2025
206
* @param {boolean} [ignoreInterfacedParameters]
May 13, 2023
May 13, 2023
207
* @throws {Error}
208
* @returns {ParamNameInfo[]}
Feb 13, 2022
Feb 13, 2022
209
*/
Jan 24, 2021
Jan 24, 2021
210
const getFunctionParameterNames = (
Nov 13, 2025
Nov 13, 2025
211
functionNode, checkDefaultObjects, ignoreInterfacedParameters,
Feb 13, 2022
Feb 13, 2022
212
) => {
May 11, 2023
May 11, 2023
213
/* eslint-disable complexity -- Temporary */
214
/**
May 13, 2023
May 13, 2023
215
* @param {import('estree').Identifier|import('estree').AssignmentPattern|
216
* import('estree').ObjectPattern|import('estree').Property|
217
* import('estree').RestElement|import('estree').ArrayPattern|
May 14, 2023
May 14, 2023
218
* import('@typescript-eslint/types').TSESTree.TSParameterProperty|
219
* import('@typescript-eslint/types').TSESTree.Property|
May 15, 2023
May 15, 2023
220
* import('@typescript-eslint/types').TSESTree.RestElement|
221
* import('@typescript-eslint/types').TSESTree.Identifier|
222
* import('@typescript-eslint/types').TSESTree.ObjectPattern|
May 16, 2023
May 16, 2023
223
* import('@typescript-eslint/types').TSESTree.BindingName|
224
* import('@typescript-eslint/types').TSESTree.Parameter
May 13, 2023
May 13, 2023
225
* } param
May 11, 2023
May 11, 2023
226
* @param {boolean} [isProperty]
May 16, 2023
May 16, 2023
227
* @returns {ParamNameInfo|[string, ParamNameInfo[]]}
May 11, 2023
May 11, 2023
228
*/
May 8, 2020
May 8, 2020
229
const getParamName = (param, isProperty) => {
May 11, 2023
May 11, 2023
230
/* eslint-enable complexity -- Temporary */
Nov 29, 2021
Nov 29, 2021
231
const hasLeftTypeAnnotation = 'left' in param && 'typeAnnotation' in param.left;
232
233
if ('typeAnnotation' in param || hasLeftTypeAnnotation) {
Nov 13, 2025
Nov 13, 2025
234
if (ignoreInterfacedParameters && 'typeAnnotation' in param &&
235
param.typeAnnotation) {
236
// No-op
237
return [
238
undefined, {
239
hasPropertyRest: false,
240
hasRestElement: false,
241
names: [],
242
rests: [],
243
},
244
];
245
}
246
May 13, 2023
May 13, 2023
247
const typeAnnotation = hasLeftTypeAnnotation ?
248
/** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
249
param.left
May 14, 2023
May 14, 2023
250
).typeAnnotation :
251
/** @type {import('@typescript-eslint/types').TSESTree.Identifier|import('@typescript-eslint/types').TSESTree.ObjectPattern} */
252
(param).typeAnnotation;
Nov 29, 2021
Nov 29, 2021
253
Mar 31, 2023
Mar 31, 2023
254
if (typeAnnotation?.typeAnnotation?.type === 'TSTypeLiteral') {
Feb 29, 2020
Feb 29, 2020
255
const propertyNames = typeAnnotation.typeAnnotation.members.map((member) => {
May 14, 2023
May 14, 2023
256
return getPropertiesFromPropertySignature(
257
/** @type {import('@typescript-eslint/types').TSESTree.TSPropertySignature} */
258
(member),
259
);
Feb 29, 2020
Feb 29, 2020
260
});
May 14, 2023
May 14, 2023
261
Jun 20, 2020
Jun 20, 2020
262
const flattened = {
263
...flattenRoots(propertyNames),
May 14, 2023
May 14, 2023
264
annotationParamName: 'name' in param ? param.name : undefined,
Jun 20, 2020
Jun 20, 2020
265
};
Nov 29, 2021
Nov 29, 2021
266
const hasLeftName = 'left' in param && 'name' in param.left;
267
268
if ('name' in param || hasLeftName) {
Dec 21, 2021
Dec 21, 2021
269
return [
May 14, 2023
May 14, 2023
270
hasLeftName ?
271
/** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
272
param.left
273
).name :
274
/** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
275
param
276
).name,
277
flattened,
Dec 21, 2021
Dec 21, 2021
278
];
Feb 29, 2020
Feb 29, 2020
279
}
280
Dec 21, 2021
Dec 21, 2021
281
return [
282
undefined, flattened,
283
];
Feb 29, 2020
Feb 29, 2020
284
}
285
}
286
Nov 29, 2021
Nov 29, 2021
287
if ('name' in param) {
Nov 23, 2016
Nov 23, 2016
288
return param.name;
289
}
Dec 28, 2015
Dec 28, 2015
290
Nov 29, 2021
Nov 29, 2021
291
if ('left' in param && 'name' in param.left) {
Nov 23, 2016
Nov 23, 2016
292
return param.left.name;
293
}
Dec 28, 2015
Dec 28, 2015
294
May 14, 2023
May 14, 2023
295
if (
296
param.type === 'ObjectPattern' ||
297
('left' in param &&
298
(
299
param
300
).left.type === 'ObjectPattern')
301
) {
302
const properties = /** @type {import('@typescript-eslint/types').TSESTree.ObjectPattern} */ (
303
param
304
).properties ||
305
/** @type {import('estree').ObjectPattern} */
306
(
307
/** @type {import('@typescript-eslint/types').TSESTree.AssignmentPattern} */ (
308
param
309
).left
310
)?.properties;
May 7, 2020
May 7, 2020
311
const roots = properties.map((prop) => {
May 8, 2020
May 8, 2020
312
return getParamName(prop, true);
May 7, 2020
May 7, 2020
313
});
Feb 29, 2020
Feb 29, 2020
314
Dec 21, 2021
Dec 21, 2021
315
return [
316
undefined, flattenRoots(roots),
317
];
Nov 23, 2016
Nov 23, 2016
318
}
Jan 6, 2016
Jan 6, 2016
319
Mar 9, 2020
Mar 9, 2020
320
if (param.type === 'Property') {
Feb 23, 2021
Feb 23, 2021
321
switch (param.value.type) {
May 31, 2025
May 31, 2025
322
case 'ArrayPattern': {
Dec 21, 2021
Dec 21, 2021
323
return [
May 31, 2025
May 31, 2025
324
/** @type {import('estree').Identifier} */
May 16, 2023
May 16, 2023
325
(param.key).name,
326
/** @type {import('estree').ArrayPattern} */ (
May 31, 2025
May 31, 2025
327
param.value
May 11, 2023
May 11, 2023
328
).elements.map((prop, idx) => {
Dec 21, 2021
Dec 21, 2021
329
return {
330
name: idx,
May 11, 2023
May 11, 2023
331
restElement: prop?.type === 'RestElement',
Dec 21, 2021
Dec 21, 2021
332
};
333
}),
334
];
Sep 12, 2020
Sep 12, 2020
335
}
May 31, 2025
May 31, 2025
336
337
case 'ObjectPattern': {
338
return [
339
/** @type {import('estree').Identifier} */ (param.key).name,
340
/** @type {import('estree').ObjectPattern} */ (
341
param.value
342
).properties.map((prop) => {
343
return /** @type {string|[string, string[]]} */ (getParamName(prop, isProperty));
344
}),
345
];
346
}
347
348
case 'AssignmentPattern': {
349
switch (param.value.left.type) {
350
case 'ArrayPattern':
351
return [
352
/** @type {import('estree').Identifier} */
353
(param.key).name,
354
/** @type {import('estree').ArrayPattern} */ (
355
param.value.left
356
).elements.map((prop, idx) => {
357
return {
358
name: idx,
359
restElement: prop?.type === 'RestElement',
360
};
361
}),
362
];
363
case 'Identifier':
364
// Default parameter
365
if (checkDefaultObjects && param.value.right.type === 'ObjectExpression') {
366
return [
367
/** @type {import('estree').Identifier} */ (
368
param.key
369
).name,
370
/** @type {import('estree').AssignmentPattern} */ (
371
param.value
372
).right.properties.map((prop) => {
373
return /** @type {string} */ (getParamName(
374
/** @type {import('estree').Property} */
375
(prop),
376
isProperty,
377
));
378
}),
379
];
380
}
381
382
break;
383
case 'ObjectPattern':
384
return [
385
/** @type {import('estree').Identifier} */
386
(param.key).name,
387
/** @type {import('estree').ObjectPattern} */ (
388
param.value.left
389
).properties.map((prop) => {
390
return getParamName(prop, isProperty);
391
}),
392
];
393
}
394
}
Feb 23, 2021
Feb 23, 2021
395
}
Mar 9, 2020
Mar 9, 2020
396
Feb 23, 2021
Feb 23, 2021
397
switch (param.key.type) {
May 31, 2025
May 31, 2025
398
case 'Identifier':
399
return param.key.name;
Jul 9, 2020
Jul 9, 2020
400
May 31, 2025
May 31, 2025
401
// The key of an object could also be a string or number
402
case 'Literal':
Jan 2, 2024
Jan 2, 2024
403
/* c8 ignore next 2 -- `raw` may not be present in all parsers */
May 31, 2025
May 31, 2025
404
return /** @type {string} */ (param.key.raw ||
May 16, 2023
May 16, 2023
405
param.key.value);
Feb 23, 2021
Feb 23, 2021
406
May 31, 2025
May 31, 2025
407
// case 'MemberExpression':
408
default:
Feb 23, 2021
Feb 23, 2021
409
// Todo: We should really create a structure (and a corresponding
410
// option analogous to `checkRestProperty`) which allows for
411
// (and optionally requires) dynamic properties to have a single
412
// line of documentation
May 31, 2025
May 31, 2025
413
return undefined;
Jul 9, 2020
Jul 9, 2020
414
}
Mar 9, 2020
Mar 9, 2020
415
}
416
May 16, 2023
May 16, 2023
417
if (
418
param.type === 'ArrayPattern' ||
419
/** @type {import('estree').AssignmentPattern} */ (
420
param
421
).left?.type === 'ArrayPattern'
422
) {
May 11, 2023
May 11, 2023
423
const elements = /** @type {import('estree').ArrayPattern} */ (
424
param
425
).elements || /** @type {import('estree').ArrayPattern} */ (
May 16, 2023
May 16, 2023
426
/** @type {import('estree').AssignmentPattern} */ (
427
param
428
).left
May 11, 2023
May 11, 2023
429
)?.elements;
May 7, 2020
May 7, 2020
430
const roots = elements.map((prop, idx) => {
May 8, 2020
May 8, 2020
431
return {
Apr 28, 2021
Apr 28, 2021
432
name: `"${idx}"`,
433
restElement: prop?.type === 'RestElement',
May 8, 2020
May 8, 2020
434
};
May 7, 2020
May 7, 2020
435
});
436
Dec 21, 2021
Dec 21, 2021
437
return [
438
undefined, flattenRoots(roots),
439
];
Dec 7, 2018
Dec 7, 2018
440
}
441
Dec 21, 2021
Dec 21, 2021
442
if ([
May 31, 2025
May 31, 2025
443
'ExperimentalRestProperty', 'RestElement',
Dec 21, 2021
Dec 21, 2021
444
].includes(param.type)) {
May 7, 2020
May 7, 2020
445
return {
May 8, 2020
May 8, 2020
446
isRestProperty: isProperty,
May 16, 2023
May 16, 2023
447
name: /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
448
/** @type {import('@typescript-eslint/types').TSESTree.RestElement} */ (
449
param
Jul 5, 2024
Jul 5, 2024
450
// @ts-expect-error Ok
May 31, 2025
May 31, 2025
451
).argument).name ?? param?.argument?.elements?.map(({
452
// @ts-expect-error Ok
453
name,
454
}) => {
455
return name;
456
}),
May 7, 2020
May 7, 2020
457
restElement: true,
458
};
Nov 23, 2016
Nov 23, 2016
459
}
Feb 14, 2016
Feb 14, 2016
460
Jun 21, 2019
Jun 21, 2019
461
if (param.type === 'TSParameterProperty') {
May 15, 2023
May 15, 2023
462
return getParamName(
463
/** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
464
/** @type {import('@typescript-eslint/types').TSESTree.TSParameterProperty} */ (
465
param
466
).parameter
467
),
468
true,
469
);
Jun 21, 2019
Jun 21, 2019
470
}
471
Feb 23, 2021
Feb 23, 2021
472
throw new Error(`Unsupported function signature format: \`${param.type}\`.`);
Jun 21, 2019
Jun 21, 2019
473
};
474
Jul 29, 2022
Jul 29, 2022
475
if (!functionNode) {
476
return [];
477
}
478
Feb 15, 2026
Feb 15, 2026
479
return (
480
/** @type {import('@typescript-eslint/types').TSESTree.TSFunctionType} */
481
(/** @type {import('@typescript-eslint/types').TSESTree.TSPropertySignature} */ (functionNode)?.typeAnnotation?.typeAnnotation)?.params ||
482
/** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */ (
483
functionNode
484
).params || /** @type {import('@typescript-eslint/types').TSESTree.MethodDefinition} */ (
485
functionNode
486
).value?.params || []).map((param) => {
May 8, 2020
May 8, 2020
487
return getParamName(param);
488
});
Dec 28, 2015
Dec 28, 2015
489
};
490
Feb 13, 2022
Feb 13, 2022
491
/**
May 11, 2023
May 11, 2023
492
* @param {ESTreeOrTypeScriptNode} functionNode
Feb 13, 2022
Feb 13, 2022
493
* @returns {Integer}
494
*/
Sep 9, 2020
Sep 9, 2020
495
const hasParams = (functionNode) => {
496
// Should also check `functionNode.value.params` if supporting `MethodDefinition`
May 15, 2023
May 15, 2023
497
return /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */ (
498
functionNode
499
).params.length;
Sep 9, 2020
Sep 9, 2020
500
};
501
Dec 30, 2015
Dec 30, 2015
502
/**
Jan 1, 2020
Jan 1, 2020
503
* Gets all names of the target type, including those that refer to a path, e.g.
Jun 28, 2023
Jun 28, 2023
504
* `foo` or `foo.bar`.
May 11, 2023
May 11, 2023
505
* @param {import('comment-parser').Block} jsdoc
Feb 13, 2022
Feb 13, 2022
506
* @param {string} targetTagName
May 11, 2023
May 11, 2023
507
* @returns {{
508
* idx: Integer,
509
* name: string,
510
* type: string
511
* }[]}
Dec 30, 2015
Dec 30, 2015
512
*/
Feb 13, 2022
Feb 13, 2022
513
const getJsdocTagsDeep = (jsdoc, targetTagName) => {
May 17, 2020
May 17, 2020
514
const ret = [];
Dec 21, 2021
Dec 21, 2021
515
for (const [
516
idx,
517
{
518
name,
519
tag,
520
type,
521
},
522
] of jsdoc.tags.entries()) {
Dec 31, 2019
Dec 31, 2019
523
if (tag !== targetTagName) {
Oct 26, 2021
Oct 26, 2021
524
continue;
Dec 31, 2019
Dec 31, 2019
525
}
Oct 26, 2021
Oct 26, 2021
526
May 17, 2020
May 17, 2020
527
ret.push({
Dec 31, 2019
Dec 31, 2019
528
idx,
529
name,
May 8, 2020
May 8, 2020
530
type,
Dec 31, 2019
Dec 31, 2019
531
});
Oct 26, 2021
Oct 26, 2021
532
}
Dec 28, 2015
Dec 28, 2015
533
May 17, 2020
May 17, 2020
534
return ret;
Dec 28, 2015
Dec 28, 2015
535
};
536
Oct 29, 2019
Oct 29, 2019
537
const modeWarnSettings = WarnSettings();
538
Feb 13, 2022
Feb 13, 2022
539
/**
May 13, 2023
May 13, 2023
540
* @param {ParserMode|undefined} mode
Oct 20, 2025
Oct 20, 2025
541
* @param {import('eslint').Rule.RuleContext} context
May 11, 2023
May 11, 2023
542
* @returns {import('./tagNames.js').AliasedTags}
Feb 13, 2022
Feb 13, 2022
543
*/
Oct 29, 2019
Oct 29, 2019
544
const getTagNamesForMode = (mode, context) => {
545
switch (mode) {
May 31, 2025
May 31, 2025
546
case 'closure':
547
case 'permissive':
548
return closureTags;
549
case 'jsdoc':
Sep 9, 2025
Sep 9, 2025
550
return jsdocTags;
551
case 'typescript':
May 31, 2025
May 31, 2025
552
return typeScriptTags;
553
default:
554
if (!modeWarnSettings.hasBeenWarned(context, 'mode')) {
555
context.report({
556
loc: {
557
end: {
558
column: 1,
559
line: 1,
560
},
561
start: {
562
column: 1,
563
line: 1,
564
},
May 15, 2023
May 15, 2023
565
},
May 31, 2025
May 31, 2025
566
message: `Unrecognized value \`${mode}\` for \`settings.jsdoc.mode\`.`,
567
});
568
modeWarnSettings.markSettingAsWarned(context, 'mode');
569
}
Oct 29, 2019
Oct 29, 2019
570
May 31, 2025
May 31, 2025
571
// We'll avoid breaking too many other rules
572
return jsdocTags;
Oct 29, 2019
Oct 29, 2019
573
}
574
};
575
Jul 25, 2024
Jul 25, 2024
576
/**
577
* @param {import('comment-parser').Spec} tg
578
* @param {boolean} [returnArray]
579
* @returns {string[]|string}
580
*/
581
const getTagDescription = (tg, returnArray) => {
May 31, 2025
May 31, 2025
582
/**
583
* @type {string[]}
584
*/
585
const descriptions = [];
586
tg.source.some(({
587
tokens: {
588
description,
589
end,
590
lineEnd,
591
name,
592
postDelimiter,
593
postTag,
594
tag,
595
type,
596
},
597
}) => {
598
const desc = (
599
tag && postTag ||
Jul 25, 2024
Jul 25, 2024
600
!tag && !name && !type && postDelimiter || ''
601
May 31, 2025
May 31, 2025
602
// Remove space
603
).slice(1) +
Jul 25, 2024
Jul 25, 2024
604
(description || '') + (lineEnd || '');
605
May 31, 2025
May 31, 2025
606
if (end) {
607
if (desc) {
608
descriptions.push(desc);
Jul 25, 2024
Jul 25, 2024
609
}
610
May 31, 2025
May 31, 2025
611
return true;
612
}
Jul 25, 2024
Jul 25, 2024
613
May 31, 2025
May 31, 2025
614
descriptions.push(desc);
Jul 25, 2024
Jul 25, 2024
615
May 31, 2025
May 31, 2025
616
return false;
617
});
618
619
return returnArray ? descriptions : descriptions.join('\n');
Jul 25, 2024
Jul 25, 2024
620
};
621
Feb 13, 2022
Feb 13, 2022
622
/**
Jul 25, 2024
Jul 25, 2024
623
* @typedef {{
624
* report: (descriptor: import('eslint').Rule.ReportDescriptor) => void
625
* }} Reporter
626
*/
627
628
/**
Feb 13, 2022
Feb 13, 2022
629
* @param {string} name
Jul 25, 2024
Jul 25, 2024
630
* @param {ParserMode|undefined} mode
May 11, 2023
May 11, 2023
631
* @param {TagNamePreference} tagPreference
Oct 20, 2025
Oct 20, 2025
632
* @param {import('eslint').Rule.RuleContext} context
May 15, 2023
May 15, 2023
633
* @returns {string|false|{
May 11, 2023
May 11, 2023
634
* message: string;
635
* replacement?: string|undefined;
636
* }}
Feb 13, 2022
Feb 13, 2022
637
*/
Jul 25, 2024
Jul 25, 2024
638
const getPreferredTagNameSimple = (
Feb 13, 2022
Feb 13, 2022
639
name,
Jul 25, 2024
Jul 25, 2024
640
mode,
Feb 13, 2022
Feb 13, 2022
641
tagPreference = {},
Oct 20, 2025
Oct 20, 2025
642
// @ts-expect-error Just a no-op
May 31, 2025
May 31, 2025
643
// eslint-disable-next-line unicorn/no-object-as-default-parameter -- Ok
Jul 25, 2024
Jul 25, 2024
644
context = {
645
report () {
646
// No-op
May 31, 2025
May 31, 2025
647
},
Jul 25, 2024
Jul 25, 2024
648
},
Feb 13, 2022
Feb 13, 2022
649
) => {
May 14, 2020
May 14, 2020
650
const prefValues = Object.values(tagPreference);
Aug 20, 2019
Aug 20, 2019
651
if (prefValues.includes(name) || prefValues.some((prefVal) => {
652
return prefVal && typeof prefVal === 'object' && prefVal.replacement === name;
653
})) {
Nov 23, 2016
Nov 23, 2016
654
return name;
655
}
Dec 30, 2015
Dec 30, 2015
656
May 14, 2020
May 14, 2020
657
// Allow keys to have a 'tag ' prefix to avoid upstream bug in ESLint
658
// that disallows keys that conflict with Object.prototype,
Jun 23, 2020
Jun 23, 2020
659
// e.g. 'tag constructor' for 'constructor':
660
// https://github.com/eslint/eslint/issues/13289
661
// https://github.com/gajus/eslint-plugin-jsdoc/issues/537
Nov 29, 2021
Nov 29, 2021
662
const tagPreferenceFixed = Object.fromEntries(
663
Object
664
.entries(tagPreference)
Dec 21, 2021
Dec 21, 2021
665
.map(([
666
key,
667
value,
668
]) => {
669
return [
Jul 28, 2025
Jul 28, 2025
670
key.replace(/^tag /v, ''), value,
Dec 21, 2021
Dec 21, 2021
671
];
Nov 29, 2021
Nov 29, 2021
672
}),
673
);
674
Jul 28, 2025
Jul 28, 2025
675
if (Object.hasOwn(tagPreferenceFixed, name)) {
May 14, 2020
May 14, 2020
676
return tagPreferenceFixed[name];
Jun 30, 2019
Jun 30, 2019
677
}
678
Oct 29, 2019
Oct 29, 2019
679
const tagNames = getTagNamesForMode(mode, context);
680
Dec 21, 2021
Dec 21, 2021
681
const preferredTagName = Object.entries(tagNames).find(([
682
, aliases,
683
]) => {
May 16, 2019
May 16, 2019
684
return aliases.includes(name);
May 14, 2020
May 14, 2020
685
})?.[0];
Nov 23, 2016
Nov 23, 2016
686
if (preferredTagName) {
687
return preferredTagName;
688
}
Dec 30, 2015
Dec 30, 2015
689
Jun 30, 2019
Jun 30, 2019
690
return name;
Dec 30, 2015
Dec 30, 2015
691
};
692
Feb 13, 2022
Feb 13, 2022
693
/**
May 13, 2023
May 13, 2023
694
* @param {import('eslint').Rule.RuleContext} context
695
* @param {ParserMode|undefined} mode
Feb 13, 2022
Feb 13, 2022
696
* @param {string} name
May 13, 2023
May 13, 2023
697
* @param {string[]} definedTags
Feb 13, 2022
Feb 13, 2022
698
* @returns {boolean}
699
*/
Oct 29, 2019
Oct 29, 2019
700
const isValidTag = (
701
context,
Feb 13, 2022
Feb 13, 2022
702
mode,
703
name,
704
definedTags,
705
) => {
Oct 29, 2019
Oct 29, 2019
706
const tagNames = getTagNamesForMode(mode, context);
Apr 26, 2021
Apr 26, 2021
707
May 11, 2021
May 11, 2021
708
const validTagNames = Object.keys(tagNames).concat(Object.values(tagNames).flat());
Jul 5, 2019
Jul 5, 2019
709
const additionalTags = definedTags;
Nov 23, 2016
Nov 23, 2016
710
const allTags = validTagNames.concat(additionalTags);
Dec 30, 2015
Dec 30, 2015
711
May 16, 2019
May 16, 2019
712
return allTags.includes(name);
Dec 30, 2015
Dec 30, 2015
713
};
714
Feb 13, 2022
Feb 13, 2022
715
/**
May 15, 2023
May 15, 2023
716
* @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
Feb 13, 2022
Feb 13, 2022
717
* @param {string} targetTagName
718
* @returns {boolean}
719
*/
720
const hasTag = (jsdoc, targetTagName) => {
Nov 23, 2016
Nov 23, 2016
721
const targetTagLower = targetTagName.toLowerCase();
722
Feb 13, 2022
Feb 13, 2022
723
return jsdoc.tags.some((doc) => {
Nov 23, 2016
Nov 23, 2016
724
return doc.tag.toLowerCase() === targetTagLower;
725
});
Nov 23, 2016
Nov 23, 2016
726
};
727
Jul 25, 2024
Jul 25, 2024
728
/**
729
* @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
730
* @param {(tag: import('@es-joy/jsdoccomment').JsdocTagWithInline) => boolean} filter
731
* @returns {import('@es-joy/jsdoccomment').JsdocTagWithInline[]}
732
*/
733
const filterTags = (jsdoc, filter) => {
734
return jsdoc.tags.filter((tag) => {
735
return filter(tag);
736
});
737
};
738
739
/**
740
* @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
741
* @param {string} tagName
742
* @returns {import('comment-parser').Spec[]}
743
*/
744
const getTags = (jsdoc, tagName) => {
745
return filterTags(jsdoc, (item) => {
746
return item.tag === tagName;
747
});
748
};
749
750
/**
751
* @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
752
* @param {{
753
* tagName: string,
Jul 29, 2024
Jul 29, 2024
754
* context?: import('eslint').Rule.RuleContext,
755
* mode?: ParserMode,
756
* report?: import('./iterateJsdoc.js').Report
757
* tagNamePreference?: TagNamePreference
Jul 25, 2024
Jul 25, 2024
758
* skipReportingBlockedTag?: boolean,
759
* allowObjectReturn?: boolean,
Jul 25, 2024
Jul 25, 2024
760
* defaultMessage?: string,
Jul 25, 2024
Jul 25, 2024
761
* }} cfg
762
* @returns {string|undefined|false|{
763
* message: string;
764
* replacement?: string|undefined;
765
* }|{
766
* blocked: true,
767
* tagName: string
768
* }}
769
*/
Jul 25, 2024
Jul 25, 2024
770
const getPreferredTagName = (jsdoc, {
May 31, 2025
May 31, 2025
771
allowObjectReturn = false,
772
context,
Jul 25, 2024
Jul 25, 2024
773
tagName,
May 31, 2025
May 31, 2025
774
defaultMessage = `Unexpected tag \`@${tagName}\``,
775
mode,
Jul 29, 2024
Jul 29, 2024
776
report = () => {},
Jul 25, 2024
Jul 25, 2024
777
skipReportingBlockedTag = false,
May 31, 2025
May 31, 2025
778
tagNamePreference,
Jul 25, 2024
Jul 25, 2024
779
}) => {
Jul 25, 2024
Jul 25, 2024
780
const ret = getPreferredTagNameSimple(tagName, mode, tagNamePreference, context);
Jul 25, 2024
Jul 25, 2024
781
const isObject = ret && typeof ret === 'object';
782
if (hasTag(jsdoc, tagName) && (ret === false || isObject && !ret.replacement)) {
783
if (skipReportingBlockedTag) {
784
return {
785
blocked: true,
786
tagName,
787
};
788
}
789
790
const message = isObject && ret.message || defaultMessage;
791
report(message, null, getTags(jsdoc, tagName)[0]);
792
793
return false;
794
}
795
796
return isObject && !allowObjectReturn ? ret.replacement : ret;
797
};
798
799
/**
800
* @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
801
* @param {string} tagName
802
* @param {(
Jul 25, 2024
Jul 25, 2024
803
* matchingJsdocTag: import('@es-joy/jsdoccomment').JsdocTagWithInline,
804
* targetTagName: string
805
* ) => void} arrayHandler
806
* @param {object} cfg
Jul 29, 2024
Jul 29, 2024
807
* @param {import('eslint').Rule.RuleContext} [cfg.context]
808
* @param {ParserMode} [cfg.mode]
809
* @param {import('./iterateJsdoc.js').Report} [cfg.report]
810
* @param {TagNamePreference} [cfg.tagNamePreference]
Jul 25, 2024
Jul 25, 2024
811
* @param {boolean} [cfg.skipReportingBlockedTag]
812
* @returns {void}
813
*/
814
const forEachPreferredTag = (
815
jsdoc, tagName, arrayHandler,
816
{
May 31, 2025
May 31, 2025
817
context,
818
mode,
819
report,
Jul 25, 2024
Jul 25, 2024
820
skipReportingBlockedTag = false,
May 31, 2025
May 31, 2025
821
tagNamePreference,
822
} = {},
Jul 25, 2024
Jul 25, 2024
823
) => {
Jul 25, 2024
Jul 25, 2024
824
const targetTagName = /** @type {string|false} */ (
Jul 25, 2024
Jul 25, 2024
825
getPreferredTagName(jsdoc, {
May 31, 2025
May 31, 2025
826
context,
827
mode,
828
report,
Jul 25, 2024
Jul 25, 2024
829
skipReportingBlockedTag,
830
tagName,
May 31, 2025
May 31, 2025
831
tagNamePreference,
Jul 25, 2024
Jul 25, 2024
832
})
833
);
834
if (!targetTagName ||
835
skipReportingBlockedTag && targetTagName && typeof targetTagName === 'object'
836
) {
837
return;
838
}
839
840
const matchingJsdocTags = jsdoc.tags.filter(({
841
tag,
842
}) => {
843
return tag === targetTagName;
844
});
845
846
for (const matchingJsdocTag of matchingJsdocTags) {
847
arrayHandler(
848
/**
849
* @type {import('@es-joy/jsdoccomment').JsdocTagWithInline}
850
*/ (
851
matchingJsdocTag
852
), targetTagName,
853
);
854
}
855
};
856
May 10, 2023
May 10, 2023
857
/**
Sep 28, 2025
Sep 28, 2025
858
* Get all inline tags and inline tags in tags
May 15, 2023
May 15, 2023
859
* @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
May 11, 2023
May 11, 2023
860
* @returns {(import('comment-parser').Spec|
Sep 28, 2025
Sep 28, 2025
861
* import('@es-joy/jsdoccomment').JsdocInlineTagNoType & {
862
* line?: number | undefined; column?: number | undefined;
863
* })[]}
May 10, 2023
May 10, 2023
864
*/
Sep 28, 2025
Sep 28, 2025
865
const getInlineTags = (jsdoc) => {
May 14, 2023
May 14, 2023
866
return [
May 11, 2023
May 11, 2023
867
...jsdoc.inlineTags.map((inlineTag) => {
868
// Tags don't have source or line numbers, so add before returning
869
let line = -1;
870
for (const {
871
tokens: {
872
description,
873
},
874
} of jsdoc.source) {
875
line++;
876
if (description && description.includes(`{@${inlineTag.tag}`)) {
877
break;
878
}
879
}
880
881
inlineTag.line = line;
882
883
return inlineTag;
884
}),
May 10, 2023
May 10, 2023
885
...jsdoc.tags.flatMap((tag) => {
May 11, 2023
May 11, 2023
886
for (const inlineTag of tag.inlineTags) {
May 15, 2023
May 15, 2023
887
/** @type {import('./iterateJsdoc.js').Integer} */
888
let line = 0;
May 11, 2023
May 11, 2023
889
for (const {
890
number,
891
tokens: {
892
description,
893
},
894
} of tag.source) {
895
if (description && description.includes(`{@${inlineTag.tag}`)) {
896
line = number;
897
break;
898
}
899
}
900
Sep 28, 2025
Sep 28, 2025
901
inlineTag.line = line;
May 11, 2023
May 11, 2023
902
}
903
May 11, 2023
May 11, 2023
904
return (
905
/**
906
* @type {import('comment-parser').Spec & {
907
* inlineTags: import('@es-joy/jsdoccomment').JsdocInlineTagNoType[]
908
* }}
909
*/ (
910
tag
911
).inlineTags
912
);
May 10, 2023
May 10, 2023
913
}),
May 14, 2023
May 14, 2023
914
];
May 10, 2023
May 10, 2023
915
};
916
Sep 28, 2025
Sep 28, 2025
917
/**
918
* Get all tags, inline tags and inline tags in tags
919
* @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
920
* @returns {(import('comment-parser').Spec|
921
* import('@es-joy/jsdoccomment').JsdocInlineTagNoType & {
922
* line?: number | undefined; column?: number | undefined;
923
* })[]}
924
*/
925
const getAllTags = (jsdoc) => {
926
return [
927
...jsdoc.tags,
928
...getInlineTags(jsdoc),
929
];
930
};
931
Feb 13, 2022
Feb 13, 2022
932
/**
May 15, 2023
May 15, 2023
933
* @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
May 11, 2023
May 11, 2023
934
* @param {string[]} targetTagNames
Feb 13, 2022
Feb 13, 2022
935
* @returns {boolean}
936
*/
937
const hasATag = (jsdoc, targetTagNames) => {
Apr 7, 2019
Apr 7, 2019
938
return targetTagNames.some((targetTagName) => {
939
return hasTag(jsdoc, targetTagName);
940
});
941
};
942
May 16, 2019
May 16, 2019
943
/**
Jan 20, 2023
Jan 20, 2023
944
* Checks if the JSDoc comment has an undefined type.
May 13, 2023
May 13, 2023
945
* @param {import('comment-parser').Spec|null|undefined} tag
May 16, 2019
May 16, 2019
946
* the tag which should be checked.
May 11, 2023
May 11, 2023
947
* @param {ParserMode} mode
May 16, 2019
May 16, 2019
948
* @returns {boolean}
Jan 20, 2023
Jan 20, 2023
949
* true in case a defined type is undeclared; otherwise false.
May 16, 2019
May 16, 2019
950
*/
Jan 20, 2023
Jan 20, 2023
951
const mayBeUndefinedTypeTag = (tag, mode) => {
Jan 21, 2021
Jan 21, 2021
952
// The function should not continue in the event the type is not defined...
May 16, 2019
May 16, 2019
953
if (typeof tag === 'undefined' || tag === null) {
Jan 20, 2023
Jan 20, 2023
954
return true;
May 16, 2019
May 16, 2019
955
}
956
Jan 21, 2021
Jan 21, 2021
957
// .. same applies if it declares an `{undefined}` or `{void}` type
May 16, 2019
May 16, 2019
958
const tagType = tag.type.trim();
Oct 24, 2022
Oct 24, 2022
959
960
// Exit early if matching
Jan 20, 2023
Jan 20, 2023
961
if (
962
tagType === 'undefined' || tagType === 'void' ||
963
tagType === '*' || tagType === 'any'
964
) {
965
return true;
May 16, 2019
May 16, 2019
966
}
967
Oct 24, 2022
Oct 24, 2022
968
let parsedTypes;
969
try {
970
parsedTypes = tryParse(
971
tagType,
972
mode === 'permissive' ? undefined : [
973
mode,
974
],
975
);
976
} catch {
977
// Ignore
978
}
979
980
if (
981
// We do not traverse deeply as it could be, e.g., `Promise<void>`
982
parsedTypes &&
983
parsedTypes.type === 'JsdocTypeUnion' &&
Oct 1, 2025
Oct 1, 2025
984
parsedTypes.elements.some((elem) => {
Oct 24, 2022
Oct 24, 2022
985
return elem.type === 'JsdocTypeUndefined' ||
986
elem.type === 'JsdocTypeName' && elem.value === 'void';
987
})) {
Jan 20, 2023
Jan 20, 2023
988
return true;
Oct 24, 2022
Oct 24, 2022
989
}
990
Jan 21, 2021
Jan 21, 2021
991
// In any other case, a type is present
Jan 20, 2023
Jan 20, 2023
992
return false;
May 16, 2019
May 16, 2019
993
};
994
Feb 13, 2022
Feb 13, 2022
995
/**
May 11, 2023
May 11, 2023
996
* @param {import('./getDefaultTagStructureForMode.js').TagStructure} map
997
* @param {string} tag
May 15, 2023
May 15, 2023
998
* @returns {Map<string, string|string[]|boolean|undefined>}
Feb 13, 2022
Feb 13, 2022
999
*/
Jul 19, 2020
Jul 19, 2020
1000
const ensureMap = (map, tag) => {