Skip to content

Commit ae4e95d

Browse files
committed
feat: add reject-any-type and reject-function-type rules and extraRuleDefinitions.preferTypes option
BREAKING CHANGE: The new rules are added to `recommended` configs
1 parent bb607b9 commit ae4e95d

17 files changed

+691
-51
lines changed

.README/advanced.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,56 @@ export const a = (abc, def) => {
164164
};
165165
/* eslint-enable jsdoc/forbid-Any */
166166
```
167+
168+
#### Preferring type structures
169+
170+
When the structures in question are types, a disadvantage of the previous approach
171+
is that one cannot perform replacements nor can one distinguish between parent and
172+
child types for a generic.
173+
174+
If targeting a type structure, you can use `extraRuleDefinitions.preferTypes`.
175+
176+
While one can get this same behavior using the `preferredTypes` setting, the
177+
advantage of creating a rule definition is that handling is distributed not to
178+
a single rule (`jsdoc/check-types`), but to an individual rule for each preferred
179+
type (which can then be selectively enabled and disabled).
180+
181+
```js
182+
import {jsdoc} from 'eslint-plugin-jsdoc';
183+
184+
export default [
185+
jsdoc({
186+
config: 'flat/recommended',
187+
extraRuleDefinitions: {
188+
preferTypes: {
189+
// This key will be used in the rule name
190+
promise: {
191+
description: 'This rule disallows Promises without a generic type',
192+
overrideSettings: {
193+
// Uses the same keys are are available on the `preferredTypes` settings
194+
195+
// This key will indicate the type node name to find
196+
Promise: {
197+
// This is the specific error message if reported
198+
message: 'Add a generic type for this Promise.',
199+
200+
// This can instead be a string replacement if an auto-replacement
201+
// is desired
202+
replacement: false,
203+
204+
// If `true`, this will check in both parent and child positions
205+
unifyParentAndChildTypeChecks: false,
206+
},
207+
},
208+
url: 'https://example.com/Promise-rule.md',
209+
},
210+
},
211+
},
212+
rules: {
213+
// Don't forget to enable the above-defined rules
214+
'jsdoc/prefer-type-promise': [
215+
'error',
216+
],
217+
}
218+
})
219+
```

.README/rules/no-multi-asterisks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Prevent the likes of this:
5151
|||
5252
|---|---|
5353
|Context|everywhere|
54-
|Tags|(any)|
54+
|Tags|(Any)|
5555
|Recommended|true|
5656
|Settings||
5757
|Options|`allowWhitespace`, `preventAtEnd`, `preventAtMiddleLines`|

.README/rules/reject-any-type.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# `reject-any-type`
2+
3+
Reports use of `any` (or `*`) type within JSDoc tag types.
4+
5+
|||
6+
|---|---|
7+
|Context|everywhere|
8+
|Tags|`augments`, `class`, `constant`, `enum`, `implements`, `member`, `module`, `namespace`, `param`, `property`, `returns`, `throws`, `type`, `typedef`, `yields`|
9+
|Aliases|`constructor`, `const`, `extends`, `var`, `arg`, `argument`, `prop`, `return`, `exception`, `yield`|
10+
|Closure-only|`package`, `private`, `protected`, `public`, `static`|
11+
|Recommended|true|
12+
|Settings|`mode`|
13+
|Options||
14+
15+
## Failing examples
16+
17+
<!-- assertions-failing rejectAnyType -->
18+
19+
## Passing examples
20+
21+
<!-- assertions-passing rejectAnyType -->
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# `reject-function-type`
2+
3+
Reports use of `Function` type within JSDoc tag types.
4+
5+
|||
6+
|---|---|
7+
|Context|everywhere|
8+
|Tags|`augments`, `class`, `constant`, `enum`, `implements`, `member`, `module`, `namespace`, `param`, `property`, `returns`, `throws`, `type`, `typedef`, `yields`|
9+
|Aliases|`constructor`, `const`, `extends`, `var`, `arg`, `argument`, `prop`, `return`, `exception`, `yield`|
10+
|Closure-only|`package`, `private`, `protected`, `public`, `static`|
11+
|Recommended|true|
12+
|Settings|`mode`|
13+
|Options||
14+
15+
## Failing examples
16+
17+
<!-- assertions-failing rejectFunctionType -->
18+
19+
## Passing examples
20+
21+
<!-- assertions-passing rejectFunctionType -->

docs/advanced.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* [Uses/Tips for AST](#user-content-advanced-ast-and-selectors-uses-tips-for-ast)
99
* [Creating your own rules](#user-content-advanced-creating-your-own-rules)
1010
* [Forbidding structures](#user-content-advanced-creating-your-own-rules-forbidding-structures)
11+
* [Preferring type structures](#user-content-advanced-creating-your-own-rules-preferring-type-structures)
1112

1213

1314
<a name="user-content-advanced-ast-and-selectors"></a>
@@ -184,3 +185,58 @@ export const a = (abc, def) => {
184185
};
185186
/* eslint-enable jsdoc/forbid-Any */
186187
```
188+
189+
<a name="user-content-advanced-creating-your-own-rules-preferring-type-structures"></a>
190+
<a name="advanced-creating-your-own-rules-preferring-type-structures"></a>
191+
#### Preferring type structures
192+
193+
When the structures in question are types, a disadvantage of the previous approach
194+
is that one cannot perform replacements nor can one distinguish between parent and
195+
child types for a generic.
196+
197+
If targeting a type structure, you can use `extraRuleDefinitions.preferTypes`.
198+
199+
While one can get this same behavior using the `preferredTypes` setting, the
200+
advantage of creating a rule definition is that handling is distributed not to
201+
a single rule (`jsdoc/check-types`), but to an individual rule for each preferred
202+
type (which can then be selectively enabled and disabled).
203+
204+
```js
205+
import {jsdoc} from 'eslint-plugin-jsdoc';
206+
207+
export default [
208+
jsdoc({
209+
config: 'flat/recommended',
210+
extraRuleDefinitions: {
211+
preferTypes: {
212+
// This key will be used in the rule name
213+
promise: {
214+
description: 'This rule disallows Promises without a generic type',
215+
overrideSettings: {
216+
// Uses the same keys are are available on the `preferredTypes` settings
217+
218+
// This key will indicate the type node name to find
219+
Promise: {
220+
// This is the specific error message if reported
221+
message: 'Add a generic type for this Promise.',
222+
223+
// This can instead be a string replacement if an auto-replacement
224+
// is desired
225+
replacement: false,
226+
227+
// If `true`, this will check in both parent and child positions
228+
unifyParentAndChildTypeChecks: false,
229+
},
230+
},
231+
url: 'https://example.com/Promise-rule.md',
232+
},
233+
},
234+
},
235+
rules: {
236+
// Don't forget to enable the above-defined rules
237+
'jsdoc/prefer-type-promise': [
238+
'error',
239+
],
240+
}
241+
})
242+
```

docs/rules/no-multi-asterisks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Prevent the likes of this:
6565
|||
6666
|---|---|
6767
|Context|everywhere|
68-
|Tags|(any)|
68+
|Tags|(Any)|
6969
|Recommended|true|
7070
|Settings||
7171
|Options|`allowWhitespace`, `preventAtEnd`, `preventAtMiddleLines`|

docs/rules/reject-any-type.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<a name="user-content-reject-any-type"></a>
2+
<a name="reject-any-type"></a>
3+
# <code>reject-any-type</code>
4+
5+
Reports use of `any` (or `*`) type within JSDoc tag types.
6+
7+
|||
8+
|---|---|
9+
|Context|everywhere|
10+
|Tags|`augments`, `class`, `constant`, `enum`, `implements`, `member`, `module`, `namespace`, `param`, `property`, `returns`, `throws`, `type`, `typedef`, `yields`|
11+
|Aliases|`constructor`, `const`, `extends`, `var`, `arg`, `argument`, `prop`, `return`, `exception`, `yield`|
12+
|Closure-only|`package`, `private`, `protected`, `public`, `static`|
13+
|Recommended|true|
14+
|Settings|`mode`|
15+
|Options||
16+
17+
<a name="user-content-reject-any-type-failing-examples"></a>
18+
<a name="reject-any-type-failing-examples"></a>
19+
## Failing examples
20+
21+
The following patterns are considered problems:
22+
23+
````ts
24+
/**
25+
* @param {any} abc
26+
*/
27+
function quux () {}
28+
// Message: Prefer a more specific type to `any`
29+
30+
/**
31+
* @param {string|Promise<any>} abc
32+
*/
33+
function quux () {}
34+
// Message: Prefer a more specific type to `any`
35+
````
36+
37+
38+
39+
<a name="user-content-reject-any-type-passing-examples"></a>
40+
<a name="reject-any-type-passing-examples"></a>
41+
## Passing examples
42+
43+
The following patterns are not considered problems:
44+
45+
````ts
46+
/**
47+
* @param {SomeType} abc
48+
*/
49+
function quux () {}
50+
````
51+

docs/rules/reject-function-type.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<a name="user-content-reject-function-type"></a>
2+
<a name="reject-function-type"></a>
3+
# <code>reject-function-type</code>
4+
5+
Reports use of `Function` type within JSDoc tag types.
6+
7+
|||
8+
|---|---|
9+
|Context|everywhere|
10+
|Tags|`augments`, `class`, `constant`, `enum`, `implements`, `member`, `module`, `namespace`, `param`, `property`, `returns`, `throws`, `type`, `typedef`, `yields`|
11+
|Aliases|`constructor`, `const`, `extends`, `var`, `arg`, `argument`, `prop`, `return`, `exception`, `yield`|
12+
|Closure-only|`package`, `private`, `protected`, `public`, `static`|
13+
|Recommended|true|
14+
|Settings|`mode`|
15+
|Options||
16+
17+
<a name="user-content-reject-function-type-failing-examples"></a>
18+
<a name="reject-function-type-failing-examples"></a>
19+
## Failing examples
20+
21+
The following patterns are considered problems:
22+
23+
````ts
24+
/**
25+
* @param {Function} abc
26+
*/
27+
function quux () {}
28+
// Message: Prefer a more specific type to `Function`
29+
30+
/**
31+
* @param {string|Array<Function>} abc
32+
*/
33+
function quux () {}
34+
// Message: Prefer a more specific type to `Function`
35+
````
36+
37+
38+
39+
<a name="user-content-reject-function-type-passing-examples"></a>
40+
<a name="reject-function-type-passing-examples"></a>
41+
## Passing examples
42+
43+
The following patterns are not considered problems:
44+
45+
````ts
46+
/**
47+
* @param {SomeType} abc
48+
*/
49+
function quux () {}
50+
````
51+

src/buildRejectOrPreferRuleDefinition.js

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ const infoUC = {
9797

9898
/**
9999
* @param {{
100-
* checkNativeTypes: import('./rules/checkTypes.js').CheckNativeTypes|null
101-
* overrideSettings?: null,
100+
* checkNativeTypes?: import('./rules/checkTypes.js').CheckNativeTypes|null
101+
* overrideSettings?: import('./iterateJsdoc.js').Settings['preferredTypes']|null,
102102
* description?: string,
103103
* schema?: import('eslint').Rule.RuleMetaData['schema'],
104+
* typeName?: string,
104105
* url?: string,
105106
* }} cfg
106107
* @returns {import('@eslint/core').RuleDefinition<
@@ -109,7 +110,8 @@ const infoUC = {
109110
*/
110111
export const buildRejectOrPreferRuleDefinition = ({
111112
checkNativeTypes = null,
112-
description = 'Reports invalid types.',
113+
typeName,
114+
description = typeName ?? 'Reports invalid types.',
113115
overrideSettings = null,
114116
schema = [],
115117
url = 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-types.md#repos-sticky-header',
@@ -136,10 +138,14 @@ export const buildRejectOrPreferRuleDefinition = ({
136138
* }}
137139
*/
138140
{
139-
mode = settings.mode,
141+
mode,
140142
preferredTypes: preferredTypesOriginal,
141-
structuredTags = {},
142-
} = overrideSettings ?? settings;
143+
structuredTags,
144+
} = overrideSettings ? {
145+
mode: settings.mode,
146+
preferredTypes: overrideSettings,
147+
structuredTags: {},
148+
} : settings;
143149

144150
const injectObjectPreferredTypes = !('Object' in preferredTypesOriginal ||
145151
'object' in preferredTypesOriginal ||
@@ -199,7 +205,7 @@ export const buildRejectOrPreferRuleDefinition = ({
199205
const getPreferredTypeInfo = (_type, typeNodeName, parentNode, property) => {
200206
let hasMatchingPreferredType = false;
201207
let isGenericMatch = false;
202-
let typeName = typeNodeName;
208+
let typName = typeNodeName;
203209

204210
const isNameOfGeneric = parentNode !== undefined && parentNode.type === 'JsdocTypeGeneric' && property === 'left';
205211

@@ -228,7 +234,7 @@ export const buildRejectOrPreferRuleDefinition = ({
228234
) &&
229235
preferredType !== undefined
230236
) {
231-
typeName += checkPostFix;
237+
typName += checkPostFix;
232238

233239
return true;
234240
}
@@ -259,7 +265,7 @@ export const buildRejectOrPreferRuleDefinition = ({
259265
preferredType?.unifyParentAndChildTypeChecks)) &&
260266
preferredType !== undefined
261267
) {
262-
typeName = checkPostFix;
268+
typName = checkPostFix;
263269

264270
return true;
265271
}
@@ -280,7 +286,7 @@ export const buildRejectOrPreferRuleDefinition = ({
280286
directNameMatch && !property;
281287

282288
return [
283-
hasMatchingPreferredType, typeName, isGenericMatch,
289+
hasMatchingPreferredType, typName, isGenericMatch,
284290
];
285291
};
286292

@@ -302,15 +308,15 @@ export const buildRejectOrPreferRuleDefinition = ({
302308

303309
const [
304310
hasMatchingPreferredType,
305-
typeName,
311+
typName,
306312
isGenericMatch,
307313
] = getPreferredTypeInfo(type, typeNodeName, parentNode, property);
308314

309315
let preferred;
310316
let types;
311317
if (hasMatchingPreferredType) {
312-
const preferredSetting = preferredTypes[typeName];
313-
typeNodeName = typeName === '[]' ? typeName : typeNodeName;
318+
const preferredSetting = preferredTypes[typName];
319+
typeNodeName = typName === '[]' ? typName : typeNodeName;
314320

315321
if (!preferredSetting) {
316322
invalidTypes.push([

0 commit comments

Comments
 (0)