eslint-plugin-jsdoc 48.0.0 → 48.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/package.json +2 -2
  2. package/src/WarnSettings.js +34 -0
  3. package/src/alignTransform.js +356 -0
  4. package/src/defaultTagOrder.js +168 -0
  5. package/src/exportParser.js +957 -0
  6. package/src/getDefaultTagStructureForMode.js +969 -0
  7. package/src/index.js +266 -0
  8. package/src/iterateJsdoc.js +2555 -0
  9. package/src/jsdocUtils.js +1693 -0
  10. package/src/rules/checkAccess.js +45 -0
  11. package/src/rules/checkAlignment.js +63 -0
  12. package/src/rules/checkExamples.js +594 -0
  13. package/src/rules/checkIndentation.js +75 -0
  14. package/src/rules/checkLineAlignment.js +364 -0
  15. package/src/rules/checkParamNames.js +404 -0
  16. package/src/rules/checkPropertyNames.js +152 -0
  17. package/src/rules/checkSyntax.js +30 -0
  18. package/src/rules/checkTagNames.js +314 -0
  19. package/src/rules/checkTypes.js +535 -0
  20. package/src/rules/checkValues.js +220 -0
  21. package/src/rules/emptyTags.js +88 -0
  22. package/src/rules/implementsOnClasses.js +64 -0
  23. package/src/rules/importsAsDependencies.js +131 -0
  24. package/src/rules/informativeDocs.js +182 -0
  25. package/src/rules/matchDescription.js +286 -0
  26. package/src/rules/matchName.js +147 -0
  27. package/src/rules/multilineBlocks.js +333 -0
  28. package/src/rules/noBadBlocks.js +109 -0
  29. package/src/rules/noBlankBlockDescriptions.js +69 -0
  30. package/src/rules/noBlankBlocks.js +53 -0
  31. package/src/rules/noDefaults.js +85 -0
  32. package/src/rules/noMissingSyntax.js +195 -0
  33. package/src/rules/noMultiAsterisks.js +134 -0
  34. package/src/rules/noRestrictedSyntax.js +91 -0
  35. package/src/rules/noTypes.js +73 -0
  36. package/src/rules/noUndefinedTypes.js +328 -0
  37. package/src/rules/requireAsteriskPrefix.js +189 -0
  38. package/src/rules/requireDescription.js +161 -0
  39. package/src/rules/requireDescriptionCompleteSentence.js +333 -0
  40. package/src/rules/requireExample.js +118 -0
  41. package/src/rules/requireFileOverview.js +154 -0
  42. package/src/rules/requireHyphenBeforeParamDescription.js +178 -0
  43. package/src/rules/requireJsdoc.js +629 -0
  44. package/src/rules/requireParam.js +592 -0
  45. package/src/rules/requireParamDescription.js +89 -0
  46. package/src/rules/requireParamName.js +55 -0
  47. package/src/rules/requireParamType.js +89 -0
  48. package/src/rules/requireProperty.js +48 -0
  49. package/src/rules/requirePropertyDescription.js +25 -0
  50. package/src/rules/requirePropertyName.js +25 -0
  51. package/src/rules/requirePropertyType.js +25 -0
  52. package/src/rules/requireReturns.js +238 -0
  53. package/src/rules/requireReturnsCheck.js +141 -0
  54. package/src/rules/requireReturnsDescription.js +59 -0
  55. package/src/rules/requireReturnsType.js +51 -0
  56. package/src/rules/requireThrows.js +111 -0
  57. package/src/rules/requireYields.js +216 -0
  58. package/src/rules/requireYieldsCheck.js +208 -0
  59. package/src/rules/sortTags.js +557 -0
  60. package/src/rules/tagLines.js +359 -0
  61. package/src/rules/textEscaping.js +146 -0
  62. package/src/rules/validTypes.js +368 -0
  63. package/src/tagNames.js +234 -0
  64. package/src/utils/hasReturnValue.js +549 -0
