eslint-plugin-jsdoc 57.2.1 → 58.1.0

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.
@@ -1,10 +1,6 @@
1
- import iterateJsdoc from '../iterateJsdoc.js';
2
1
  import {
3
- parse,
4
- stringify,
5
- traverse,
6
- tryParse,
7
- } from '@es-joy/jsdoccomment';
2
+ buildRejectOrPreferRuleDefinition,
3
+ } from '../buildRejectOrPreferRuleDefinition.js';
8
4
 
9
5
  const strictNativeTypes = [
10
6
  'undefined',
@@ -22,535 +18,110 @@ const strictNativeTypes = [
22
18
  ];
23
19
 
24
20
  /**
25
- * Adjusts the parent type node `meta` for generic matches (or type node
26
- * `type` for `JsdocTypeAny`) and sets the type node `value`.
27
- * @param {string} type The actual type
28
- * @param {string} preferred The preferred type
29
- * @param {boolean} isGenericMatch
21
+ * @callback CheckNativeTypes
22
+ * Iterates strict types to see if any should be added to `invalidTypes` (and
23
+ * the the relevant strict type returned as the new preferred type).
24
+ * @param {import('../iterateJsdoc.js').PreferredTypes} preferredTypes
30
25
  * @param {string} typeNodeName
31
- * @param {import('jsdoc-type-pratt-parser').NonRootResult} node
26
+ * @param {string|undefined} preferred
32
27
  * @param {import('jsdoc-type-pratt-parser').NonRootResult|undefined} parentNode
33
- * @returns {void}
28
+ * @param {(string|false|undefined)[][]} invalidTypes
29
+ * @returns {string|undefined} The `preferred` type string, optionally changed
34
30
  */
35
- const adjustNames = (type, preferred, isGenericMatch, typeNodeName, node, parentNode) => {
36
- let ret = preferred;
37
- if (isGenericMatch) {
38
- const parentMeta = /** @type {import('jsdoc-type-pratt-parser').GenericResult} */ (
39
- parentNode
40
- ).meta;
41
- if (preferred === '[]') {
42
- parentMeta.brackets = 'square';
43
- parentMeta.dot = false;
44
- ret = 'Array';
45
- } else {
46
- const dotBracketEnd = preferred.match(/\.(?:<>)?$/v);
47
- if (dotBracketEnd) {
48
- parentMeta.brackets = 'angle';
49
- parentMeta.dot = true;
50
- ret = preferred.slice(0, -dotBracketEnd[0].length);
51
- } else {
52
- const bracketEnd = preferred.endsWith('<>');
53
- if (bracketEnd) {
54
- parentMeta.brackets = 'angle';
55
- parentMeta.dot = false;
56
- ret = preferred.slice(0, -2);
57
- } else if (
58
- parentMeta?.brackets === 'square' &&
59
- (typeNodeName === '[]' || typeNodeName === 'Array')
60
- ) {
61
- parentMeta.brackets = 'angle';
62
- parentMeta.dot = false;
63
- }
64
- }
65
- }
66
- } else if (type === 'JsdocTypeAny') {
67
- node.type = 'JsdocTypeName';
68
- }
69
-
70
- /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (
71
- node
72
- ).value = ret.replace(/(?:\.|<>|\.<>|\[\])$/v, '');
73
-
74
- // For bare pseudo-types like `<>`
75
- if (!ret) {
76
- /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (
77
- node
78
- ).value = typeNodeName;
79
- }
80
- };
81
-
82
- /**
83
- * @param {boolean} [upperCase]
84
- * @returns {string}
85
- */
86
- const getMessage = (upperCase) => {
87
- return 'Use object shorthand or index signatures instead of ' +
88
- '`' + (upperCase ? 'O' : 'o') + 'bject`, e.g., `{[key: string]: string}`';
89
- };
90
-
91
- /**
92
- * @type {{
93
- * message: string,
94
- * replacement: false
95
- * }}
96
- */
97
- const info = {
98
- message: getMessage(),
99
- replacement: false,
100
- };
101
-
102
- /**
103
- * @type {{
104
- * message: string,
105
- * replacement: false
106
- * }}
107
- */
108
- const infoUC = {
109
- message: getMessage(true),
110
- replacement: false,
111
- };
112
-
113
- export default iterateJsdoc(({
114
- context,
115
- jsdocNode,
116
- report,
117
- settings,
118
- sourceCode,
119
- utils,
120
- }) => {
121
- const jsdocTagsWithPossibleType = utils.filterTags((tag) => {
122
- return Boolean(utils.tagMightHaveTypePosition(tag.tag));
123
- });
124
-
125
- const
126
- /**
127
- * @type {{
128
- * preferredTypes: import('../iterateJsdoc.js').PreferredTypes,
129
- * structuredTags: import('../iterateJsdoc.js').StructuredTags,
130
- * mode: import('../jsdocUtils.js').ParserMode
131
- * }}
132
- */
133
- {
134
- mode,
135
- preferredTypes: preferredTypesOriginal,
136
- structuredTags,
137
- } = settings;
138
-
139
- const injectObjectPreferredTypes = !('Object' in preferredTypesOriginal ||
140
- 'object' in preferredTypesOriginal ||
141
- 'object.<>' in preferredTypesOriginal ||
142
- 'Object.<>' in preferredTypesOriginal ||
143
- 'object<>' in preferredTypesOriginal);
144
-
145
- /** @type {import('../iterateJsdoc.js').PreferredTypes} */
146
- const typeToInject = mode === 'typescript' ?
147
- {
148
- Object: 'object',
149
- 'object.<>': info,
150
- 'Object.<>': infoUC,
151
- 'object<>': info,
152
- 'Object<>': infoUC,
153
- } :
154
- {
155
- Object: 'object',
156
- 'object.<>': 'Object<>',
157
- 'Object.<>': 'Object<>',
158
- 'object<>': 'Object<>',
159
- };
160
-
161
- /** @type {import('../iterateJsdoc.js').PreferredTypes} */
162
- const preferredTypes = {
163
- ...injectObjectPreferredTypes ?
164
- typeToInject :
165
- {},
166
- ...preferredTypesOriginal,
167
- };
168
-
169
- const
170
- /**
171
- * @type {{
172
- * noDefaults: boolean,
173
- * unifyParentAndChildTypeChecks: boolean,
174
- * exemptTagContexts: ({
175
- * tag: string,
176
- * types: true|string[]
177
- * })[]
178
- * }}
179
- */ {
180
- exemptTagContexts = [],
181
- noDefaults,
182
- unifyParentAndChildTypeChecks,
183
- } = context.options[0] || {};
184
-
185
- /**
186
- * Gets information about the preferred type: whether there is a matching
187
- * preferred type, what the type is, and whether it is a match to a generic.
188
- * @param {string} _type Not currently in use
189
- * @param {string} typeNodeName
190
- * @param {import('jsdoc-type-pratt-parser').NonRootResult|undefined} parentNode
191
- * @param {string|undefined} property
192
- * @returns {[hasMatchingPreferredType: boolean, typeName: string, isGenericMatch: boolean]}
193
- */
194
- const getPreferredTypeInfo = (_type, typeNodeName, parentNode, property) => {
195
- let hasMatchingPreferredType = false;
196
- let isGenericMatch = false;
197
- let typeName = typeNodeName;
198
-
199
- const isNameOfGeneric = parentNode !== undefined && parentNode.type === 'JsdocTypeGeneric' && property === 'left';
200
-
201
- const brackets = /** @type {import('jsdoc-type-pratt-parser').GenericResult} */ (
202
- parentNode
203
- )?.meta?.brackets;
204
- const dot = /** @type {import('jsdoc-type-pratt-parser').GenericResult} */ (
205
- parentNode
206
- )?.meta?.dot;
207
-
208
- if (brackets === 'angle') {
209
- const checkPostFixes = dot ? [
210
- '.', '.<>',
211
- ] : [
212
- '<>',
213
- ];
214
- isGenericMatch = checkPostFixes.some((checkPostFix) => {
215
- const preferredType = preferredTypes?.[typeNodeName + checkPostFix];
216
-
217
- // Does `unifyParentAndChildTypeChecks` need to be checked here?
218
- if (
219
- (unifyParentAndChildTypeChecks || isNameOfGeneric ||
220
- /* c8 ignore next 2 -- If checking `unifyParentAndChildTypeChecks` */
221
- (typeof preferredType === 'object' &&
222
- preferredType?.unifyParentAndChildTypeChecks)
223
- ) &&
224
- preferredType !== undefined
225
- ) {
226
- typeName += checkPostFix;
227
-
228
- return true;
229
- }
230
-
231
- return false;
232
- });
233
- }
234
31
 
32
+ /** @type {CheckNativeTypes} */
33
+ const checkNativeTypes = (preferredTypes, typeNodeName, preferred, parentNode, invalidTypes) => {
34
+ let changedPreferred = preferred;
35
+ for (const strictNativeType of strictNativeTypes) {
235
36
  if (
236
- !isGenericMatch && property &&
237
- /** @type {import('jsdoc-type-pratt-parser').NonRootResult} */ (
238
- parentNode
239
- ).type === 'JsdocTypeGeneric'
240
- ) {
241
- const checkPostFixes = dot ? [
242
- '.', '.<>',
243
- ] : [
244
- brackets === 'angle' ? '<>' : '[]',
245
- ];
246
-
247
- isGenericMatch = checkPostFixes.some((checkPostFix) => {
248
- const preferredType = preferredTypes?.[checkPostFix];
249
- if (
250
- // Does `unifyParentAndChildTypeChecks` need to be checked here?
251
- (unifyParentAndChildTypeChecks || isNameOfGeneric ||
252
- /* c8 ignore next 2 -- If checking `unifyParentAndChildTypeChecks` */
253
- (typeof preferredType === 'object' &&
254
- preferredType?.unifyParentAndChildTypeChecks)) &&
255
- preferredType !== undefined
256
- ) {
257
- typeName = checkPostFix;
258
-
259
- return true;
260
- }
261
-
262
- return false;
263
- });
264
- }
265
-
266
- const prefType = preferredTypes?.[typeNodeName];
267
- const directNameMatch = prefType !== undefined &&
268
- !Object.values(preferredTypes).includes(typeNodeName);
269
- const specificUnify = typeof prefType === 'object' &&
270
- prefType?.unifyParentAndChildTypeChecks;
271
- const unifiedSyntaxParentMatch = property && directNameMatch && (unifyParentAndChildTypeChecks || specificUnify);
272
- isGenericMatch = isGenericMatch || Boolean(unifiedSyntaxParentMatch);
273
-
274
- hasMatchingPreferredType = isGenericMatch ||
275
- directNameMatch && !property;
276
-
277
- return [
278
- hasMatchingPreferredType, typeName, isGenericMatch,
279
- ];
280
- };
281
-
282
- /**
283
- * Iterates strict types to see if any should be added to `invalidTypes` (and
284
- * the the relevant strict type returned as the new preferred type).
285
- * @param {string} typeNodeName
286
- * @param {string|undefined} preferred
287
- * @param {import('jsdoc-type-pratt-parser').NonRootResult|undefined} parentNode
288
- * @param {(string|false|undefined)[][]} invalidTypes
289
- * @returns {string|undefined} The `preferred` type string, optionally changed
290
- */
291
- const checkNativeTypes = (typeNodeName, preferred, parentNode, invalidTypes) => {
292
- let changedPreferred = preferred;
293
- for (const strictNativeType of strictNativeTypes) {
294
- if (
295
- strictNativeType === 'object' &&
37
+ strictNativeType === 'object' &&
38
+ (
39
+ // This is not set to remap with exact type match (e.g.,
40
+ // `object: 'Object'`), so can ignore (including if circular)
41
+ !preferredTypes?.[typeNodeName] ||
42
+ // Although present on `preferredTypes` for remapping, this is a
43
+ // parent object without a parent match (and not
44
+ // `unifyParentAndChildTypeChecks`) and we don't want
45
+ // `object<>` given TypeScript issue https://github.com/microsoft/TypeScript/issues/20555
46
+ /**
47
+ * @type {import('jsdoc-type-pratt-parser').GenericResult}
48
+ */
296
49
  (
297
- // This is not set to remap with exact type match (e.g.,
298
- // `object: 'Object'`), so can ignore (including if circular)
299
- !preferredTypes?.[typeNodeName] ||
300
- // Although present on `preferredTypes` for remapping, this is a
301
- // parent object without a parent match (and not
302
- // `unifyParentAndChildTypeChecks`) and we don't want
303
- // `object<>` given TypeScript issue https://github.com/microsoft/TypeScript/issues/20555
304
- /**
305
- * @type {import('jsdoc-type-pratt-parser').GenericResult}
306
- */
50
+ parentNode
51
+ )?.elements?.length && (
52
+ /**
53
+ * @type {import('jsdoc-type-pratt-parser').GenericResult}
54
+ */
307
55
  (
308
56
  parentNode
309
- )?.elements?.length && (
57
+ )?.left?.type === 'JsdocTypeName' &&
310
58
  /**
311
59
  * @type {import('jsdoc-type-pratt-parser').GenericResult}
312
60
  */
313
- (
314
- parentNode
315
- )?.left?.type === 'JsdocTypeName' &&
316
- /**
317
- * @type {import('jsdoc-type-pratt-parser').GenericResult}
318
- */
319
- (parentNode)?.left?.value === 'Object'
320
- )
61
+ (parentNode)?.left?.value === 'Object'
321
62
  )
322
- ) {
323
- continue;
324
- }
325
-
326
- if (strictNativeType !== typeNodeName &&
327
- strictNativeType.toLowerCase() === typeNodeName.toLowerCase() &&
328
-
329
- // Don't report if user has own map for a strict native type
330
- (!preferredTypes || preferredTypes?.[strictNativeType] === undefined)
331
- ) {
332
- changedPreferred = strictNativeType;
333
- invalidTypes.push([
334
- typeNodeName, changedPreferred,
335
- ]);
336
- break;
337
- }
63
+ )
64
+ ) {
65
+ continue;
338
66
  }
339
67
 
340
- return changedPreferred;
341
- };
342
-
343
- /**
344
- * Collect invalid type info.
345
- * @param {string} type
346
- * @param {string} value
347
- * @param {string} tagName
348
- * @param {string} nameInTag
349
- * @param {number} idx
350
- * @param {string|undefined} property
351
- * @param {import('jsdoc-type-pratt-parser').NonRootResult} node
352
- * @param {import('jsdoc-type-pratt-parser').NonRootResult|undefined} parentNode
353
- * @param {(string|false|undefined)[][]} invalidTypes
354
- * @returns {void}
355
- */
356
- const getInvalidTypes = (type, value, tagName, nameInTag, idx, property, node, parentNode, invalidTypes) => {
357
- let typeNodeName = type === 'JsdocTypeAny' ? '*' : value;
68
+ if (strictNativeType !== typeNodeName &&
69
+ strictNativeType.toLowerCase() === typeNodeName.toLowerCase() &&
358
70
 
359
- const [
360
- hasMatchingPreferredType,
361
- typeName,
362
- isGenericMatch,
363
- ] = getPreferredTypeInfo(type, typeNodeName, parentNode, property);
364
-
365
- let preferred;
366
- let types;
367
- if (hasMatchingPreferredType) {
368
- const preferredSetting = preferredTypes[typeName];
369
- typeNodeName = typeName === '[]' ? typeName : typeNodeName;
370
-
371
- if (!preferredSetting) {
372
- invalidTypes.push([
373
- typeNodeName,
374
- ]);
375
- } else if (typeof preferredSetting === 'string') {
376
- preferred = preferredSetting;
377
- invalidTypes.push([
378
- typeNodeName, preferred,
379
- ]);
380
- } else if (preferredSetting && typeof preferredSetting === 'object') {
381
- const nextItem = preferredSetting.skipRootChecking && jsdocTagsWithPossibleType[idx + 1];
382
-
383
- if (!nextItem || !nextItem.name.startsWith(`${nameInTag}.`)) {
384
- preferred = preferredSetting.replacement;
385
- invalidTypes.push([
386
- typeNodeName,
387
- preferred,
388
- preferredSetting.message,
389
- ]);
390
- }
391
- } else {
392
- utils.reportSettings(
393
- 'Invalid `settings.jsdoc.preferredTypes`. Values must be falsy, a string, or an object.',
394
- );
395
-
396
- return;
397
- }
398
- } else if (Object.entries(structuredTags).some(([
399
- tag,
400
- {
401
- type: typs,
402
- },
403
- ]) => {
404
- types = typs;
405
-
406
- return tag === tagName &&
407
- Array.isArray(types) &&
408
- !types.includes(typeNodeName);
409
- })) {
71
+ // Don't report if user has own map for a strict native type
72
+ (!preferredTypes || preferredTypes?.[strictNativeType] === undefined)
73
+ ) {
74
+ changedPreferred = strictNativeType;
410
75
  invalidTypes.push([
411
- typeNodeName, types,
76
+ typeNodeName, changedPreferred,
412
77
  ]);
413
- } else if (!noDefaults && type === 'JsdocTypeName') {
414
- preferred = checkNativeTypes(typeNodeName, preferred, parentNode, invalidTypes);
415
- }
416
-
417
- // For fixer
418
- if (preferred) {
419
- adjustNames(type, preferred, isGenericMatch, typeNodeName, node, parentNode);
78
+ break;
420
79
  }
421
- };
422
-
423
- for (const [
424
- idx,
425
- jsdocTag,
426
- ] of jsdocTagsWithPossibleType.entries()) {
427
- /** @type {(string|false|undefined)[][]} */
428
- const invalidTypes = [];
429
- let typeAst;
430
-
431
- try {
432
- typeAst = mode === 'permissive' ? tryParse(jsdocTag.type) : parse(jsdocTag.type, mode);
433
- } catch {
434
- continue;
435
- }
436
-
437
- const {
438
- name: nameInTag,
439
- tag: tagName,
440
- } = jsdocTag;
441
-
442
- traverse(typeAst, (node, parentNode, property) => {
443
- const {
444
- type,
445
- value,
446
- } =
447
- /**
448
- * @type {import('jsdoc-type-pratt-parser').NameResult}
449
- */ (node);
450
- if (![
451
- 'JsdocTypeAny', 'JsdocTypeName',
452
- ].includes(type)) {
453
- return;
454
- }
455
-
456
- getInvalidTypes(type, value, tagName, nameInTag, idx, property, node, parentNode, invalidTypes);
457
- });
458
-
459
- if (invalidTypes.length) {
460
- const fixedType = stringify(typeAst);
461
-
462
- /**
463
- * @type {import('eslint').Rule.ReportFixer}
464
- */
465
- const fix = (fixer) => {
466
- return fixer.replaceText(
467
- jsdocNode,
468
- sourceCode.getText(jsdocNode).replace(
469
- `{${jsdocTag.type}}`,
470
- `{${fixedType}}`,
471
- ),
472
- );
473
- };
80
+ }
474
81
 
475
- for (const [
476
- badType,
477
- preferredType = '',
478
- msg,
479
- ] of invalidTypes) {
480
- const tagValue = jsdocTag.name ? ` "${jsdocTag.name}"` : '';
481
- if (exemptTagContexts.some(({
482
- tag,
483
- types,
484
- }) => {
485
- return tag === tagName &&
486
- (types === true || types.includes(jsdocTag.type));
487
- })) {
488
- continue;
489
- }
82
+ return changedPreferred;
83
+ };
490
84
 
491
- report(
492
- msg ||
493
- `Invalid JSDoc @${tagName}${tagValue} type "${badType}"` +
494
- (preferredType ? '; ' : '.') +
495
- (preferredType ? `prefer: ${JSON.stringify(preferredType)}.` : ''),
496
- preferredType ? fix : null,
497
- jsdocTag,
498
- msg ? {
499
- tagName,
500
- tagValue,
501
- } : undefined,
502
- );
503
- }
504
- }
505
- }
506
- }, {
507
- iterateAllJsdocs: true,
508
- meta: {
509
- docs: {
510
- description: 'Reports invalid types.',
511
- url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-types.md#repos-sticky-header',
512
- },
513
- fixable: 'code',
514
- schema: [
515
- {
516
- additionalProperties: false,
517
- properties: {
518
- exemptTagContexts: {
519
- items: {
520
- additionalProperties: false,
521
- properties: {
522
- tag: {
523
- type: 'string',
524
- },
525
- types: {
526
- oneOf: [
527
- {
528
- type: 'boolean',
529
- },
530
- {
531
- items: {
532
- type: 'string',
533
- },
534
- type: 'array',
85
+ export default buildRejectOrPreferRuleDefinition({
86
+ checkNativeTypes,
87
+ schema: [
88
+ {
89
+ additionalProperties: false,
90
+ properties: {
91
+ exemptTagContexts: {
92
+ items: {
93
+ additionalProperties: false,
94
+ properties: {
95
+ tag: {
96
+ type: 'string',
97
+ },
98
+ types: {
99
+ oneOf: [
100
+ {
101
+ type: 'boolean',
102
+ },
103
+ {
104
+ items: {
105
+ type: 'string',
535
106
  },
536
- ],
537
- },
107
+ type: 'array',
108
+ },
109
+ ],
538
110
  },
539
- type: 'object',
540
111
  },
541
- type: 'array',
542
- },
543
- noDefaults: {
544
- type: 'boolean',
545
- },
546
- unifyParentAndChildTypeChecks: {
547
- description: '@deprecated Use the `preferredTypes[preferredType]` setting of the same name instead',
548
- type: 'boolean',
112
+ type: 'object',
549
113
  },
114
+ type: 'array',
115
+ },
116
+ noDefaults: {
117
+ type: 'boolean',
118
+ },
119
+ unifyParentAndChildTypeChecks: {
120
+ description: '@deprecated Use the `preferredTypes[preferredType]` setting of the same name instead',
121
+ type: 'boolean',
550
122
  },
551
- type: 'object',
552
123
  },
553
- ],
554
- type: 'suggestion',
555
- },
124
+ type: 'object',
125
+ },
126
+ ],
556
127
  });
@@ -35,6 +35,7 @@ const rootNamer = (desiredRoots, currentIndex) => {
35
35
  export default iterateJsdoc(({
36
36
  context,
37
37
  jsdoc,
38
+ node,
38
39
  utils,
39
40
  }) => {
40
41
  /* eslint-enable complexity -- Temporary */
@@ -57,12 +58,28 @@ export default iterateJsdoc(({
57
58
  enableRestElementFixer = true,
58
59
  enableRootFixer = true,
59
60
  ignoreWhenAllParamsMissing = false,
61
+ interfaceExemptsParamsCheck = false,
60
62
  unnamedRootBase = [
61
63
  'root',
62
64
  ],
63
65
  useDefaultObjectProperties = false,
64
66
  } = context.options[0] || {};
65
67
 
68
+ if (interfaceExemptsParamsCheck) {
69
+ if (node && 'params' in node && node.params.length === 1 &&
70
+ node.params?.[0] && typeof node.params[0] === 'object' &&
71
+ node.params[0].type === 'ObjectPattern' &&
72
+ 'typeAnnotation' in node.params[0] && node.params[0].typeAnnotation
73
+ ) {
74
+ return;
75
+ }
76
+
77
+ if (node && node.parent.type === 'VariableDeclarator' &&
78
+ 'typeAnnotation' in node.parent.id && node.parent.id.typeAnnotation) {
79
+ return;
80
+ }
81
+ }
82
+
66
83
  const preferredTagName = /** @type {string} */ (utils.getPreferredTagName({
67
84
  tagName: 'param',
68
85
  }));
@@ -579,6 +596,9 @@ export default iterateJsdoc(({
579
596
  ignoreWhenAllParamsMissing: {
580
597
  type: 'boolean',
581
598
  },
599
+ interfaceExemptsParamsCheck: {
600
+ type: 'boolean',
601
+ },
582
602
  unnamedRootBase: {
583
603
  items: {
584
604
  type: 'string',