Skip to content

Commit 2aaea71

Browse files
committed
fix(no-unnecessary-type-assertion): add fixer
1 parent b41f175 commit 2aaea71

6 files changed

Lines changed: 204 additions & 8 deletions

File tree

‎.README/rules/no-unnecessary-type-assertion.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ TypeScript infers for the expression.
66

77
Currently only supports `VariableDeclaration`.
88

9+
The fixer removes the redundant `@type` tag, deleting the whole JSDoc block if
10+
nothing else is left in it.
11+
912
**Note that this experimental rule requires that the `typescript` package is installed.
1013
You must also install and point to the `typescript-eslint` parser, targeting your
1114
JavaScript + JSDoc files. Note also that this rule runs fairly slowly.**
@@ -34,6 +37,7 @@ export default [
3437
'jsdoc/no-unnecessary-type-assertion': ['error', {
3538
// You can change these defaults
3639
checkLiteralConstAssertions: false,
40+
enableFixer: true,
3741
treatAnyAsRedundant: false,
3842
typesToIgnore: [],
3943
}]
@@ -51,7 +55,7 @@ export default [
5155
|Context|`VariableDeclaration`|
5256
|Tags|`type`|
5357
|Recommended|false|
54-
|Options|`checkLiteralConstAssertions`, `treatAnyAsRedundant`, `typesToIgnore`|
58+
|Options|`checkLiteralConstAssertions`, `enableFixer`, `treatAnyAsRedundant`, `typesToIgnore`|
5559

5660
## Failing examples
5761

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ non-default-recommended fixer).
512512
||| [no-restricted-syntax](./docs/rules/no-restricted-syntax.md#readme) | Reports when certain comment structures are present. |
513513
|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). |
514514
|: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+
||:wrench:| [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. |
516516
||:wrench:| [normalize-see-links](./docs/rules/normalize-see-links.md#readme) | Normalizes labeled links in `@see` tags to a canonical `{@link}` form. |
517517
||:wrench:| [prefer-import-tag](./docs/rules/prefer-import-tag.md#readme) | Prefer `@import` tags to inline `import()` statements. |
518518
|:heavy_check_mark:|| [reject-any-type](./docs/rules/reject-any-type.md#readme) | Reports use of `any` or `*` type |

‎docs/rules/no-unnecessary-type-assertion.md‎

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ TypeScript infers for the expression.
88

99
Currently only supports `VariableDeclaration`.
1010

11+
The fixer removes the redundant `@type` tag, deleting the whole JSDoc block if
12+
nothing else is left in it.
13+
1114
**Note that this experimental rule requires that the `typescript` package is installed.
1215
You must also install and point to the `typescript-eslint` parser, targeting your
1316
JavaScript + JSDoc files. Note also that this rule runs fairly slowly.**
@@ -36,6 +39,7 @@ export default [
3639
'jsdoc/no-unnecessary-type-assertion': ['error', {
3740
// You can change these defaults
3841
checkLiteralConstAssertions: false,
42+
enableFixer: true,
3943
treatAnyAsRedundant: false,
4044
typesToIgnore: [],
4145
}]
@@ -56,6 +60,12 @@ A single options object has the following properties.
5660

5761
Whether to check `const` type assertions as redundant
5862

63+
<a name="user-content-no-unnecessary-type-assertion-options-enablefixer"></a>
64+
<a name="no-unnecessary-type-assertion-options-enablefixer"></a>
65+
### <code>enableFixer</code>
66+
67+
Whether to enable the fixer that removes the redundant `@type` tag (and the JSDoc block if it becomes empty). Defaults to `true`.
68+
5969
<a name="user-content-no-unnecessary-type-assertion-options-treatanyasredundant"></a>
6070
<a name="no-unnecessary-type-assertion-options-treatanyasredundant"></a>
6171
### <code>treatAnyAsRedundant</code>
@@ -74,7 +84,7 @@ An array list of types to ignore
7484
|Context|`VariableDeclaration`|
7585
|Tags|`type`|
7686
|Recommended|false|
77-
|Options|`checkLiteralConstAssertions`, `treatAnyAsRedundant`, `typesToIgnore`|
87+
|Options|`checkLiteralConstAssertions`, `enableFixer`, `treatAnyAsRedundant`, `typesToIgnore`|
7888

7989
<a name="user-content-no-unnecessary-type-assertion-failing-examples"></a>
8090
<a name="no-unnecessary-type-assertion-failing-examples"></a>
@@ -89,6 +99,13 @@ The following patterns are considered problems:
8999
const a = 5;
90100
// Message: The @type tag declaring "5" is redundant as TypeScript infers it automatically.
91101

102+
/**
103+
* This is a special comment.
104+
* @type {5}
105+
*/
106+
const a = 5;
107+
// Message: The @type tag declaring "5" is redundant as TypeScript infers it automatically.
108+
92109
/**
93110
* @type {string}
94111
*/
@@ -146,6 +163,24 @@ const a = /** @type {any} */ (5);
146163
const b = a;
147164
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"treatAnyAsRedundant":true}]
148165
// Message: The @type tag declaring "any" is redundant as TypeScript infers it automatically.
166+
167+
/** @type {{prop: string}} */
168+
const mapPaths = {prop: "text"};
169+
// Message: The @type tag declaring "{prop: string}" is redundant as TypeScript infers it automatically.
170+
171+
/**
172+
* Keep me.
173+
* @type {string}
174+
*/
175+
const a = 'hello';
176+
// Message: The @type tag declaring "string" is redundant as TypeScript infers it automatically.
177+
178+
/**
179+
* @type {string}
180+
*/
181+
const a = 'hello';
182+
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"enableFixer":false}]
183+
// Message: The @type tag declaring "string" is redundant as TypeScript infers it automatically.
149184
````
150185

151186

@@ -212,5 +247,14 @@ function quux (b) {
212247
const a = b;
213248
}
214249
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"checkLiteralConstAssertions":true}]
250+
251+
/** @type {string[]} */
252+
const mapPaths = [];
253+
254+
/** @type {{prop?: string}} */
255+
const mapPaths = {};
256+
257+
/** @type {{prop?: string}} */
258+
const mapPaths = {prop: "text"};
215259
````
216260

