@depup/eslint-plugin-jsdoc 64.0.2-depup.1 → 64.2.0-depup.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.
package/README.md CHANGED
@@ -13,16 +13,10 @@ npm install @depup/eslint-plugin-jsdoc
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [eslint-plugin-jsdoc](https://www.npmjs.com/package/eslint-plugin-jsdoc) @ 64.0.2 |
17
- | Processed | 2026-08-10 |
16
+ | Original | [eslint-plugin-jsdoc](https://www.npmjs.com/package/eslint-plugin-jsdoc) @ 64.2.0 |
17
+ | Processed | 2026-08-15 |
18
18
  | Smoke test | passed |
19
- | Deps updated | 1 |
20
-
21
- ## Dependency Changes
22
-
23
- | Dependency | From | To |
24
- |------------|------|-----|
25
- | @es-joy/jsdoccomment | ~0.93.2 | ^0.94.0 |
19
+ | Deps updated | 0 |
26
20
 
27
21
  ---
28
22
 
package/changes.json CHANGED
@@ -1,10 +1,5 @@
1
1
  {
2
- "bumped": {
3
- "@es-joy/jsdoccomment": {
4
- "from": "~0.93.2",
5
- "to": "^0.94.0"
6
- }
7
- },
8
- "timestamp": "2026-08-10T00:23:14.163Z",
9
- "totalUpdated": 1
2
+ "bumped": {},
3
+ "timestamp": "2026-08-15T08:06:56.049Z",
4
+ "totalUpdated": 0
10
5
  }
@@ -52,12 +52,16 @@ export type BasicUtils = {
52
52
  export type IsIteratingFunction = () => boolean;
53
53
  export type IsVirtualFunction = () => boolean;
54
54
  export type Stringify = (tagBlock: import("comment-parser").Block, specRewire?: boolean | undefined) => string;
55
+ export type JsdocSuggestions = {
56
+ desc: string;
57
+ handler: (fixer: import("eslint").Rule.RuleFixer) => import("eslint").Rule.Fix | void;
58
+ }[];
55
59
  export type ReportJSDoc = (msg: string, tag?: import("comment-parser").Spec | {
56
60
  line: Integer;
57
61
  column?: Integer;
58
62
  } | null | undefined, handler?: ((fixer: import("eslint").Rule.RuleFixer) => import("eslint").Rule.Fix | void) | null | undefined, specRewire?: boolean | undefined, data?: undefined | {
59
63
  [key: string]: string;
60
- }) => any;
64
+ }, suggestions?: JsdocSuggestions | undefined) => any;
61
65
  export type GetRegexFromString = (str: string, requiredFlags?: string | undefined) => RegExp;
62
66
  export type GetTagDescription = (tg: import("comment-parser").Spec, returnArray?: boolean | undefined) => string[] | string;
63
67
  export type SetTagDescription = (tg: import("comment-parser").Spec, matcher: RegExp, setter: (description: string) => string) => Integer;
@@ -256,7 +260,7 @@ export type Report = (message: string, fix?: import("@eslint/core").RuleFixer |
256
260
  line?: Integer;
257
261
  }) | null | undefined, data?: undefined | {
258
262
  [key: string]: string;
259
- }) => void;
263
+ }, suggest?: import("eslint").Rule.SuggestionReportDescriptor[] | undefined) => void;
260
264
  export type PreferredTypes = {
261
265
  [key: string]: false | string | {
262
266
  message: string;
package/dist/rules.d.ts CHANGED
@@ -143,6 +143,16 @@ export interface Rules {
143
143
  * `@param`'s and present function parameters will still be reported.
144
144
  */
145
145
  allowExtraTrailingParamDocs?: boolean;
146
+ /**
147
+ * Whether to offer a suggestion to rename a mismatched `@param` to the
148
+ * corresponding function parameter name. Defaults to `false`.
149
+ */
150
+ badParamNames?: boolean;
151
+ /**
152
+ * Whether to report `@param` definitions whose names match the function
153
+ * parameters but appear in a different order. Defaults to `true`.
154
+ */
155
+ badParamOrder?: boolean;
146
156
  /**
147
157
  * Whether to check destructured properties. Defaults to `true`.
148
158
  */
@@ -206,14 +216,23 @@ export interface Rules {
206
216
  */
207
217
  disableMissingParamChecks?: boolean;
208
218
  /**
209
- * Set to `true` to auto-remove `@param` duplicates (based on identical
210
- * names).
219
+ * Whether to report duplicate `@param` definitions. Defaults to `true`.
220
+ */
221
+ duplicateParams?: boolean;
222
+ /**
223
+ * Set to `true` to reorder non-nested `@param` definitions to match the
224
+ * function signature and to auto-remove duplicates (based on identical names).
211
225
  *
212
226
  * Note that this option will remove duplicates of the same name even if
213
227
  * the definitions do not match in other ways (e.g., the second param will
214
228
  * be removed even if it has a different type or description).
215
229
  */
216
230
  enableFixer?: boolean;
231
+ /**
232
+ * Whether to offer a suggestion to remove an `@param` that has no
233
+ * corresponding function parameter. Defaults to `false`.
234
+ */
235
+ extraParams?: boolean;
217
236
  /**
218
237
  * Set to `true` if you wish to avoid reporting of child property documentation
219
238
  * where instead of destructuring, a whole plain object is supplied as default
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "http://gajus.com"
6
6
  },
7
7
  "dependencies": {
8
- "@es-joy/jsdoccomment": "^0.94.0",
8
+ "@es-joy/jsdoccomment": "~0.95.0",
9
9
  "@es-joy/resolve.exports": "1.2.0",
10
10
  "are-docs-informative": "^0.1.1",
11
11
  "comment-parser": "1.4.8",
@@ -43,7 +43,7 @@
43
43
  "@types/node": "^26.2.0",
44
44
  "@types/semver": "^7.8.0",
45
45
  "@types/spdx-expression-parse": "^4.0.0",
46
- "@typescript-eslint/types": "8.66.0",
46
+ "@typescript-eslint/types": "8.67.0",
47
47
  "babel-plugin-add-module-exports": "^1.0.4",
48
48
  "babel-plugin-transform-import-meta": "^3.0.0",
49
49
  "c8": "^12.0.0",
@@ -56,7 +56,7 @@
56
56
  "glob": "^13.0.6",
57
57
  "globals": "^17.9.0",
58
58
  "husky": "^9.1.7",
59
- "jsdoc-type-pratt-parser": "^9.0.1",
59
+ "jsdoc-type-pratt-parser": "^9.1.1",
60
60
  "json-schema": "^0.4.0",
61
61
  "json-schema-to-typescript": "^15.0.4",
62
62
  "lint-staged": "^17.3.0",
@@ -69,7 +69,7 @@
69
69
  "sinon": "^22.1.0",
70
70
  "ts-api-utils": "^2.5.0",
71
71
  "typescript": "5.9.3",
72
- "typescript-eslint": "8.66.0"
72
+ "typescript-eslint": "8.67.0"
73
73
  },
74
74
  "engines": {
75
75
  "node": "^22.22.2 || >=24.15.0"
@@ -160,18 +160,13 @@
160
160
  "test-cov": "TIMING=1 c8 --reporter text pnpm run test-no-cov",
161
161
  "test-index": "pnpm run test-no-cov test/rules/index.js"
162
162
  },
163
- "version": "64.0.2-depup.1",
163
+ "version": "64.2.0-depup.0",
164
164
  "depup": {
165
- "changes": {
166
- "@es-joy/jsdoccomment": {
167
- "from": "~0.93.2",
168
- "to": "^0.94.0"
169
- }
170
- },
171
- "depsUpdated": 1,
165
+ "changes": {},
166
+ "depsUpdated": 0,
172
167
  "originalPackage": "eslint-plugin-jsdoc",
173
- "originalVersion": "64.0.2",
174
- "processedAt": "2026-08-10T00:23:37.482Z",
168
+ "originalVersion": "64.2.0",
169
+ "processedAt": "2026-08-15T08:07:17.964Z",
175
170
  "smokeTest": "passed"
176
171
  }
177
172
  }
@@ -117,6 +117,13 @@ import esquery from 'esquery';
117
117
  * @returns {string}
118
118
  */
119
119
 
120
+ /**
121
+ * @typedef {{
122
+ * desc: string,
123
+ * handler: (fixer: import('eslint').Rule.RuleFixer) => import('eslint').Rule.Fix|void
124
+ * }[]} JsdocSuggestions
125
+ */
126
+
120
127
  /**
121
128
  * @callback ReportJSDoc
122
129
  * @param {string} msg
@@ -126,6 +133,7 @@ import esquery from 'esquery';
126
133
  * @param {undefined|{
127
134
  * [key: string]: string
128
135
  * }} [data]
136
+ * @param {JsdocSuggestions} [suggestions]
129
137
  */
130
138
 
131
139
  /**
@@ -698,6 +706,7 @@ const getBasicUtils = (context, {
698
706
  * @param {undefined|{
699
707
  * [key: string]: string
700
708
  * }} [data]
709
+ * @param {import('eslint').Rule.SuggestionReportDescriptor[]} [suggest]
701
710
  * @returns {void}
702
711
  */
703
712
 
@@ -808,24 +817,42 @@ const getUtils = (
808
817
  };
809
818
 
810
819
  /** @type {ReportJSDoc} */
811
- utils.reportJSDoc = (msg, tag, handler, specRewire, data) => {
812
- report(msg, handler ? /** @type {import('eslint').Rule.ReportFixer} */ (
813
- fixer,
814
- ) => {
815
- const extraFix = handler(fixer);
816
-
817
- const replacement = utils.stringify(jsdoc, specRewire);
818
-
819
- if (!replacement) {
820
- const text = sourceCode.getText();
821
- const lastLineBreakPos = text.slice(
822
- 0, jsdocNode.range[0],
823
- ).search(/\n[ \t]*$/v);
824
- if (lastLineBreakPos > -1) {
820
+ utils.reportJSDoc = (msg, tag, handler, specRewire, data, suggestions) => {
821
+ /**
822
+ * @param {(fixer: import('eslint').Rule.RuleFixer) => import('eslint').Rule.Fix|void} fixHandler
823
+ * @returns {import('eslint').Rule.ReportFixer}
824
+ */
825
+ const makeFix = (fixHandler) => {
826
+ return (fixer) => {
827
+ const extraFix = fixHandler(fixer);
828
+
829
+ const replacement = utils.stringify(jsdoc, specRewire);
830
+
831
+ if (!replacement) {
832
+ const text = sourceCode.getText();
833
+ const lastLineBreakPos = text.slice(
834
+ 0, jsdocNode.range[0],
835
+ ).search(/\n[ \t]*$/v);
836
+ if (lastLineBreakPos > -1) {
837
+ return [
838
+ fixer.removeRange([
839
+ lastLineBreakPos, jsdocNode.range[1],
840
+ ]),
841
+ /* c8 ignore next 2 -- Guard */
842
+ ...(extraFix ? [
843
+ extraFix,
844
+ ] : []),
845
+ ];
846
+ }
847
+
825
848
  return [
826
- fixer.removeRange([
827
- lastLineBreakPos, jsdocNode.range[1],
828
- ]),
849
+ fixer.removeRange(
850
+ (/\s/v).test(text.charAt(jsdocNode.range[1])) ?
851
+ [
852
+ jsdocNode.range[0], jsdocNode.range[1] + 1,
853
+ ] :
854
+ jsdocNode.range,
855
+ ),
829
856
  /* c8 ignore next 2 -- Guard */
830
857
  ...(extraFix ? [
831
858
  extraFix,
@@ -834,27 +861,29 @@ const getUtils = (
834
861
  }
835
862
 
836
863
  return [
837
- fixer.removeRange(
838
- (/\s/v).test(text.charAt(jsdocNode.range[1])) ?
839
- [
840
- jsdocNode.range[0], jsdocNode.range[1] + 1,
841
- ] :
842
- jsdocNode.range,
843
- ),
844
- /* c8 ignore next 2 -- Guard */
864
+ fixer.replaceText(jsdocNode, replacement),
845
865
  ...(extraFix ? [
846
866
  extraFix,
847
867
  ] : []),
848
868
  ];
849
- }
869
+ };
870
+ };
850
871
 
851
- return [
852
- fixer.replaceText(jsdocNode, replacement),
853
- ...(extraFix ? [
854
- extraFix,
855
- ] : []),
856
- ];
857
- } : null, tag, data);
872
+ report(
873
+ msg,
874
+ handler ? makeFix(handler) : null,
875
+ tag,
876
+ data,
877
+ suggestions?.map(({
878
+ desc,
879
+ handler: suggestionHandler,
880
+ }) => {
881
+ return {
882
+ desc,
883
+ fix: makeFix(suggestionHandler),
884
+ };
885
+ }),
886
+ );
858
887
  };
859
888
 
860
889
  /** @type {GetRegexFromString} */
@@ -1911,7 +1940,9 @@ const getSettings = (context) => {
1911
1940
  /** @type {MakeReport} */
1912
1941
  const makeReport = (context, commentNode) => {
1913
1942
  /** @type {Report} */
1914
- const report = (message, fix = null, jsdocLoc = null, data = undefined) => {
1943
+ const report = (
1944
+ message, fix = null, jsdocLoc = null, data = undefined, suggest = undefined,
1945
+ ) => {
1915
1946
  let loc;
1916
1947
 
1917
1948
  if (jsdocLoc) {
@@ -1953,6 +1984,7 @@ const makeReport = (context, commentNode) => {
1953
1984
  loc,
1954
1985
  message,
1955
1986
  node: commentNode,
1987
+ suggest,
1956
1988
  });
1957
1989
  };
1958
1990
 
@@ -1,14 +1,107 @@
1
1
  import iterateJsdoc from '../iterateJsdoc.js';
2
2
 
3
+ /**
4
+ * @param {import('../jsdocUtils.js').ParamNameInfo} parameter
5
+ * @returns {string|undefined}
6
+ */
7
+ const getSimpleParameterName = (parameter) => {
8
+ if (typeof parameter === 'string') {
9
+ return parameter;
10
+ }
11
+
12
+ if (
13
+ !parameter ||
14
+ Array.isArray(parameter) ||
15
+ typeof parameter !== 'object' ||
16
+ !('name' in parameter) ||
17
+ typeof parameter.name !== 'string'
18
+ ) {
19
+ return undefined;
20
+ }
21
+
22
+ return parameter.name;
23
+ };
24
+
25
+ /**
26
+ * @param {import('../iterateJsdoc.js').Integer} firstChangedTagIndex
27
+ * @param {import('comment-parser').Spec[]} orderedTags
28
+ * @param {import('comment-parser').Block} jsdoc
29
+ * @param {import('../iterateJsdoc.js').Utils} utils
30
+ * @returns {() => void}
31
+ */
32
+ const makeParamOrderFix = (
33
+ firstChangedTagIndex, orderedTags, jsdoc, utils,
34
+ ) => {
35
+ return () => {
36
+ const itemsToMoveRange = [
37
+ ...Array.from({
38
+ length: jsdoc.tags.length - firstChangedTagIndex,
39
+ }).keys(),
40
+ ];
41
+
42
+ const unchangedPriorTagDescriptions = jsdoc.tags.slice(
43
+ 0,
44
+ firstChangedTagIndex,
45
+ ).reduce((count, {
46
+ source,
47
+ }) => {
48
+ return count + source.length - 1;
49
+ }, 0);
50
+
51
+ const initialOffset =
52
+ /** @type {import('../iterateJsdoc.js').Integer} */ (utils.getFirstLine()) +
53
+ firstChangedTagIndex + unchangedPriorTagDescriptions;
54
+
55
+ for (const index of itemsToMoveRange) {
56
+ utils.removeTag(index + firstChangedTagIndex);
57
+ }
58
+
59
+ const changedTags = orderedTags.slice(firstChangedTagIndex);
60
+ let extraTagCount = 0;
61
+
62
+ for (const index of itemsToMoveRange) {
63
+ const changedTag = changedTags[index];
64
+
65
+ utils.addTag(
66
+ changedTag.tag,
67
+ extraTagCount + initialOffset + index,
68
+ {
69
+ ...changedTag.source[0].tokens,
70
+ end: '',
71
+ },
72
+ );
73
+
74
+ for (const {
75
+ tokens,
76
+ } of changedTag.source.slice(1)) {
77
+ if (!tokens.end) {
78
+ utils.addLine(
79
+ extraTagCount + initialOffset + index + 1,
80
+ {
81
+ ...tokens,
82
+ end: '',
83
+ },
84
+ );
85
+ extraTagCount++;
86
+ }
87
+ }
88
+ }
89
+ };
90
+ };
91
+
3
92
  /**
4
93
  * @param {string} targetTagName
5
94
  * @param {boolean} allowExtraTrailingParamDocs
95
+ * @param {boolean} badParamNames
96
+ * @param {boolean} badParamOrder
6
97
  * @param {boolean} checkDestructured
7
98
  * @param {boolean} checkRestProperty
8
99
  * @param {RegExp} checkTypesRegex
9
100
  * @param {boolean} disableExtraPropertyReporting
10
101
  * @param {boolean} disableMissingParamChecks
102
+ * @param {boolean} duplicateParams
11
103
  * @param {boolean} enableFixer
104
+ * @param {boolean} extraParams
12
105
  * @param {import('../jsdocUtils.js').ParamNameInfo[]} functionParameterNames
13
106
  * @param {import('comment-parser').Block} jsdoc
14
107
  * @param {import('../iterateJsdoc.js').Utils} utils
@@ -18,12 +111,16 @@ import iterateJsdoc from '../iterateJsdoc.js';
18
111
  const validateParameterNames = (
19
112
  targetTagName,
20
113
  allowExtraTrailingParamDocs,
114
+ badParamNames,
115
+ badParamOrder,
21
116
  checkDestructured,
22
117
  checkRestProperty,
23
118
  checkTypesRegex,
24
119
  disableExtraPropertyReporting,
25
120
  disableMissingParamChecks,
121
+ duplicateParams,
26
122
  enableFixer,
123
+ extraParams,
27
124
  functionParameterNames, jsdoc, utils, report,
28
125
  ) => {
29
126
  const paramTags = Object.entries(jsdoc.tags).filter(([
@@ -36,12 +133,39 @@ const validateParameterNames = (
36
133
  ]) => {
37
134
  return !tag.name.includes('.');
38
135
  });
136
+ const actualParamNames = paramTagsNonNested.map(([
137
+ , tag,
138
+ ]) => {
139
+ return tag.name.trim();
140
+ });
141
+ const documentsThis = actualParamNames.includes('this');
142
+ const simpleFunctionParameterNames = functionParameterNames.filter((parameter) => {
143
+ return documentsThis || parameter !== 'this';
144
+ }).map(getSimpleParameterName);
145
+ const hasOnlySimpleParameterNames = simpleFunctionParameterNames.every((name) => {
146
+ return name !== undefined;
147
+ });
148
+ const expectedParamNames = /** @type {string[]} */ (simpleFunctionParameterNames);
149
+ const sortedActualParamNames = actualParamNames.toSorted();
150
+ const sortedExpectedParamNames = expectedParamNames.toSorted();
151
+ const sameParamNames = hasOnlySimpleParameterNames &&
152
+ actualParamNames.length === expectedParamNames.length &&
153
+ sortedActualParamNames.every((name, index) => {
154
+ return name === sortedExpectedParamNames[index];
155
+ });
156
+ const isBadParamOrder = sameParamNames && actualParamNames.some((name, index) => {
157
+ return name !== expectedParamNames[index];
158
+ });
159
+ const canFixParamOrder =
160
+ paramTags.length === paramTagsNonNested.length &&
161
+ new Set(actualParamNames).size === actualParamNames.length;
39
162
 
40
163
  let dotted = 0;
41
164
  let thisOffset = 0;
42
165
 
43
166
  return paramTags.some(([
44
- , tag,
167
+ ,
168
+ tag,
45
169
  // eslint-disable-next-line complexity
46
170
  ], index) => {
47
171
  /** @type {import('../iterateJsdoc.js').Integer} */
@@ -54,7 +178,7 @@ const validateParameterNames = (
54
178
 
55
179
  return tg.name === tag.name && idx !== index;
56
180
  });
57
- if (dupeTagInfo) {
181
+ if (dupeTagInfo && duplicateParams) {
58
182
  utils.reportJSDoc(`Duplicate @${targetTagName} "${tag.name}"`, dupeTagInfo[1], enableFixer ? () => {
59
183
  utils.removeTag(tagsIndex);
60
184
  } : null);
@@ -62,6 +186,14 @@ const validateParameterNames = (
62
186
  return true;
63
187
  }
64
188
 
189
+ if (!duplicateParams && paramTags.some(([
190
+ , earlierTag,
191
+ ], earlierIndex) => {
192
+ return earlierIndex < index && earlierTag.name === tag.name;
193
+ })) {
194
+ return false;
195
+ }
196
+
65
197
  if (tag.name.includes('.')) {
66
198
  dotted++;
67
199
 
@@ -69,7 +201,7 @@ const validateParameterNames = (
69
201
  }
70
202
 
71
203
  let functionParameterName = functionParameterNames[index - dotted + thisOffset];
72
- if (functionParameterName === 'this' && tag.name.trim() !== 'this') {
204
+ if (!documentsThis && functionParameterName === 'this' && tag.name.trim() !== 'this') {
73
205
  ++thisOffset;
74
206
  functionParameterName = functionParameterNames[index - dotted + thisOffset];
75
207
  }
@@ -79,10 +211,25 @@ const validateParameterNames = (
79
211
  return false;
80
212
  }
81
213
 
82
- report(
214
+ const removeExtraParam = () => {
215
+ const currentTagIndex = jsdoc.tags.indexOf(tag);
216
+ if (currentTagIndex !== -1) {
217
+ utils.removeTag(currentTagIndex);
218
+ }
219
+ };
220
+
221
+ utils.reportJSDoc(
83
222
  `@${targetTagName} "${tag.name}" does not match an existing function parameter.`,
84
- null,
85
223
  tag,
224
+ extraParams ? removeExtraParam : null,
225
+ false,
226
+ undefined,
227
+ [
228
+ {
229
+ desc: `Remove the extra @${targetTagName} "${tag.name}".`,
230
+ handler: removeExtraParam,
231
+ },
232
+ ],
86
233
  );
87
234
 
88
235
  return true;
@@ -267,8 +414,59 @@ const validateParameterNames = (
267
414
 
268
415
  return item;
269
416
  }).filter((item) => {
270
- return item !== 'this';
417
+ return documentsThis || item !== 'this';
271
418
  });
419
+ const message = `Expected @${targetTagName} names to be "${
420
+ expectedNames.map((expectedName) => {
421
+ return typeof expectedName === 'object' &&
422
+ 'name' in expectedName &&
423
+ expectedName.restElement ?
424
+ '...' + expectedName.name :
425
+ expectedName;
426
+ }).join(', ')
427
+ }". Got "${actualNames.join(', ')}".`;
428
+
429
+ if (isBadParamOrder) {
430
+ if (!badParamOrder) {
431
+ return false;
432
+ }
433
+
434
+ let orderFix = null;
435
+ if (enableFixer && canFixParamOrder) {
436
+ const orderedTags =
437
+ /** @type {import('comment-parser').Spec[]} */ (
438
+ JSON.parse(JSON.stringify(jsdoc.tags))
439
+ );
440
+ for (const [
441
+ paramIndex,
442
+ expectedName,
443
+ ] of expectedParamNames.entries()) {
444
+ const matchingTag = /** @type {[string, import('comment-parser').Spec]} */ (
445
+ paramTagsNonNested.find(([
446
+ , candidateTag,
447
+ ]) => {
448
+ return candidateTag.name.trim() === expectedName;
449
+ })
450
+ );
451
+ orderedTags[Number(paramTagsNonNested[paramIndex][0])] =
452
+ JSON.parse(JSON.stringify(matchingTag[1]));
453
+ }
454
+
455
+ const firstChangedParamIndex = actualParamNames.findIndex((name, paramIndex) => {
456
+ return name !== expectedParamNames[paramIndex];
457
+ });
458
+ orderFix = makeParamOrderFix(
459
+ Number(paramTagsNonNested[firstChangedParamIndex][0]),
460
+ orderedTags,
461
+ jsdoc,
462
+ utils,
463
+ );
464
+ }
465
+
466
+ utils.reportJSDoc(message, tag, orderFix, true);
467
+
468
+ return true;
469
+ }
272
470
 
273
471
  // When disableMissingParamChecks is true tag names can be omitted.
274
472
  // Report when the tag names do not match the expected names or they are used out of order.
@@ -286,18 +484,22 @@ const validateParameterNames = (
286
484
  }
287
485
  }
288
486
 
289
- report(
290
- `Expected @${targetTagName} names to be "${
291
- expectedNames.map((expectedName) => {
292
- return typeof expectedName === 'object' &&
293
- 'name' in expectedName &&
294
- expectedName.restElement ?
295
- '...' + expectedName.name :
296
- expectedName;
297
- }).join(', ')
298
- }". Got "${actualNames.join(', ')}".`,
299
- null,
487
+ const renameBadParam = () => {
488
+ tag.source[0].tokens.name = String(funcParamName);
489
+ };
490
+
491
+ utils.reportJSDoc(
492
+ message,
300
493
  tag,
494
+ badParamNames ? renameBadParam : null,
495
+ true,
496
+ undefined,
497
+ [
498
+ {
499
+ desc: `Rename @${targetTagName} "${tag.name.trim()}" to "${funcParamName}".`,
500
+ handler: renameBadParam,
501
+ },
502
+ ],
301
503
  );
302
504
 
303
505
  return true;
@@ -377,12 +579,16 @@ export default iterateJsdoc(({
377
579
  }) => {
378
580
  const {
379
581
  allowExtraTrailingParamDocs,
582
+ badParamNames = false,
583
+ badParamOrder = true,
380
584
  checkDestructured = true,
381
585
  checkRestProperty = false,
382
586
  checkTypesPattern = '/^(?:[oO]bject|[aA]rray|PlainObject|Generic(?:Object|Array))$/',
383
587
  disableExtraPropertyReporting = false,
384
588
  disableMissingParamChecks = false,
589
+ duplicateParams = true,
385
590
  enableFixer = false,
591
+ extraParams = false,
386
592
  useDefaultObjectProperties = false,
387
593
  } = context.options[0] || {};
388
594
 
@@ -416,12 +622,16 @@ export default iterateJsdoc(({
416
622
  const isError = validateParameterNames(
417
623
  targetTagName,
418
624
  allowExtraTrailingParamDocs,
625
+ badParamNames,
626
+ badParamOrder,
419
627
  checkDestructured,
420
628
  checkRestProperty,
421
629
  checkTypesRegex,
422
630
  disableExtraPropertyReporting,
423
631
  disableMissingParamChecks,
632
+ duplicateParams,
424
633
  enableFixer,
634
+ extraParams,
425
635
  functionParameterNames,
426
636
  jsdoc,
427
637
  utils,
@@ -443,6 +653,7 @@ export default iterateJsdoc(({
443
653
  url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-param-names.md#repos-sticky-header',
444
654
  },
445
655
  fixable: 'code',
656
+ hasSuggestions: true,
446
657
  schema: [
447
658
  {
448
659
  additionalProperties: false,
@@ -454,6 +665,16 @@ their presence within the function signature. Other inconsistencies between
454
665
  \`@param\`'s and present function parameters will still be reported.`,
455
666
  type: 'boolean',
456
667
  },
668
+ badParamNames: {
669
+ description: `Whether to auto-fix a mismatched \`@param\` name to the corresponding
670
+ function parameter name. A suggestion is always offered. Defaults to \`false\`.`,
671
+ type: 'boolean',
672
+ },
673
+ badParamOrder: {
674
+ description: `Whether to report \`@param\` definitions whose names match the function
675
+ parameters but appear in a different order. Defaults to \`true\`.`,
676
+ type: 'boolean',
677
+ },
457
678
  checkDestructured: {
458
679
  description: 'Whether to check destructured properties. Defaults to `true`.',
459
680
  type: 'boolean',
@@ -516,15 +737,24 @@ that are available and actually used in the function.`,
516
737
  description: 'Whether to avoid checks for missing `@param` definitions. Defaults to `false`. Change to `true` if you want to be able to omit properties.',
517
738
  type: 'boolean',
518
739
  },
740
+ duplicateParams: {
741
+ description: 'Whether to report duplicate `@param` definitions. Defaults to `true`.',
742
+ type: 'boolean',
743
+ },
519
744
  enableFixer: {
520
- description: `Set to \`true\` to auto-remove \`@param\` duplicates (based on identical
521
- names).
745
+ description: `Set to \`true\` to reorder non-nested \`@param\` definitions to match the
746
+ function signature and to auto-remove duplicates (based on identical names).
522
747
 
523
748
  Note that this option will remove duplicates of the same name even if
524
749
  the definitions do not match in other ways (e.g., the second param will
525
750
  be removed even if it has a different type or description).`,
526
751
  type: 'boolean',
527
752
  },
753
+ extraParams: {
754
+ description: `Whether to auto-remove an \`@param\` that has no corresponding function
755
+ parameter. A suggestion is always offered. Defaults to \`false\`.`,
756
+ type: 'boolean',
757
+ },
528
758
  useDefaultObjectProperties: {
529
759
  description: `Set to \`true\` if you wish to avoid reporting of child property documentation
530
760
  where instead of destructuring, a whole plain object is supplied as default
package/src/rules.d.ts CHANGED
@@ -143,6 +143,16 @@ export interface Rules {
143
143
  * `@param`'s and present function parameters will still be reported.
144
144
  */
145
145
  allowExtraTrailingParamDocs?: boolean;
146
+ /**
147
+ * Whether to offer a suggestion to rename a mismatched `@param` to the
148
+ * corresponding function parameter name. Defaults to `false`.
149
+ */
150
+ badParamNames?: boolean;
151
+ /**
152
+ * Whether to report `@param` definitions whose names match the function
153
+ * parameters but appear in a different order. Defaults to `true`.
154
+ */
155
+ badParamOrder?: boolean;
146
156
  /**
147
157
  * Whether to check destructured properties. Defaults to `true`.
148
158
  */
@@ -206,14 +216,23 @@ export interface Rules {
206
216
  */
207
217
  disableMissingParamChecks?: boolean;
208
218
  /**
209
- * Set to `true` to auto-remove `@param` duplicates (based on identical
210
- * names).
219
+ * Whether to report duplicate `@param` definitions. Defaults to `true`.
220
+ */
221
+ duplicateParams?: boolean;
222
+ /**
223
+ * Set to `true` to reorder non-nested `@param` definitions to match the
224
+ * function signature and to auto-remove duplicates (based on identical names).
211
225
  *
212
226
  * Note that this option will remove duplicates of the same name even if
213
227
  * the definitions do not match in other ways (e.g., the second param will
214
228
  * be removed even if it has a different type or description).
215
229
  */
216
230
  enableFixer?: boolean;
231
+ /**
232
+ * Whether to offer a suggestion to remove an `@param` that has no
233
+ * corresponding function parameter. Defaults to `false`.
234
+ */
235
+ extraParams?: boolean;
217
236
  /**
218
237
  * Set to `true` if you wish to avoid reporting of child property documentation
219
238
  * where instead of destructuring, a whole plain object is supplied as default