eslint-plugin-jsdoc 64.2.1 → 64.3.1

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
@@ -512,6 +512,7 @@ non-default-recommended fixer).
512
512
  ||| [no-restricted-syntax](./docs/rules/no-restricted-syntax.md#readme) | Reports when certain comment structures are present. |
513
513
  |On in TS; Off in TS flavor|:wrench:| [no-types](./docs/rules/no-types.md#readme) | This rule reports types being used on `@param` or `@returns` (redundant with TypeScript). |
514
514
  |:heavy_check_mark: (Off in TS; Off in TS flavor)|| [no-undefined-types](./docs/rules/no-undefined-types.md#readme) | Besides some expected built-in types, prohibits any types not specified as globals or within `@typedef`. |
515
+ ||| [no-unnecessary-type-assertion](./docs/rules/no-unnecessary-type-assertion.md#readme) | Reports redundant @type tags that match or broaden the naturally inferred TypeScript type. |
515
516
  ||:wrench:| [normalize-see-links](./docs/rules/normalize-see-links.md#readme) | Normalizes labeled links in `@see` tags to a canonical `{@link}` form. |
516
517
  ||:wrench:| [prefer-import-tag](./docs/rules/prefer-import-tag.md#readme) | Prefer `@import` tags to inline `import()` statements. |
517
518
  |:heavy_check_mark:|| [reject-any-type](./docs/rules/reject-any-type.md#readme) | Reports use of `any` or `*` type |
@@ -0,0 +1,3 @@
1
+ declare const _default: import("eslint").Rule.RuleModule;
2
+ export default _default;
3
+ //# sourceMappingURL=noUnnecessaryTypeAssertion.d.ts.map
package/dist/rules.d.ts CHANGED
@@ -144,8 +144,8 @@ export interface Rules {
144
144
  */
145
145
  allowExtraTrailingParamDocs?: boolean;
146
146
  /**
147
- * Whether to offer a suggestion to rename a mismatched `@param` to the
148
- * corresponding function parameter name. Defaults to `false`.
147
+ * Whether to auto-fix a mismatched `@param` name to the corresponding
148
+ * function parameter name. A suggestion is always offered. Defaults to `false`.
149
149
  */
150
150
  badParamNames?: boolean;
151
151
  /**
@@ -229,8 +229,8 @@ export interface Rules {
229
229
  */
230
230
  enableFixer?: boolean;
231
231
  /**
232
- * Whether to offer a suggestion to remove an `@param` that has no
233
- * corresponding function parameter. Defaults to `false`.
232
+ * Whether to auto-remove an `@param` that has no corresponding function
233
+ * parameter. A suggestion is always offered. Defaults to `false`.
234
234
  */
235
235
  extraParams?: boolean;
236
236
  /**
@@ -1311,6 +1311,26 @@ export interface Rules {
1311
1311
  }
1312
1312
  ];
1313
1313
 
1314
+ /** Reports redundant @type tags that match or broaden the naturally inferred TypeScript type. */
1315
+ "jsdoc/no-unnecessary-type-assertion":
1316
+ | []
1317
+ | [
1318
+ {
1319
+ /**
1320
+ * Whether to check `const` type assertions as redundant
1321
+ */
1322
+ checkLiteralConstAssertions?: boolean;
1323
+ /**
1324
+ * Whether to treat `any` type casts as redundant
1325
+ */
1326
+ treatAnyAsRedundant?: boolean;
1327
+ /**
1328
+ * An array list of types to ignore
1329
+ */
1330
+ typesToIgnore?: string[];
1331
+ }
1332
+ ];
1333
+
1314
1334
  /** Normalizes labeled links in `@see` tags to a canonical `{@link}` form. */
1315
1335
  "jsdoc/normalize-see-links":
1316
1336
  | []
package/package.json CHANGED
@@ -7,6 +7,7 @@
7
7
  "dependencies": {
8
8
  "@es-joy/jsdoccomment": "~0.95.1",
9
9
  "@es-joy/resolve.exports": "1.2.0",
10
+ "@typescript-eslint/utils": "^8.68.0",
10
11
  "are-docs-informative": "^0.1.1",
11
12
  "comment-parser": "1.4.8",
12
13
  "debug": "^4.4.3",
@@ -155,5 +156,5 @@
155
156
  "test-cov": "TIMING=1 c8 --reporter text pnpm run test-no-cov",
156
157
  "test-index": "pnpm run test-no-cov test/rules/index.js"
157
158
  },
158
- "version": "64.2.1"
159
+ "version": "64.3.1"
159
160
  }
package/src/index.js CHANGED
@@ -39,6 +39,7 @@ import noRestrictedSyntax from './rules/noRestrictedSyntax.js';
39
39
  import normalizeSeeLinks from './rules/normalizeSeeLinks.js';
40
40
  import noTypes from './rules/noTypes.js';
41
41
  import noUndefinedTypes from './rules/noUndefinedTypes.js';
42
+ import noUnnecessaryTypeAssertion from './rules/noUnnecessaryTypeAssertion.js';
42
43
  import preferImportTag from './rules/preferImportTag.js';
43
44
  import requireAsteriskPrefix from './rules/requireAsteriskPrefix.js';
44
45
  import requireDescription from './rules/requireDescription.js';
@@ -134,6 +135,7 @@ index.rules = {
134
135
  'no-restricted-syntax': noRestrictedSyntax,
135
136
  'no-types': noTypes,
136
137
  'no-undefined-types': noUndefinedTypes,
138
+ 'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion,
137
139
  'normalize-see-links': normalizeSeeLinks,
138
140
  'prefer-import-tag': preferImportTag,
139
141
  'reject-any-type': buildRejectOrPreferRuleDefinition({
@@ -325,6 +327,7 @@ const createRecommendedRuleset = (warnOrError, flatName) => {
325
327
  'jsdoc/no-restricted-syntax': 'off',
326
328
  'jsdoc/no-types': 'off',
327
329
  'jsdoc/no-undefined-types': warnOrError,
330
+ 'jsdoc/no-unnecessary-type-assertion': 'off',
328
331
  'jsdoc/normalize-see-links': 'off',
329
332
  'jsdoc/prefer-import-tag': 'off',
330
333
  'jsdoc/reject-any-type': warnOrError,
@@ -0,0 +1,232 @@
1
+ /* eslint-disable jsdoc/reject-any-type -- TypeScript types need not be installed */
2
+ import iterateJsdoc from '../iterateJsdoc.js';
3
+ import {
4
+ createRequire,
5
+ } from 'module';
6
+
7
+ let warned = false;
8
+
9
+ /** @type {any} */
10
+ let ts;
11
+
12
+ // 1. Create a require function bound to the current file's URL
13
+ const require = createRequire(import.meta.url);
14
+
15
+ try {
16
+ // 2. Attempt to import the package synchronously
17
+ ts = require('typescript');
18
+ /* c8 ignore next 10 -- Guard */
19
+ } catch (error) {
20
+ // 3. Fall back gracefully if it is not installed
21
+ if (/** @type {{code?: string}} */ (error).code !== 'MODULE_NOT_FOUND') {
22
+ // Re-throw if it's a different error (e.g., syntax error inside the package)
23
+ throw error;
24
+ }
25
+
26
+ // eslint-disable-next-line no-console -- Warning user
27
+ console.warn('⚠️ typescript is not installed. `jsdoc/no-unnecessary-type-assertion` will not work. To disable this warning, you must disable the rule.');
28
+ }
29
+
30
+ // Helper to check for standard literals and boolean/enum/template literals
31
+ /**
32
+ * @param {any} type The type is `ts.Type`
33
+ * @returns {boolean}
34
+ */
35
+ const isLiteralType = (type) => {
36
+ // Standard primitive literals (string, number, bigint, boolean literal flags)
37
+ if (type.isLiteral()) {
38
+ return true;
39
+ }
40
+
41
+ // Check using TypeFlags for specific edge cases like boolean or template literals
42
+ // eslint-disable-next-line no-bitwise -- Convenient
43
+ if (type.flags & (ts.TypeFlags.BooleanLiteral | ts.TypeFlags.TemplateLiteral)) {
44
+ return true;
45
+ }
46
+
47
+ // If dealing with a union (e.g. 'a' | 'b'), you can check if all constituents are literals
48
+ if (type.isUnion()) {
49
+ return type.types.every(
50
+ /**
51
+ * @param {any} typ
52
+ */
53
+ (typ) => {
54
+ return isLiteralType(typ);
55
+ },
56
+ );
57
+ }
58
+
59
+ return false;
60
+ };
61
+
62
+ export default iterateJsdoc(({
63
+ context,
64
+ node: nde,
65
+ report,
66
+ utils,
67
+ }) => {
68
+ /* c8 ignore next 4 -- Guard */
69
+ // Already handled
70
+ if (!ts) {
71
+ return;
72
+ }
73
+
74
+ const types = utils.getTags('type');
75
+
76
+ // 1. Only proceed if there is a @type tag
77
+ if (!types.length) {
78
+ return;
79
+ }
80
+
81
+ const {
82
+ // https://typescript-eslint.io/rules/no-unnecessary-type-assertion/
83
+ checkLiteralConstAssertions = false,
84
+ treatAnyAsRedundant = false,
85
+ typesToIgnore = [],
86
+ } = context.options[0] ?? {};
87
+
88
+ const node =
89
+ /**
90
+ * @type {import('@typescript-eslint/utils').TSESTree.Node}
91
+ */ (nde);
92
+
93
+ // 2. Safely get TypeScript parser services and TypeChecker
94
+ const services =
95
+ /**
96
+ * @type {import('@typescript-eslint/utils').ParserServices}
97
+ */ (context.sourceCode.parserServices);
98
+
99
+ /* c8 ignore next 10 -- Guard */
100
+ if (!services || !services.program) {
101
+ if (!warned) {
102
+ // Cannot proceed without type-aware linting enabled
103
+ // eslint-disable-next-line no-console -- Feedback
104
+ console.warn('⚠️ You must point ESLint to the `typescript-eslint` parser using `languageOptions`. See the documentation for `jsdoc/no-unnecessary-type-assertion`.');
105
+ warned = true;
106
+ }
107
+
108
+ return;
109
+ }
110
+
111
+ // 1. Grab the parser services and the active TypeChecker
112
+ // const services = ESLintUtils.getParserServices(context);
113
+ const checker = services.program.getTypeChecker();
114
+
115
+ /**
116
+ * @param {string} typeStr
117
+ * @returns {any} `ts.Type`
118
+ */
119
+ const getTypeFromString = (typeStr) => {
120
+ // 2. Parse the type string into a virtual TypeScript SourceFile
121
+ const virtualSourceFile = ts.createSourceFile(
122
+ 'inline-type-eval.ts',
123
+ `type __TargetType = ${typeStr};`,
124
+ ts.ScriptTarget.Latest,
125
+ true,
126
+ );
127
+
128
+ // 3. Find the TypeNode in the AST (type __TargetType = <TypeNode>;)
129
+ // Type `ts.TypeAliasDeclaration`
130
+ const typeAlias = /** @type {any} */ (
131
+ virtualSourceFile.statements[0]
132
+ );
133
+ const typeNode = typeAlias.type;
134
+
135
+ // 4. Resolve the type using the TypeChecker
136
+ // Note: If the type string references external symbols or types from your
137
+ // actual codebase, the checker may require a complete program context to resolve them.
138
+ return checker.getTypeFromTypeNode(typeNode);
139
+ };
140
+
141
+ // Todo: Support more than just VariableDeclaration!
142
+
143
+ // 3. For this example, let's assume we are checking VariableDeclarators
144
+ // e.g., `/** @type {number} */ const x = 5;`
145
+ if (node?.type === 'VariableDeclaration') {
146
+ const decl = node.declarations[0];
147
+
148
+ /* c8 ignore next 4 -- How to cover? */
149
+ if (!decl.init) {
150
+ // No initializer, type is likely `any`, so @type isn't redundant
151
+ return;
152
+ }
153
+
154
+ // 4. Map the ESLint AST nodes to TypeScript AST nodes
155
+ // const tsNode = services.esTreeNodeToTSNodeMap.get(decl.id);
156
+ const tsInit = services.esTreeNodeToTSNodeMap.get(decl.init);
157
+
158
+ // 5. Get the types
159
+ // tsNode type includes the JSDoc explicit type
160
+ // tsInit type is the purely inferred type from the right-hand side
161
+ const inferredType = checker.getTypeAtLocation(tsInit);
162
+
163
+ // 6. Compare the types.
164
+ const declaredTypeStr = types[0].type;
165
+ const declaredType = getTypeFromString(declaredTypeStr);
166
+
167
+ const isRedundantType = checker.isTypeAssignableTo(
168
+ inferredType,
169
+ declaredType,
170
+ );
171
+
172
+ if (declaredTypeStr === 'const') {
173
+ if (checkLiteralConstAssertions && isLiteralType(inferredType)) {
174
+ report(
175
+ 'The @type tag declaring "{{ type }}" is redundant as TypeScript infers it automatically for literals.',
176
+ null,
177
+ types[0],
178
+ {
179
+ type: declaredTypeStr,
180
+ },
181
+ );
182
+ }
183
+
184
+ return;
185
+ }
186
+
187
+ if (
188
+ isRedundantType && (treatAnyAsRedundant || declaredTypeStr !== 'any') &&
189
+ !typesToIgnore.includes(declaredTypeStr)
190
+ ) {
191
+ report(
192
+ 'The @type tag declaring "{{ type }}" is redundant as TypeScript infers it automatically.',
193
+ null,
194
+ types[0],
195
+ {
196
+ type: declaredTypeStr,
197
+ },
198
+ );
199
+ }
200
+ }
201
+ }, {
202
+ iterateAllJsdocs: true,
203
+ meta: {
204
+ docs: {
205
+ description: 'Reports redundant @type tags that match or broaden the naturally inferred TypeScript type.',
206
+ },
207
+ schema: [
208
+ {
209
+ additionalProperties: false,
210
+ properties: {
211
+ checkLiteralConstAssertions: {
212
+ description: 'Whether to check `const` type assertions as redundant',
213
+ type: 'boolean',
214
+ },
215
+ treatAnyAsRedundant: {
216
+ description: 'Whether to treat `any` type casts as redundant',
217
+ type: 'boolean',
218
+ },
219
+ typesToIgnore: {
220
+ description: 'An array list of types to ignore',
221
+ items: {
222
+ type: 'string',
223
+ },
224
+ type: 'array',
225
+ },
226
+ },
227
+ type: 'object',
228
+ },
229
+ ],
230
+ type: 'suggestion',
231
+ },
232
+ });
package/src/rules.d.ts CHANGED
@@ -144,8 +144,8 @@ export interface Rules {
144
144
  */
145
145
  allowExtraTrailingParamDocs?: boolean;
146
146
  /**
147
- * Whether to offer a suggestion to rename a mismatched `@param` to the
148
- * corresponding function parameter name. Defaults to `false`.
147
+ * Whether to auto-fix a mismatched `@param` name to the corresponding
148
+ * function parameter name. A suggestion is always offered. Defaults to `false`.
149
149
  */
150
150
  badParamNames?: boolean;
151
151
  /**
@@ -229,8 +229,8 @@ export interface Rules {
229
229
  */
230
230
  enableFixer?: boolean;
231
231
  /**
232
- * Whether to offer a suggestion to remove an `@param` that has no
233
- * corresponding function parameter. Defaults to `false`.
232
+ * Whether to auto-remove an `@param` that has no corresponding function
233
+ * parameter. A suggestion is always offered. Defaults to `false`.
234
234
  */
235
235
  extraParams?: boolean;
236
236
  /**
@@ -1311,6 +1311,26 @@ export interface Rules {
1311
1311
  }
1312
1312
  ];
1313
1313
 
1314
+ /** Reports redundant @type tags that match or broaden the naturally inferred TypeScript type. */
1315
+ "jsdoc/no-unnecessary-type-assertion":
1316
+ | []
1317
+ | [
1318
+ {
1319
+ /**
1320
+ * Whether to check `const` type assertions as redundant
1321
+ */
1322
+ checkLiteralConstAssertions?: boolean;
1323
+ /**
1324
+ * Whether to treat `any` type casts as redundant
1325
+ */
1326
+ treatAnyAsRedundant?: boolean;
1327
+ /**
1328
+ * An array list of types to ignore
1329
+ */
1330
+ typesToIgnore?: string[];
1331
+ }
1332
+ ];
1333
+
1314
1334
  /** Normalizes labeled links in `@see` tags to a canonical `{@link}` form. */
1315
1335
  "jsdoc/normalize-see-links":
1316
1336
  | []