‎src/rules.d.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,10 @@ export interface Rules {
13201320
* Whether to check `const` type assertions as redundant
13211321
*/
13221322
checkLiteralConstAssertions?: boolean;
1323+
/**
1324+
* Whether to enable the fixer that removes the redundant `@type` tag (and the JSDoc block if it becomes empty). Defaults to `true`.
1325+
*/
1326+
enableFixer?: boolean;
13231327
/**
13241328
* Whether to treat `any` type casts as redundant
13251329
*/

‎src/rules/noUnnecessaryTypeAssertion.js‎

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ const isLiteralType = (type) => {
6262

6363
export default iterateJsdoc(({
6464
context,
65+
jsdoc,
6566
node: nde,
66-
report,
6767
utils,
68+
// eslint-disable-next-line complexity -- Numerous type/option permutations
6869
}) => {
6970
/* c8 ignore next 4 -- Guard */
7071
// Already handled
@@ -82,10 +83,41 @@ export default iterateJsdoc(({
8283
const {
8384
// https://typescript-eslint.io/rules/no-unnecessary-type-assertion/
8485
checkLiteralConstAssertions = false,
86+
enableFixer = true,
8587
treatAnyAsRedundant = false,
8688
typesToIgnore = [],
8789
} = context.options[0] ?? {};
8890

91+
/**
92+
* Removes the redundant `@type` tag, deleting the whole JSDoc block if it
93+
* is left empty.
94+
* @returns {void}
95+
*/
96+
const removeType = () => {
97+
utils.removeTag(jsdoc.tags.indexOf(/** @type {any} */ (types[0])), {
98+
removeEmptyBlock: true,
99+
});
100+
101+
// `removeTag` only drops the enclosing block for a single-line comment; for
102+
// a multi-line block whose sole content was the `@type` tag, clear what is
103+
// left (only the delimiter lines) so the now-empty comment is removed too.
104+
const blockIsEmpty = jsdoc.source.every(({
105+
tokens: {
106+
description,
107+
name,
108+
tag,
109+
type,
110+
},
111+
}) => {
112+
return !tag && !type && !name && !description.trim();
113+
});
114+
if (blockIsEmpty) {
115+
jsdoc.source.splice(0);
116+
}
117+
};
118+
119+
const fixer = enableFixer ? removeType : null;
120+
89121
const node =
90122
/**
91123
* @type {import('@typescript-eslint/utils').TSESTree.Node}
@@ -202,10 +234,11 @@ export default iterateJsdoc(({
202234

203235
if (assertedTypeStr === 'const') {
204236
if (checkLiteralConstAssertions && isLiteralType(rawInferredType)) {
205-
report(
237+
utils.reportJSDoc(
206238
'The @type tag declaring "{{ type }}" is redundant as TypeScript infers it automatically for literals.',
207-
null,
208239
types[0],
240+
fixer,
241+
true,
209242
{
210243
type: assertedTypeStr,
211244
},
@@ -220,10 +253,11 @@ export default iterateJsdoc(({
220253
(treatAnyAsRedundant || assertedTypeStr !== 'any') &&
221254
!typesToIgnore.includes(assertedTypeStr)
222255
) {
223-
report(
256+
utils.reportJSDoc(
224257
'The @type tag declaring "{{ type }}" is redundant as TypeScript infers it automatically.',
225-
null,
226258
types[0],
259+
fixer,
260+
true,
227261
{
228262
type: assertedTypeStr,
229263
},
@@ -236,6 +270,7 @@ export default iterateJsdoc(({
236270
docs: {
237271
description: 'Reports redundant @type tags that match or broaden the naturally inferred TypeScript type.',
238272
},
273+
fixable: 'code',
239274
schema: [
240275
{
241276
additionalProperties: false,
@@ -244,6 +279,10 @@ export default iterateJsdoc(({
244279
description: 'Whether to check `const` type assertions as redundant',
245280
type: 'boolean',
246281
},
282+
enableFixer: {
283+
description: 'Whether to enable the fixer that removes the redundant `@type` tag (and the JSDoc block if it becomes empty). Defaults to `true`.',
284+
type: 'boolean',
285+
},
247286
treatAnyAsRedundant: {
248287
description: 'Whether to treat `any` type casts as redundant',
249288
type: 'boolean',

0 commit comments

Comments
 (0)