@@ -0,0 +1,1693 @@
1
+ import getDefaultTagStructureForMode from './getDefaultTagStructureForMode.js';
2
+ import {
3
+ closureTags,
4
+ jsdocTags,
5
+ typeScriptTags,
6
+ } from './tagNames.js';
7
+ import {
8
+ hasReturnValue,
9
+ hasValueOrExecutorHasNonEmptyResolveValue,
10
+ } from './utils/hasReturnValue.js';
11
+ import WarnSettings from './WarnSettings.js';
12
+ import {
13
+ tryParse,
14
+ } from '@es-joy/jsdoccomment';
15
+
16
+ /**
17
+ * @typedef {number} Integer
18
+ */
19
+ /**
20
+ * @typedef {import('./utils/hasReturnValue.js').ESTreeOrTypeScriptNode} ESTreeOrTypeScriptNode
21
+ */
22
+
23
+ /**
24
+ * @typedef {"jsdoc"|"typescript"|"closure"|"permissive"} ParserMode
25
+ */
26
+
27
+ /**
28
+ * @type {import('./getDefaultTagStructureForMode.js').TagStructure}
29
+ */
30
+ let tagStructure;
31
+
32
+ /**
33
+ * @param {ParserMode} mode
34
+ * @returns {void}
35
+ */
36
+ const setTagStructure = (mode) => {
37
+ tagStructure = getDefaultTagStructureForMode(mode);
38
+ };
39
+
40
+ /**
41
+ * @typedef {undefined|string|{
42
+ * name: Integer,
43
+ * restElement: boolean
44
+ * }|{
45
+ * isRestProperty: boolean|undefined,
46
+ * name: string,
47
+ * restElement: boolean
48
+ * }|{
49
+ * name: string,
50
+ * restElement: boolean
51
+ * }} ParamCommon
52
+ */
53
+ /**
54
+ * @typedef {ParamCommon|[string|undefined, (FlattendRootInfo & {
55
+ * annotationParamName?: string,
56
+ * })]|NestedParamInfo} ParamNameInfo
57
+ */
58
+
59
+ /**
60
+ * @typedef {{
61
+ * hasPropertyRest: boolean,
62
+ * hasRestElement: boolean,
63
+ * names: string[],
64
+ * rests: boolean[],
65
+ * }} FlattendRootInfo
66
+ */
67
+ /**
68
+ * @typedef {[string, (string[]|ParamInfo[])]} NestedParamInfo
69
+ */
70
+ /**
71
+ * @typedef {ParamCommon|
72
+ * [string|undefined, (FlattendRootInfo & {
73
+ * annotationParamName?: string
74
+ * })]|
75
+ * NestedParamInfo} ParamInfo
76
+ */
77
+
78
+ /**
79
+ * Given a nested array of property names, reduce them to a single array,
80
+ * appending the name of the root element along the way if present.
81
+ * @callback FlattenRoots
82
+ * @param {ParamInfo[]} params
83
+ * @param {string} [root]
84
+ * @returns {FlattendRootInfo}
85
+ */
86
+
87
+ /** @type {FlattenRoots} */
88
+ const flattenRoots = (params, root = '') => {
89
+ let hasRestElement = false;
90
+ let hasPropertyRest = false;
91
+
92
+ /**
93
+ * @type {boolean[]}
94
+ */
95
+ const rests = [];
96
+
97
+ const names = params.reduce(
98
+ /**
99
+ * @param {string[]} acc
100
+ * @param {ParamInfo} cur
101
+ * @returns {string[]}
102
+ */
103
+ (acc, cur) => {
104
+ if (Array.isArray(cur)) {
105
+ let nms;
106
+ if (Array.isArray(cur[1])) {
107
+ nms = cur[1];
108
+ } else {
109
+ if (cur[1].hasRestElement) {
110
+ hasRestElement = true;
111
+ }
112
+
113
+ if (cur[1].hasPropertyRest) {
114
+ hasPropertyRest = true;
115
+ }
116
+
117
+ nms = cur[1].names;
118
+ }
119
+
120
+ const flattened = flattenRoots(nms, root ? `${root}.${cur[0]}` : cur[0]);
121
+ if (flattened.hasRestElement) {
122
+ hasRestElement = true;
123
+ }
124
+
125
+ if (flattened.hasPropertyRest) {
126
+ hasPropertyRest = true;
127
+ }
128
+
129
+ const inner = /** @type {string[]} */ ([
130
+ root ? `${root}.${cur[0]}` : cur[0],
131
+ ...flattened.names,
132
+ ].filter(Boolean));
133
+ rests.push(false, ...flattened.rests);
134
+
135
+ return acc.concat(inner);
136
+ }
137
+
138
+ if (typeof cur === 'object') {
139
+ if ('isRestProperty' in cur && cur.isRestProperty) {
140
+ hasPropertyRest = true;
141
+ rests.push(true);
142
+ } else {
143
+ rests.push(false);
144
+ }
145
+
146
+ if ('restElement' in cur && cur.restElement) {
147
+ hasRestElement = true;
148
+ }
149
+
150
+ acc.push(root ? `${root}.${String(cur.name)}` : String(cur.name));
151
+ } else if (typeof cur !== 'undefined') {
152
+ rests.push(false);
153
+ acc.push(root ? `${root}.${cur}` : cur);
154
+ }
155
+
156
+ return acc;
157
+ }, [],
158
+ );
159
+
160
+ return {
161
+ hasPropertyRest,
162
+ hasRestElement,
163
+ names,
164
+ rests,
165
+ };
166
+ };
167
+
168
+ /**
169
+ * @param {import('@typescript-eslint/types').TSESTree.TSIndexSignature|
170
+ * import('@typescript-eslint/types').TSESTree.TSConstructSignatureDeclaration|
171
+ * import('@typescript-eslint/types').TSESTree.TSCallSignatureDeclaration|
172
+ * import('@typescript-eslint/types').TSESTree.TSPropertySignature} propSignature
173
+ * @returns {undefined|string|[string, string[]]}
174
+ */
175
+ const getPropertiesFromPropertySignature = (propSignature) => {
176
+ if (
177
+ propSignature.type === 'TSIndexSignature' ||
178
+ propSignature.type === 'TSConstructSignatureDeclaration' ||
179
+ propSignature.type === 'TSCallSignatureDeclaration'
180
+ ) {
181
+ return undefined;
182
+ }
183
+
184
+ if (propSignature.typeAnnotation && propSignature.typeAnnotation.typeAnnotation.type === 'TSTypeLiteral') {
185
+ return [
186
+ /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
187
+ propSignature.key
188
+ ).name,
189
+ propSignature.typeAnnotation.typeAnnotation.members.map((member) => {
190
+ return /** @type {string} */ (
191
+ getPropertiesFromPropertySignature(
192
+ /** @type {import('@typescript-eslint/types').TSESTree.TSPropertySignature} */ (
193
+ member
194
+ ),
195
+ )
196
+ );
197
+ }),
198
+ ];
199
+ }
200
+
201
+ return /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
202
+ propSignature.key
203
+ ).name;
204
+ };
205
+
206
+ /**
207
+ * @param {ESTreeOrTypeScriptNode|null} functionNode
208
+ * @param {boolean} [checkDefaultObjects]
209
+ * @throws {Error}
210
+ * @returns {ParamNameInfo[]}
211
+ */
212
+ const getFunctionParameterNames = (
213
+ functionNode, checkDefaultObjects,
214
+ ) => {
215
+ /* eslint-disable complexity -- Temporary */
216
+ /**
217
+ * @param {import('estree').Identifier|import('estree').AssignmentPattern|
218
+ * import('estree').ObjectPattern|import('estree').Property|
219
+ * import('estree').RestElement|import('estree').ArrayPattern|
220
+ * import('@typescript-eslint/types').TSESTree.TSParameterProperty|
221
+ * import('@typescript-eslint/types').TSESTree.Property|
222
+ * import('@typescript-eslint/types').TSESTree.RestElement|
223
+ * import('@typescript-eslint/types').TSESTree.Identifier|
224
+ * import('@typescript-eslint/types').TSESTree.ObjectPattern|
225
+ * import('@typescript-eslint/types').TSESTree.BindingName|
226
+ * import('@typescript-eslint/types').TSESTree.Parameter
227
+ * } param
228
+ * @param {boolean} [isProperty]
229
+ * @returns {ParamNameInfo|[string, ParamNameInfo[]]}
230
+ */
231
+ const getParamName = (param, isProperty) => {
232
+ /* eslint-enable complexity -- Temporary */
233
+ const hasLeftTypeAnnotation = 'left' in param && 'typeAnnotation' in param.left;
234
+
235
+ if ('typeAnnotation' in param || hasLeftTypeAnnotation) {
236
+ const typeAnnotation = hasLeftTypeAnnotation ?
237
+ /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
238
+ param.left
239
+ ).typeAnnotation :
240
+ /** @type {import('@typescript-eslint/types').TSESTree.Identifier|import('@typescript-eslint/types').TSESTree.ObjectPattern} */
241
+ (param).typeAnnotation;
242
+
243
+ if (typeAnnotation?.typeAnnotation?.type === 'TSTypeLiteral') {
244
+ const propertyNames = typeAnnotation.typeAnnotation.members.map((member) => {
245
+ return getPropertiesFromPropertySignature(
246
+ /** @type {import('@typescript-eslint/types').TSESTree.TSPropertySignature} */
247
+ (member),
248
+ );
249
+ });
250
+
251
+ const flattened = {
252
+ ...flattenRoots(propertyNames),
253
+ annotationParamName: 'name' in param ? param.name : undefined,
254
+ };
255
+ const hasLeftName = 'left' in param && 'name' in param.left;
256
+
257
+ if ('name' in param || hasLeftName) {
258
+ return [
259
+ hasLeftName ?
260
+ /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
261
+ param.left
262
+ ).name :
263
+ /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
264
+ param
265
+ ).name,
266
+ flattened,
267
+ ];
268
+ }
269
+
270
+ return [
271
+ undefined, flattened,
272
+ ];
273
+ }
274
+ }
275
+
276
+ if ('name' in param) {
277
+ return param.name;
278
+ }
279
+
280
+ if ('left' in param && 'name' in param.left) {
281
+ return param.left.name;
282
+ }
283
+
284
+ if (
285
+ param.type === 'ObjectPattern' ||
286
+ ('left' in param &&
287
+ (
288
+ param
289
+ ).left.type === 'ObjectPattern')
290
+ ) {
291
+ const properties = /** @type {import('@typescript-eslint/types').TSESTree.ObjectPattern} */ (
292
+ param
293
+ ).properties ||
294
+ /** @type {import('estree').ObjectPattern} */
295
+ (
296
+ /** @type {import('@typescript-eslint/types').TSESTree.AssignmentPattern} */ (
297
+ param
298
+ ).left
299
+ )?.properties;
300
+ const roots = properties.map((prop) => {
301
+ return getParamName(prop, true);
302
+ });
303
+
304
+ return [
305
+ undefined, flattenRoots(roots),
306
+ ];
307
+ }
308
+
309
+ if (param.type === 'Property') {
310
+ switch (param.value.type) {
311
+ case 'ArrayPattern': {
312
+ return [
313
+ /** @type {import('estree').Identifier} */
314
+ (param.key).name,
315
+ /** @type {import('estree').ArrayPattern} */ (
316
+ param.value
317
+ ).elements.map((prop, idx) => {
318
+ return {
319
+ name: idx,
320
+ restElement: prop?.type === 'RestElement',
321
+ };
322
+ }),
323
+ ];
324
+ }
325
+
326
+ case 'ObjectPattern': {
327
+ return [
328
+ /** @type {import('estree').Identifier} */ (param.key).name,
329
+ /** @type {import('estree').ObjectPattern} */ (
330
+ param.value
331
+ ).properties.map((prop) => {
332
+ return /** @type {string|[string, string[]]} */ (getParamName(prop, isProperty));
333
+ }),
334
+ ];
335
+ }
336
+
337
+ case 'AssignmentPattern': {
338
+ switch (param.value.left.type) {
339
+ case 'Identifier':
340
+ // Default parameter
341
+ if (checkDefaultObjects && param.value.right.type === 'ObjectExpression') {
342
+ return [
343
+ /** @type {import('estree').Identifier} */ (
344
+ param.key
345
+ ).name,
346
+ /** @type {import('estree').AssignmentPattern} */ (
347
+ param.value
348
+ ).right.properties.map((prop) => {
349
+ return /** @type {string} */ (getParamName(
350
+ /** @type {import('estree').Property} */
351
+ (prop),
352
+ isProperty,
353
+ ));
354
+ }),
355
+ ];
356
+ }
357
+
358
+ break;
359
+ case 'ObjectPattern':
360
+ return [
361
+ /** @type {import('estree').Identifier} */
362
+ (param.key).name,
363
+ /** @type {import('estree').ObjectPattern} */ (
364
+ param.value.left
365
+ ).properties.map((prop) => {
366
+ return getParamName(prop, isProperty);
367
+ }),
368
+ ];
369
+ case 'ArrayPattern':
370
+ return [
371
+ /** @type {import('estree').Identifier} */
372
+ (param.key).name,
373
+ /** @type {import('estree').ArrayPattern} */ (
374
+ param.value.left
375
+ ).elements.map((prop, idx) => {
376
+ return {
377
+ name: idx,
378
+ restElement: prop?.type === 'RestElement',
379
+ };
380
+ }),
381
+ ];
382
+ }
383
+ }
384
+ }
385
+
386
+ switch (param.key.type) {
387
+ case 'Identifier':
388
+ return param.key.name;
389
+
390
+ // The key of an object could also be a string or number
391
+ case 'Literal':
392
+ /* c8 ignore next 2 -- `raw` may not be present in all parsers */
393
+ return /** @type {string} */ (param.key.raw ||
394
+ param.key.value);
395
+
396
+ // case 'MemberExpression':
397
+ default:
398
+ // Todo: We should really create a structure (and a corresponding
399
+ // option analogous to `checkRestProperty`) which allows for
400
+ // (and optionally requires) dynamic properties to have a single
401
+ // line of documentation
402
+ return undefined;
403
+ }
404
+ }
405
+
406
+ if (
407
+ param.type === 'ArrayPattern' ||
408
+ /** @type {import('estree').AssignmentPattern} */ (
409
+ param
410
+ ).left?.type === 'ArrayPattern'
411
+ ) {
412
+ const elements = /** @type {import('estree').ArrayPattern} */ (
413
+ param
414
+ ).elements || /** @type {import('estree').ArrayPattern} */ (
415
+ /** @type {import('estree').AssignmentPattern} */ (
416
+ param
417
+ ).left
418
+ )?.elements;
419
+ const roots = elements.map((prop, idx) => {
420
+ return {
421
+ name: `"${idx}"`,
422
+ restElement: prop?.type === 'RestElement',
423
+ };
424
+ });
425
+
426
+ return [
427
+ undefined, flattenRoots(roots),
428
+ ];
429
+ }
430
+
431
+ if ([
432
+ 'RestElement', 'ExperimentalRestProperty',
433
+ ].includes(param.type)) {
434
+ return {
435
+ isRestProperty: isProperty,
436
+ name: /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
437
+ /** @type {import('@typescript-eslint/types').TSESTree.RestElement} */ (
438
+ param
439
+ ).argument).name,
440
+ restElement: true,
441
+ };
442
+ }
443
+
444
+ if (param.type === 'TSParameterProperty') {
445
+ return getParamName(
446
+ /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
447
+ /** @type {import('@typescript-eslint/types').TSESTree.TSParameterProperty} */ (
448
+ param
449
+ ).parameter
450
+ ),
451
+ true,
452
+ );
453
+ }
454
+
455
+ throw new Error(`Unsupported function signature format: \`${param.type}\`.`);
456
+ };
457
+
458
+ if (!functionNode) {
459
+ return [];
460
+ }
461
+
462
+ return (/** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */ (
463
+ functionNode
464
+ ).params || /** @type {import('@typescript-eslint/types').TSESTree.MethodDefinition} */ (
465
+ functionNode
466
+ ).value?.params || []).map((param) => {
467
+ return getParamName(param);
468
+ });
469
+ };
470
+
471
+ /**
472
+ * @param {ESTreeOrTypeScriptNode} functionNode
473
+ * @returns {Integer}
474
+ */
475
+ const hasParams = (functionNode) => {
476
+ // Should also check `functionNode.value.params` if supporting `MethodDefinition`
477
+ return /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */ (
478
+ functionNode
479
+ ).params.length;
480
+ };
481
+
482
+ /**
483
+ * Gets all names of the target type, including those that refer to a path, e.g.
484
+ * `foo` or `foo.bar`.
485
+ * @param {import('comment-parser').Block} jsdoc
486
+ * @param {string} targetTagName
487
+ * @returns {{
488
+ * idx: Integer,
489
+ * name: string,
490
+ * type: string
491
+ * }[]}
492
+ */
493
+ const getJsdocTagsDeep = (jsdoc, targetTagName) => {
494
+ const ret = [];
495
+ for (const [
496
+ idx,
497
+ {
498
+ name,
499
+ tag,
500
+ type,
501
+ },
502
+ ] of jsdoc.tags.entries()) {
503
+ if (tag !== targetTagName) {
504
+ continue;
505
+ }
506
+
507
+ ret.push({
508
+ idx,
509
+ name,
510
+ type,
511
+ });
512
+ }
513
+
514
+ return ret;
515
+ };
516
+
517
+ const modeWarnSettings = WarnSettings();
518
+
519
+ /**
520
+ * @param {ParserMode|undefined} mode
521
+ * @param {import('eslint').Rule.RuleContext} context
522
+ * @returns {import('./tagNames.js').AliasedTags}
523
+ */
524
+ const getTagNamesForMode = (mode, context) => {
525
+ switch (mode) {
526
+ case 'jsdoc':
527
+ return jsdocTags;
528
+ case 'typescript':
529
+ return typeScriptTags;
530
+ case 'closure': case 'permissive':
531
+ return closureTags;
532
+ default:
533
+ if (!modeWarnSettings.hasBeenWarned(context, 'mode')) {
534
+ context.report({
535
+ loc: {
536
+ end: {
537
+ column: 1,
538
+ line: 1,
539
+ },
540
+ start: {
541
+ column: 1,
542
+ line: 1,
543
+ },
544
+ },
545
+ message: `Unrecognized value \`${mode}\` for \`settings.jsdoc.mode\`.`,
546
+ });
547
+ modeWarnSettings.markSettingAsWarned(context, 'mode');
548
+ }
549
+
550
+ // We'll avoid breaking too many other rules
551
+ return jsdocTags;
552
+ }
553
+ };
554
+
555
+ /**
556
+ * @param {import('eslint').Rule.RuleContext} context
557
+ * @param {ParserMode|undefined} mode
558
+ * @param {string} name
559
+ * @param {TagNamePreference} tagPreference
560
+ * @returns {string|false|{
561
+ * message: string;
562
+ * replacement?: string|undefined;
563
+ * }}
564
+ */
565
+ const getPreferredTagName = (
566
+ context,
567
+ mode,
568
+ name,
569
+ tagPreference = {},
570
+ ) => {
571
+ const prefValues = Object.values(tagPreference);
572
+ if (prefValues.includes(name) || prefValues.some((prefVal) => {
573
+ return prefVal && typeof prefVal === 'object' && prefVal.replacement === name;
574
+ })) {
575
+ return name;
576
+ }
577
+
578
+ // Allow keys to have a 'tag ' prefix to avoid upstream bug in ESLint
579
+ // that disallows keys that conflict with Object.prototype,
580
+ // e.g. 'tag constructor' for 'constructor':
581
+ // https://github.com/eslint/eslint/issues/13289
582
+ // https://github.com/gajus/eslint-plugin-jsdoc/issues/537
583
+ const tagPreferenceFixed = Object.fromEntries(
584
+ Object
585
+ .entries(tagPreference)
586
+ .map(([
587
+ key,
588
+ value,
589
+ ]) => {
590
+ return [
591
+ key.replace(/^tag /u, ''), value,
592
+ ];
593
+ }),
594
+ );
595
+
596
+ if (Object.prototype.hasOwnProperty.call(tagPreferenceFixed, name)) {
597
+ return tagPreferenceFixed[name];
598
+ }
599
+
600
+ const tagNames = getTagNamesForMode(mode, context);
601
+
602
+ const preferredTagName = Object.entries(tagNames).find(([
603
+ , aliases,
604
+ ]) => {
605
+ return aliases.includes(name);
606
+ })?.[0];
607
+ if (preferredTagName) {
608
+ return preferredTagName;
609
+ }
610
+
611
+ return name;
612
+ };
613
+
614
+ /**
615
+ * @param {import('eslint').Rule.RuleContext} context
616
+ * @param {ParserMode|undefined} mode
617
+ * @param {string} name
618
+ * @param {string[]} definedTags
619
+ * @returns {boolean}
620
+ */
621
+ const isValidTag = (
622
+ context,
623
+ mode,
624
+ name,
625
+ definedTags,
626
+ ) => {
627
+ const tagNames = getTagNamesForMode(mode, context);
628
+
629
+ const validTagNames = Object.keys(tagNames).concat(Object.values(tagNames).flat());
630
+ const additionalTags = definedTags;
631
+ const allTags = validTagNames.concat(additionalTags);
632
+
633
+ return allTags.includes(name);
634
+ };
635
+
636
+ /**
637
+ * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
638
+ * @param {string} targetTagName
639
+ * @returns {boolean}
640
+ */
641
+ const hasTag = (jsdoc, targetTagName) => {
642
+ const targetTagLower = targetTagName.toLowerCase();
643
+
644
+ return jsdoc.tags.some((doc) => {
645
+ return doc.tag.toLowerCase() === targetTagLower;
646
+ });
647
+ };
648
+
649
+ /**
650
+ * Get all tags, inline tags and inline tags in tags
651
+ * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
652
+ * @returns {(import('comment-parser').Spec|
653
+ * import('@es-joy/jsdoccomment').JsdocInlineTagNoType)[]}
654
+ */
655
+ const getAllTags = (jsdoc) => {
656
+ return [
657
+ ...jsdoc.tags,
658
+ ...jsdoc.inlineTags.map((inlineTag) => {
659
+ // Tags don't have source or line numbers, so add before returning
660
+ let line = -1;
661
+ for (const {
662
+ tokens: {
663
+ description,
664
+ },
665
+ } of jsdoc.source) {
666
+ line++;
667
+ if (description && description.includes(`{@${inlineTag.tag}`)) {
668
+ break;
669
+ }
670
+ }
671
+
672
+ inlineTag.line = line;
673
+
674
+ return inlineTag;
675
+ }),
676
+ ...jsdoc.tags.flatMap((tag) => {
677
+ let tagBegins = -1;
678
+ for (const {
679
+ tokens: {
680
+ tag: tg,
681
+ },
682
+ } of jsdoc.source) {
683
+ tagBegins++;
684
+ if (tg) {
685
+ break;
686
+ }
687
+ }
688
+
689
+ for (const inlineTag of tag.inlineTags) {
690
+ /** @type {import('./iterateJsdoc.js').Integer} */
691
+ let line = 0;
692
+ for (const {
693
+ number,
694
+ tokens: {
695
+ description,
696
+ },
697
+ } of tag.source) {
698
+ if (description && description.includes(`{@${inlineTag.tag}`)) {
699
+ line = number;
700
+ break;
701
+ }
702
+ }
703
+
704
+ inlineTag.line = tagBegins + line - 1;
705
+ }
706
+
707
+ return (
708
+ /**
709
+ * @type {import('comment-parser').Spec & {
710
+ * inlineTags: import('@es-joy/jsdoccomment').JsdocInlineTagNoType[]
711
+ * }}
712
+ */ (
713
+ tag
714
+ ).inlineTags
715
+ );
716
+ }),
717
+ ];
718
+ };
719
+
720
+ /**
721
+ * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
722
+ * @param {string[]} targetTagNames
723
+ * @returns {boolean}
724
+ */
725
+ const hasATag = (jsdoc, targetTagNames) => {
726
+ return targetTagNames.some((targetTagName) => {
727
+ return hasTag(jsdoc, targetTagName);
728
+ });
729
+ };
730
+
731
+ /**
732
+ * Checks if the JSDoc comment has an undefined type.
733
+ * @param {import('comment-parser').Spec|null|undefined} tag
734
+ * the tag which should be checked.
735
+ * @param {ParserMode} mode
736
+ * @returns {boolean}
737
+ * true in case a defined type is undeclared; otherwise false.
738
+ */
739
+ const mayBeUndefinedTypeTag = (tag, mode) => {
740
+ // The function should not continue in the event the type is not defined...
741
+ if (typeof tag === 'undefined' || tag === null) {
742
+ return true;
743
+ }
744
+
745
+ // .. same applies if it declares an `{undefined}` or `{void}` type
746
+ const tagType = tag.type.trim();
747
+
748
+ // Exit early if matching
749
+ if (
750
+ tagType === 'undefined' || tagType === 'void' ||
751
+ tagType === '*' || tagType === 'any'
752
+ ) {
753
+ return true;
754
+ }
755
+
756
+ let parsedTypes;
757
+ try {
758
+ parsedTypes = tryParse(
759
+ tagType,
760
+ mode === 'permissive' ? undefined : [
761
+ mode,
762
+ ],
763
+ );
764
+ } catch {
765
+ // Ignore
766
+ }
767
+
768
+ if (
769
+ // We do not traverse deeply as it could be, e.g., `Promise<void>`
770
+ parsedTypes &&
771
+ parsedTypes.type === 'JsdocTypeUnion' &&
772
+ parsedTypes.elements.find((elem) => {
773
+ return elem.type === 'JsdocTypeUndefined' ||
774
+ elem.type === 'JsdocTypeName' && elem.value === 'void';
775
+ })) {
776
+ return true;
777
+ }
778
+
779
+ // In any other case, a type is present
780
+ return false;
781
+ };
782
+
783
+ /**
784
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} map
785
+ * @param {string} tag
786
+ * @returns {Map<string, string|string[]|boolean|undefined>}
787
+ */
788
+ const ensureMap = (map, tag) => {
789
+ if (!map.has(tag)) {
790
+ map.set(tag, new Map());
791
+ }
792
+
793
+ return /** @type {Map<string, string | boolean>} */ (map.get(tag));
794
+ };
795
+
796
+ /**
797
+ * @param {import('./iterateJsdoc.js').StructuredTags} structuredTags
798
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
799
+ * @returns {void}
800
+ */
801
+ const overrideTagStructure = (structuredTags, tagMap = tagStructure) => {
802
+ for (const [
803
+ tag,
804
+ {
805
+ name,
806
+ type,
807
+ required = [],
808
+ },
809
+ ] of Object.entries(structuredTags)) {
810
+ const tagStruct = ensureMap(tagMap, tag);
811
+
812
+ tagStruct.set('namepathRole', name);
813
+ tagStruct.set('typeAllowed', type);
814
+
815
+ const requiredName = required.includes('name');
816
+ if (requiredName && name === false) {
817
+ throw new Error('Cannot add "name" to `require` with the tag\'s `name` set to `false`');
818
+ }
819
+
820
+ tagStruct.set('nameRequired', requiredName);
821
+
822
+ const requiredType = required.includes('type');
823
+ if (requiredType && type === false) {
824
+ throw new Error('Cannot add "type" to `require` with the tag\'s `type` set to `false`');
825
+ }
826
+
827
+ tagStruct.set('typeRequired', requiredType);
828
+
829
+ const typeOrNameRequired = required.includes('typeOrNameRequired');
830
+ if (typeOrNameRequired && name === false) {
831
+ throw new Error('Cannot add "typeOrNameRequired" to `require` with the tag\'s `name` set to `false`');
832
+ }
833
+
834
+ if (typeOrNameRequired && type === false) {
835
+ throw new Error('Cannot add "typeOrNameRequired" to `require` with the tag\'s `type` set to `false`');
836
+ }
837
+
838
+ tagStruct.set('typeOrNameRequired', typeOrNameRequired);
839
+ }
840
+ };
841
+
842
+ /**
843
+ * @param {ParserMode} mode
844
+ * @param {import('./iterateJsdoc.js').StructuredTags} structuredTags
845
+ * @returns {import('./getDefaultTagStructureForMode.js').TagStructure}
846
+ */
847
+ const getTagStructureForMode = (mode, structuredTags) => {
848
+ const tagStruct = getDefaultTagStructureForMode(mode);
849
+
850
+ try {
851
+ overrideTagStructure(structuredTags, tagStruct);
852
+ /* c8 ignore next 3 */
853
+ } catch {
854
+ //
855
+ }
856
+
857
+ return tagStruct;
858
+ };
859
+
860
+ /**
861
+ * @param {string} tag
862
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
863
+ * @returns {boolean}
864
+ */
865
+ const isNamepathDefiningTag = (tag, tagMap = tagStructure) => {
866
+ const tagStruct = ensureMap(tagMap, tag);
867
+
868
+ return tagStruct.get('namepathRole') === 'namepath-defining';
869
+ };
870
+
871
+ /**
872
+ * @param {string} tag
873
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
874
+ * @returns {boolean}
875
+ */
876
+ const isNamepathReferencingTag = (tag, tagMap = tagStructure) => {
877
+ const tagStruct = ensureMap(tagMap, tag);
878
+ return tagStruct.get('namepathRole') === 'namepath-referencing';
879
+ };
880
+
881
+ /**
882
+ * @param {string} tag
883
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
884
+ * @returns {boolean}
885
+ */
886
+ const isNamepathOrUrlReferencingTag = (tag, tagMap = tagStructure) => {
887
+ const tagStruct = ensureMap(tagMap, tag);
888
+ return tagStruct.get('namepathRole') === 'namepath-or-url-referencing';
889
+ };
890
+
891
+ /**
892
+ * @param {string} tag
893
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
894
+ * @returns {boolean|undefined}
895
+ */
896
+ const tagMustHaveTypePosition = (tag, tagMap = tagStructure) => {
897
+ const tagStruct = ensureMap(tagMap, tag);
898
+
899
+ return /** @type {boolean|undefined} */ (tagStruct.get('typeRequired'));
900
+ };
901
+
902
+ /**
903
+ * @param {string} tag
904
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
905
+ * @returns {boolean|string}
906
+ */
907
+ const tagMightHaveTypePosition = (tag, tagMap = tagStructure) => {
908
+ if (tagMustHaveTypePosition(tag, tagMap)) {
909
+ return true;
910
+ }
911
+
912
+ const tagStruct = ensureMap(tagMap, tag);
913
+
914
+ const ret = /** @type {boolean|undefined} */ (tagStruct.get('typeAllowed'));
915
+
916
+ return ret === undefined ? true : ret;
917
+ };
918
+
919
+ const namepathTypes = new Set([
920
+ 'namepath-defining', 'namepath-referencing',
921
+ ]);
922
+
923
+ /**
924
+ * @param {string} tag
925
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
926
+ * @returns {boolean}
927
+ */
928
+ const tagMightHaveNamePosition = (tag, tagMap = tagStructure) => {
929
+ const tagStruct = ensureMap(tagMap, tag);
930
+
931
+ const ret = tagStruct.get('namepathRole');
932
+
933
+ return ret === undefined ? true : Boolean(ret);
934
+ };
935
+
936
+ /**
937
+ * @param {string} tag
938
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
939
+ * @returns {boolean}
940
+ */
941
+ const tagMightHaveNamepath = (tag, tagMap = tagStructure) => {
942
+ const tagStruct = ensureMap(tagMap, tag);
943
+
944
+ const nampathRole = tagStruct.get('namepathRole');
945
+
946
+ return nampathRole !== false &&
947
+ namepathTypes.has(/** @type {string} */ (nampathRole));
948
+ };
949
+
950
+ /**
951
+ * @param {string} tag
952
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
953
+ * @returns {boolean|undefined}
954
+ */
955
+ const tagMustHaveNamePosition = (tag, tagMap = tagStructure) => {
956
+ const tagStruct = ensureMap(tagMap, tag);
957
+
958
+ return /** @type {boolean|undefined} */ (tagStruct.get('nameRequired'));
959
+ };
960
+
961
+ /**
962
+ * @param {string} tag
963
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
964
+ * @returns {boolean}
965
+ */
966
+ const tagMightHaveEitherTypeOrNamePosition = (tag, tagMap) => {
967
+ return Boolean(tagMightHaveTypePosition(tag, tagMap)) || tagMightHaveNamepath(tag, tagMap);
968
+ };
969
+
970
+ /**
971
+ * @param {string} tag
972
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
973
+ * @returns {boolean|undefined}
974
+ */
975
+ const tagMustHaveEitherTypeOrNamePosition = (tag, tagMap) => {
976
+ const tagStruct = ensureMap(tagMap, tag);
977
+
978
+ return /** @type {boolean} */ (tagStruct.get('typeOrNameRequired'));
979
+ };
980
+
981
+ /**
982
+ * @param {import('comment-parser').Spec} tag
983
+ * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
984
+ * @returns {boolean|undefined}
985
+ */
986
+ const tagMissingRequiredTypeOrNamepath = (tag, tagMap = tagStructure) => {
987
+ const mustHaveTypePosition = tagMustHaveTypePosition(tag.tag, tagMap);
988
+ const mightHaveTypePosition = tagMightHaveTypePosition(tag.tag, tagMap);
989
+ const hasTypePosition = mightHaveTypePosition && Boolean(tag.type);
990
+ const hasNameOrNamepathPosition = (
991
+ tagMustHaveNamePosition(tag.tag, tagMap) ||
992
+ tagMightHaveNamepath(tag.tag, tagMap)
993
+ ) && Boolean(tag.name);
994
+ const mustHaveEither = tagMustHaveEitherTypeOrNamePosition(tag.tag, tagMap);
995
+ const hasEither = tagMightHaveEitherTypeOrNamePosition(tag.tag, tagMap) &&
996
+ (hasTypePosition || hasNameOrNamepathPosition);
997
+
998
+ return mustHaveEither && !hasEither && !mustHaveTypePosition;
999
+ };
1000
+
1001
+ /* eslint-disable complexity -- Temporary */
1002
+ /**
1003
+ * @param {ESTreeOrTypeScriptNode|null|undefined} node
1004
+ * @param {boolean} [checkYieldReturnValue]
1005
+ * @returns {boolean}
1006
+ */
1007
+ const hasNonFunctionYield = (node, checkYieldReturnValue) => {
1008
+ /* eslint-enable complexity -- Temporary */
1009
+ if (!node) {
1010
+ return false;
1011
+ }
1012
+
1013
+ switch (node.type) {
1014
+ case 'BlockStatement': {
1015
+ return node.body.some((bodyNode) => {
1016
+ return ![
1017
+ 'ArrowFunctionExpression',
1018
+ 'FunctionDeclaration',
1019
+ 'FunctionExpression',
1020
+ ].includes(bodyNode.type) && hasNonFunctionYield(
1021
+ bodyNode, checkYieldReturnValue,
1022
+ );
1023
+ });
1024
+ }
1025
+
1026
+ /* c8 ignore next 2 -- In Babel? */
1027
+ // @ts-expect-error In Babel?
1028
+ case 'OptionalCallExpression':
1029
+ case 'CallExpression':
1030
+ return node.arguments.some((element) => {
1031
+ return hasNonFunctionYield(element, checkYieldReturnValue);
1032
+ });
1033
+ case 'ChainExpression':
1034
+ case 'ExpressionStatement': {
1035
+ return hasNonFunctionYield(node.expression, checkYieldReturnValue);
1036
+ }
1037
+
1038
+ case 'LabeledStatement':
1039
+ case 'WhileStatement':
1040
+ case 'DoWhileStatement':
1041
+ case 'ForStatement':
1042
+ case 'ForInStatement':
1043
+ case 'ForOfStatement':
1044
+ case 'WithStatement': {
1045
+ return hasNonFunctionYield(node.body, checkYieldReturnValue);
1046
+ }
1047
+
1048
+ case 'ConditionalExpression':
1049
+ case 'IfStatement': {
1050
+ return hasNonFunctionYield(node.test, checkYieldReturnValue) ||
1051
+ hasNonFunctionYield(node.consequent, checkYieldReturnValue) ||
1052
+ hasNonFunctionYield(node.alternate, checkYieldReturnValue);
1053
+ }
1054
+
1055
+ case 'TryStatement': {
1056
+ return hasNonFunctionYield(node.block, checkYieldReturnValue) ||
1057
+ hasNonFunctionYield(
1058
+ node.handler && node.handler.body, checkYieldReturnValue,
1059
+ ) ||
1060
+ hasNonFunctionYield(
1061
+ /** @type {import('@typescript-eslint/types').TSESTree.BlockStatement} */
1062
+ (node.finalizer),
1063
+ checkYieldReturnValue,
1064
+ );
1065
+ }
1066
+
1067
+ case 'SwitchStatement': {
1068
+ return node.cases.some(
1069
+ (someCase) => {
1070
+ return someCase.consequent.some((nde) => {
1071
+ return hasNonFunctionYield(nde, checkYieldReturnValue);
1072
+ });
1073
+ },
1074
+ );
1075
+ }
1076
+
1077
+ case 'ArrayPattern':
1078
+ case 'ArrayExpression':
1079
+ return node.elements.some((element) => {
1080
+ return hasNonFunctionYield(element, checkYieldReturnValue);
1081
+ });
1082
+ case 'AssignmentPattern':
1083
+ return hasNonFunctionYield(node.right, checkYieldReturnValue);
1084
+
1085
+ case 'VariableDeclaration': {
1086
+ return node.declarations.some((nde) => {
1087
+ return hasNonFunctionYield(nde, checkYieldReturnValue);
1088
+ });
1089
+ }
1090
+
1091
+ case 'VariableDeclarator': {
1092
+ return hasNonFunctionYield(node.id, checkYieldReturnValue) ||
1093
+ hasNonFunctionYield(node.init, checkYieldReturnValue);
1094
+ }
1095
+
1096
+ case 'AssignmentExpression':
1097
+ case 'BinaryExpression':
1098
+ case 'LogicalExpression': {
1099
+ return hasNonFunctionYield(node.left, checkYieldReturnValue) ||
1100
+ hasNonFunctionYield(node.right, checkYieldReturnValue);
1101
+ }
1102
+
1103
+ // Comma
1104
+ case 'SequenceExpression':
1105
+ case 'TemplateLiteral':
1106
+ return node.expressions.some((subExpression) => {
1107
+ return hasNonFunctionYield(subExpression, checkYieldReturnValue);
1108
+ });
1109
+
1110
+ case 'ObjectPattern':
1111
+ case 'ObjectExpression':
1112
+ return node.properties.some((property) => {
1113
+ return hasNonFunctionYield(property, checkYieldReturnValue);
1114
+ });
1115
+
1116
+ /* c8 ignore next -- In Babel? */
1117
+ case 'PropertyDefinition':
1118
+ /* eslint-disable no-fallthrough */
1119
+ /* c8 ignore next 2 -- In Babel? */
1120
+ // @ts-expect-error In Babel?
1121
+ case 'ObjectProperty':
1122
+ /* c8 ignore next 2 -- In Babel? */
1123
+ // @ts-expect-error In Babel?
1124
+ case 'ClassProperty':
1125
+ case 'Property':
1126
+ /* eslint-enable no-fallthrough */
1127
+ return node.computed && hasNonFunctionYield(node.key, checkYieldReturnValue) ||
1128
+ hasNonFunctionYield(node.value, checkYieldReturnValue);
1129
+ /* c8 ignore next 2 -- In Babel? */
1130
+ // @ts-expect-error In Babel?
1131
+ case 'ObjectMethod':
1132
+ /* c8 ignore next 6 -- In Babel? */
1133
+ // @ts-expect-error In Babel?
1134
+ return node.computed && hasNonFunctionYield(node.key, checkYieldReturnValue) ||
1135
+ // @ts-expect-error In Babel?
1136
+ node.arguments.some((nde) => {
1137
+ return hasNonFunctionYield(nde, checkYieldReturnValue);
1138
+ });
1139
+
1140
+ case 'SpreadElement':
1141
+ case 'UnaryExpression':
1142
+ return hasNonFunctionYield(node.argument, checkYieldReturnValue);
1143
+
1144
+ case 'TaggedTemplateExpression':
1145
+ return hasNonFunctionYield(node.quasi, checkYieldReturnValue);
1146
+
1147
+ // ?.
1148
+ /* c8 ignore next 2 -- In Babel? */
1149
+ // @ts-expect-error In Babel?
1150
+ case 'OptionalMemberExpression':
1151
+ case 'MemberExpression':
1152
+ return hasNonFunctionYield(node.object, checkYieldReturnValue) ||
1153
+ hasNonFunctionYield(node.property, checkYieldReturnValue);
1154
+
1155
+ /* c8 ignore next 2 -- In Babel? */
1156
+ // @ts-expect-error In Babel?
1157
+ case 'Import':
1158
+ case 'ImportExpression':
1159
+ return hasNonFunctionYield(node.source, checkYieldReturnValue);
1160
+
1161
+ case 'ReturnStatement': {
1162
+ if (node.argument === null) {
1163
+ return false;
1164
+ }
1165
+
1166
+ return hasNonFunctionYield(node.argument, checkYieldReturnValue);
1167
+ }
1168
+
1169
+ case 'YieldExpression': {
1170
+ if (checkYieldReturnValue) {
1171
+ if (
1172
+ /** @type {import('eslint').Rule.Node} */ (
1173
+ node
1174
+ ).parent.type === 'VariableDeclarator'
1175
+ ) {
1176
+ return true;
1177
+ }
1178
+
1179
+ return false;
1180
+ }
1181
+
1182
+ // void return does not count.
1183
+ if (node.argument === null) {
1184
+ return false;
1185
+ }
1186
+
1187
+ return true;
1188
+ }
1189
+
1190
+ default: {
1191
+ return false;
1192
+ }
1193
+ }
1194
+ };
1195
+
1196
+ /**
1197
+ * Checks if a node has a return statement. Void return does not count.
1198
+ * @param {ESTreeOrTypeScriptNode} node
1199
+ * @param {boolean} [checkYieldReturnValue]
1200
+ * @returns {boolean}
1201
+ */
1202
+ const hasYieldValue = (node, checkYieldReturnValue) => {
1203
+ return /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */ (
1204
+ node
1205
+ ).generator && (
1206
+ /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */ (
1207
+ node
1208
+ ).expression || hasNonFunctionYield(
1209
+ /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */
1210
+ (node).body,
1211
+ checkYieldReturnValue,
1212
+ )
1213
+ );
1214
+ };
1215
+
1216
+ /**
1217
+ * Checks if a node has a throws statement.
1218
+ * @param {ESTreeOrTypeScriptNode|null|undefined} node
1219
+ * @param {boolean} [innerFunction]
1220
+ * @returns {boolean}
1221
+ */
1222
+ // eslint-disable-next-line complexity
1223
+ const hasThrowValue = (node, innerFunction) => {
1224
+ if (!node) {
1225
+ return false;
1226
+ }
1227
+
1228
+ // There are cases where a function may execute its inner function which
1229
+ // throws, but we're treating functions atomically rather than trying to
1230
+ // follow them
1231
+ switch (node.type) {
1232
+ case 'FunctionExpression':
1233
+ case 'FunctionDeclaration':
1234
+ case 'ArrowFunctionExpression': {
1235
+ return !innerFunction && !node.async && hasThrowValue(node.body, true);
1236
+ }
1237
+
1238
+ case 'BlockStatement': {
1239
+ return node.body.some((bodyNode) => {
1240
+ return bodyNode.type !== 'FunctionDeclaration' && hasThrowValue(bodyNode);
1241
+ });
1242
+ }
1243
+
1244
+ case 'LabeledStatement':
1245
+ case 'WhileStatement':
1246
+ case 'DoWhileStatement':
1247
+ case 'ForStatement':
1248
+ case 'ForInStatement':
1249
+ case 'ForOfStatement':
1250
+ case 'WithStatement': {
1251
+ return hasThrowValue(node.body);
1252
+ }
1253
+
1254
+ case 'IfStatement': {
1255
+ return hasThrowValue(node.consequent) || hasThrowValue(node.alternate);
1256
+ }
1257
+
1258
+ // We only consider it to throw an error if the catch or finally blocks throw an error.
1259
+ case 'TryStatement': {
1260
+ return hasThrowValue(node.handler && node.handler.body) ||
1261
+ hasThrowValue(node.finalizer);
1262
+ }
1263
+
1264
+ case 'SwitchStatement': {
1265
+ return node.cases.some(
1266
+ (someCase) => {
1267
+ return someCase.consequent.some((nde) => {
1268
+ return hasThrowValue(nde);
1269
+ });
1270
+ },
1271
+ );
1272
+ }
1273
+
1274
+ case 'ThrowStatement': {
1275
+ return true;
1276
+ }
1277
+
1278
+ default: {
1279
+ return false;
1280
+ }
1281
+ }
1282
+ };
1283
+
1284
+ /**
1285
+ * @param {string} tag
1286
+ */
1287
+ /*
1288
+ const isInlineTag = (tag) => {
1289
+ return /^(@link|@linkcode|@linkplain|@tutorial) /u.test(tag);
1290
+ };
1291
+ */
1292
+
1293
+ /**
1294
+ * Parses GCC Generic/Template types
1295
+ * @see {@link https://github.com/google/closure-compiler/wiki/Generic-Types}
1296
+ * @see {@link https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template}
1297
+ * @param {import('comment-parser').Spec} tag
1298
+ * @returns {string[]}
1299
+ */
1300
+ const parseClosureTemplateTag = (tag) => {
1301
+ return tag.name
1302
+ .split(',')
1303
+ .map((type) => {
1304
+ return type.trim().replace(/^\[(?<name>.*?)=.*\]$/u, '$<name>');
1305
+ });
1306
+ };
1307
+
1308
+ /**
1309
+ * @typedef {true|string[]} DefaultContexts
1310
+ */
1311
+
1312
+ /**
1313
+ * Checks user option for `contexts` array, defaulting to
1314
+ * contexts designated by the rule. Returns an array of
1315
+ * ESTree AST types, indicating allowable contexts.
1316
+ * @param {import('eslint').Rule.RuleContext} context
1317
+ * @param {DefaultContexts|undefined} defaultContexts
1318
+ * @param {{
1319
+ * contexts?: import('./iterateJsdoc.js').Context[]
1320
+ * }} settings
1321
+ * @returns {string[]}
1322
+ */
1323
+ const enforcedContexts = (context, defaultContexts, settings) => {
1324
+ const contexts = context.options[0]?.contexts || settings.contexts || (defaultContexts === true ? [
1325
+ 'ArrowFunctionExpression',
1326
+ 'FunctionDeclaration',
1327
+ 'FunctionExpression',
1328
+ 'TSDeclareFunction',
1329
+ ] : defaultContexts);
1330
+
1331
+ return contexts;
1332
+ };
1333
+
1334
+ /**
1335
+ * @param {import('./iterateJsdoc.js').Context[]} contexts
1336
+ * @param {import('./iterateJsdoc.js').CheckJsdoc} checkJsdoc
1337
+ * @param {import('@es-joy/jsdoccomment').CommentHandler} [handler]
1338
+ * @returns {import('eslint').Rule.RuleListener}
1339
+ */
1340
+ const getContextObject = (contexts, checkJsdoc, handler) => {
1341
+ /** @type {import('eslint').Rule.RuleListener} */
1342
+ const properties = {};
1343
+
1344
+ for (const [
1345
+ idx,
1346
+ prop,
1347
+ ] of contexts.entries()) {
1348
+ /** @type {string} */
1349
+ let property;
1350
+
1351
+ /** @type {(node: import('eslint').Rule.Node) => void} */
1352
+ let value;
1353
+
1354
+ if (typeof prop === 'object') {
1355
+ const selInfo = {
1356
+ lastIndex: idx,
1357
+ selector: prop.context,
1358
+ };
1359
+ if (prop.comment) {
1360
+ property = /** @type {string} */ (prop.context);
1361
+ value = checkJsdoc.bind(
1362
+ null,
1363
+ {
1364
+ ...selInfo,
1365
+ comment: prop.comment,
1366
+ },
1367
+ /**
1368
+ * @type {(jsdoc: import('@es-joy/jsdoccomment').JsdocBlockWithInline) => boolean}
1369
+ */
1370
+ (/** @type {import('@es-joy/jsdoccomment').CommentHandler} */ (
1371
+ handler
1372
+ ).bind(null, prop.comment)),
1373
+ );
1374
+ } else {
1375
+ property = /** @type {string} */ (prop.context);
1376
+ value = checkJsdoc.bind(null, selInfo, null);
1377
+ }
1378
+ } else {
1379
+ const selInfo = {
1380
+ lastIndex: idx,
1381
+ selector: prop,
1382
+ };
1383
+ property = prop;
1384
+ value = checkJsdoc.bind(null, selInfo, null);
1385
+ }
1386
+
1387
+ const old = /**
1388
+ * @type {((node: import('eslint').Rule.Node) => void)}
1389
+ */ (properties[property]);
1390
+ properties[property] = old ?
1391
+ /**
1392
+ * @type {((node: import('eslint').Rule.Node) => void)}
1393
+ */
1394
+ function (node) {
1395
+ old(node);
1396
+ value(node);
1397
+ } :
1398
+ value;
1399
+ }
1400
+
1401
+ return properties;
1402
+ };
1403
+
1404
+ const tagsWithNamesAndDescriptions = new Set([
1405
+ 'param', 'arg', 'argument', 'property', 'prop',
1406
+ 'template',
1407
+
1408
+ // These two are parsed by our custom parser as though having a `name`
1409
+ 'returns', 'return',
1410
+ ]);
1411
+
1412
+ /**
1413
+ * @typedef {{
1414
+ * [key: string]: false|string|
1415
+ * {message: string, replacement?: string}
1416
+ * }} TagNamePreference
1417
+ */
1418
+
1419
+ /**
1420
+ * @param {import('eslint').Rule.RuleContext} context
1421
+ * @param {ParserMode|undefined} mode
1422
+ * @param {import('comment-parser').Spec[]} tags
1423
+ * @returns {{
1424
+ * tagsWithNames: import('comment-parser').Spec[],
1425
+ * tagsWithoutNames: import('comment-parser').Spec[]
1426
+ * }}
1427
+ */
1428
+ const getTagsByType = (context, mode, tags) => {
1429
+ /**
1430
+ * @type {import('comment-parser').Spec[]}
1431
+ */
1432
+ const tagsWithoutNames = [];
1433
+ const tagsWithNames = tags.filter((tag) => {
1434
+ const {
1435
+ tag: tagName,
1436
+ } = tag;
1437
+ const tagWithName = tagsWithNamesAndDescriptions.has(tagName);
1438
+ if (!tagWithName) {
1439
+ tagsWithoutNames.push(tag);
1440
+ }
1441
+
1442
+ return tagWithName;
1443
+ });
1444
+
1445
+ return {
1446
+ tagsWithNames,
1447
+ tagsWithoutNames,
1448
+ };
1449
+ };
1450
+
1451
+ /**
1452
+ * @param {import('eslint').SourceCode|{
1453
+ * text: string
1454
+ * }} sourceCode
1455
+ * @returns {string}
1456
+ */
1457
+ const getIndent = (sourceCode) => {
1458
+ return (sourceCode.text.match(/^\n*([ \t]+)/u)?.[1] ?? '') + ' ';
1459
+ };
1460
+
1461
+ /**
1462
+ * @param {import('eslint').Rule.Node|null} node
1463
+ * @returns {boolean}
1464
+ */
1465
+ const isConstructor = (node) => {
1466
+ return node?.type === 'MethodDefinition' && node.kind === 'constructor' ||
1467
+ /** @type {import('@typescript-eslint/types').TSESTree.MethodDefinition} */ (
1468
+ node?.parent
1469
+ )?.kind === 'constructor';
1470
+ };
1471
+
1472
+ /**
1473
+ * @param {import('eslint').Rule.Node|null} node
1474
+ * @returns {boolean}
1475
+ */
1476
+ const isGetter = (node) => {
1477
+ return node !== null &&
1478
+ /**
1479
+ * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition|
1480
+ * import('@typescript-eslint/types').TSESTree.Property}
1481
+ */ (
1482
+ node.parent
1483
+ )?.kind === 'get';
1484
+ };
1485
+
1486
+ /**
1487
+ * @param {import('eslint').Rule.Node|null} node
1488
+ * @returns {boolean}
1489
+ */
1490
+ const isSetter = (node) => {
1491
+ return node !== null &&
1492
+ /**
1493
+ * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition|
1494
+ * import('@typescript-eslint/types').TSESTree.Property}
1495
+ */(
1496
+ node.parent
1497
+ )?.kind === 'set';
1498
+ };
1499
+
1500
+ /**
1501
+ * @param {import('eslint').Rule.Node} node
1502
+ * @returns {boolean}
1503
+ */
1504
+ const hasAccessorPair = (node) => {
1505
+ const {
1506
+ type,
1507
+ kind: sourceKind,
1508
+ key,
1509
+ } =
1510
+ /**
1511
+ * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition|
1512
+ * import('@typescript-eslint/types').TSESTree.Property}
1513
+ */ (node);
1514
+
1515
+ const sourceName =
1516
+ /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
1517
+ key
1518
+ ).name;
1519
+
1520
+ const oppositeKind = sourceKind === 'get' ? 'set' : 'get';
1521
+
1522
+ const sibling = type === 'MethodDefinition' ?
1523
+ /** @type {import('@typescript-eslint/types').TSESTree.ClassBody} */ (
1524
+ node.parent
1525
+ ).body :
1526
+ /** @type {import('@typescript-eslint/types').TSESTree.ObjectExpression} */ (
1527
+ node.parent
1528
+ ).properties;
1529
+
1530
+ return (
1531
+ sibling.some((child) => {
1532
+ const {
1533
+ kind,
1534
+ key: ky,
1535
+ } = /**
1536
+ * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition|
1537
+ * import('@typescript-eslint/types').TSESTree.Property}
1538
+ */ (child);
1539
+
1540
+ const name =
1541
+ /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
1542
+ ky
1543
+ ).name;
1544
+
1545
+ return kind === oppositeKind && name === sourceName;
1546
+ })
1547
+ );
1548
+ };
1549
+
1550
+ /**
1551
+ * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
1552
+ * @param {import('eslint').Rule.Node|null} node
1553
+ * @param {import('eslint').Rule.RuleContext} context
1554
+ * @param {import('json-schema').JSONSchema4} schema
1555
+ * @returns {boolean}
1556
+ */
1557
+ const exemptSpeciaMethods = (jsdoc, node, context, schema) => {
1558
+ /**
1559
+ * @param {"checkGetters"|"checkSetters"|"checkConstructors"} prop
1560
+ * @returns {boolean|"no-setter"|"no-getter"}
1561
+ */
1562
+ const hasSchemaOption = (prop) => {
1563
+ const schemaProperties = schema[0].properties;
1564
+
1565
+ return context.options[0]?.[prop] ??
1566
+ (schemaProperties[prop] && schemaProperties[prop].default);
1567
+ };
1568
+
1569
+ const checkGetters = hasSchemaOption('checkGetters');
1570
+ const checkSetters = hasSchemaOption('checkSetters');
1571
+
1572
+ return !hasSchemaOption('checkConstructors') &&
1573
+ (
1574
+ isConstructor(node) ||
1575
+ hasATag(jsdoc, [
1576
+ 'class',
1577
+ 'constructor',
1578
+ ])) ||
1579
+ isGetter(node) && (
1580
+ !checkGetters ||
1581
+ checkGetters === 'no-setter' && hasAccessorPair(/** @type {import('./iterateJsdoc.js').Node} */ (node).parent)
1582
+ ) ||
1583
+ isSetter(node) && (
1584
+ !checkSetters ||
1585
+ checkSetters === 'no-getter' && hasAccessorPair(/** @type {import('./iterateJsdoc.js').Node} */ (node).parent)
1586
+ );
1587
+ };
1588
+
1589
+ /**
1590
+ * Since path segments may be unquoted (if matching a reserved word,
1591
+ * identifier or numeric literal) or single or double quoted, in either
1592
+ * the `@param` or in source, we need to strip the quotes to give a fair
1593
+ * comparison.
1594
+ * @param {string} str
1595
+ * @returns {string}
1596
+ */
1597
+ const dropPathSegmentQuotes = (str) => {
1598
+ return str.replaceAll(/\.(['"])(.*)\1/gu, '.$2');
1599
+ };
1600
+
1601
+ /**
1602
+ * @param {string} name
1603
+ * @returns {(otherPathName: string) => boolean}
1604
+ */
1605
+ const comparePaths = (name) => {
1606
+ return (otherPathName) => {
1607
+ return otherPathName === name ||
1608
+ dropPathSegmentQuotes(otherPathName) === dropPathSegmentQuotes(name);
1609
+ };
1610
+ };
1611
+
1612
+ /**
1613
+ * @callback PathDoesNotBeginWith
1614
+ * @param {string} name
1615
+ * @param {string} otherPathName
1616
+ * @returns {boolean}
1617
+ */
1618
+
1619
+ /** @type {PathDoesNotBeginWith} */
1620
+ const pathDoesNotBeginWith = (name, otherPathName) => {
1621
+ return !name.startsWith(otherPathName) &&
1622
+ !dropPathSegmentQuotes(name).startsWith(dropPathSegmentQuotes(otherPathName));
1623
+ };
1624
+
1625
+ /**
1626
+ * @param {string} regexString
1627
+ * @param {string} [requiredFlags]
1628
+ * @returns {RegExp}
1629
+ */
1630
+ const getRegexFromString = (regexString, requiredFlags) => {
1631
+ const match = regexString.match(/^\/(.*)\/([gimyus]*)$/us);
1632
+ let flags = 'u';
1633
+ let regex = regexString;
1634
+ if (match) {
1635
+ [
1636
+ , regex,
1637
+ flags,
1638
+ ] = match;
1639
+ if (!flags) {
1640
+ flags = 'u';
1641
+ }
1642
+ }
1643
+
1644
+ const uniqueFlags = [
1645
+ ...new Set(flags + (requiredFlags || '')),
1646
+ ];
1647
+ flags = uniqueFlags.join('');
1648
+
1649
+ return new RegExp(regex, flags);
1650
+ };
1651
+
1652
+ export default {
1653
+ comparePaths,
1654
+ dropPathSegmentQuotes,
1655
+ enforcedContexts,
1656
+ exemptSpeciaMethods,
1657
+ flattenRoots,
1658
+ getAllTags,
1659
+ getContextObject,
1660
+ getFunctionParameterNames,
1661
+ getIndent,
1662
+ getJsdocTagsDeep,
1663
+ getPreferredTagName,
1664
+ getRegexFromString,
1665
+ getTagsByType,
1666
+ getTagStructureForMode,
1667
+ hasATag,
1668
+ hasParams,
1669
+ hasReturnValue,
1670
+ hasTag,
1671
+ hasThrowValue,
1672
+ hasValueOrExecutorHasNonEmptyResolveValue,
1673
+ hasYieldValue,
1674
+ isConstructor,
1675
+ isGetter,
1676
+ isNamepathDefiningTag,
1677
+ isNamepathOrUrlReferencingTag,
1678
+ isNamepathReferencingTag,
1679
+ isSetter,
1680
+ isValidTag,
1681
+ mayBeUndefinedTypeTag,
1682
+ overrideTagStructure,
1683
+ parseClosureTemplateTag,
1684
+ pathDoesNotBeginWith,
1685
+ setTagStructure,
1686
+ tagMightHaveEitherTypeOrNamePosition,
1687
+ tagMightHaveNamepath,
1688
+ tagMightHaveNamePosition,
1689
+ tagMightHaveTypePosition,
1690
+ tagMissingRequiredTypeOrNamepath,
1691
+ tagMustHaveNamePosition,
1692
+ tagMustHaveTypePosition,
1693
+ };