Skip to content

Commit 0eaa342

Browse files
committed
BREAKING CHANGE: Cause preferredTypes setting not to apply by default to parent types; need to target type name with "<>" appended to target parent types (and only parent types); can alternatively; also adds option unifyParentAndChildTypeChecks to restore the old behavior
Allows for warning against overly generic types only (e.g., `Array` without its own children) or against the parent type only (to disallow `Array.<someType>`) To restore old behavior, also target type names with "<>" or set the `unifyParentAndChildTypeChecks` option.
1 parent 7d6b9ed commit 0eaa342

File tree

5 files changed

+567
-14
lines changed

5 files changed

+567
-14
lines changed

.README/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ If `no-undefined-types` has the option key `preferredTypesDefined` set to
243243
`true`, the preferred types indicated in the `settings.jsdoc.preferredTypes`
244244
map will be assumed to be defined.
245245

246+
See the option of `check-types`, `unifyParentAndChildTypeChecks`, for
247+
how the keys of `preferredTypes` may have `<>` appended and its bearing
248+
on whether types are checked as parents/children only (e.g., to match `Array`
249+
if the type is `Array` vs. `Array.<string>`).
250+
246251
### Settings to Configure `valid-types`
247252

248253
* `settings.jsdoc.allowEmptyNamepaths` - Set to `false` to disallow

.README/rules/check-types.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,21 @@ RegExp
2121

2222
`check-types` allows one option:
2323

24-
- An option object with the key `noDefaults` to insist that only the
25-
supplied option type map is to be used, and that the default preferences
26-
(such as "string" over "String") will not be enforced.
24+
- An option object:
25+
- with the key `noDefaults` to insist that only the supplied option type
26+
map is to be used, and that the default preferences (such as "string"
27+
over "String") will not be enforced.
28+
- with the key `unifyParentAndChildTypeChecks` to treat
29+
`settings.jsdoc.preferredTypes` keys the same whether they are of the form
30+
`SomeType` or `SomeType<>`. If this is `false` or unset, the former
31+
will only apply to types which are not parent types/unions whereas the
32+
latter will only apply for parent types/unions.
2733

2834
See also the documentation on `settings.jsdoc.preferredTypes` which impacts
2935
the behavior of `check-types`.
3036

37+
38+
3139
#### Why not capital case everything?
3240

3341
Why are `boolean`, `number` and `string` exempt from starting with a capital letter? Let's take `string` as an example. In Javascript, everything is an object. The string Object has prototypes for string functions such as `.toUpperCase()`.
@@ -70,7 +78,7 @@ String | **string** | **string** | `("test") instanceof String` -> **`false`**
7078
|Tags|`class`, `constant`, `enum`, `implements`, `member`, `module`, `namespace`, `param`, `property`, `returns`, `throws`, `type`, `typedef`, `yields`|
7179
|Aliases|`constructor`, `const`, `var`, `arg`, `argument`, `prop`, `return`, `exception`|
7280
|Closure-only|`package`, `private`, `protected`, `public`, `static`|
73-
|Options|`noDefaults`|
81+
|Options|`noDefaults`, `unifyParentAndChildTypeChecks`|
7482
|Settings|`preferredTypes`|
7583

7684
<!-- assertions checkTypes -->

README.md

Lines changed: 171 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ If `no-undefined-types` has the option key `preferredTypesDefined` set to
296296
`true`, the preferred types indicated in the `settings.jsdoc.preferredTypes`
297297
map will be assumed to be defined.
298298

299+
See the option of `check-types`, `unifyParentAndChildTypeChecks`, for
300+
how the keys of `preferredTypes` may have `<>` appended and its bearing
301+
on whether types are checked as parents/children only (e.g., to match `Array`
302+
if the type is `Array` vs. `Array.<string>`).
303+
299304
<a name="eslint-plugin-jsdoc-settings-settings-to-configure-valid-types"></a>
300305
### Settings to Configure <code>valid-types</code>
301306

@@ -1284,13 +1289,21 @@ RegExp
12841289

12851290
`check-types` allows one option:
12861291

1287-
- An option object with the key `noDefaults` to insist that only the
1288-
supplied option type map is to be used, and that the default preferences
1289-
(such as "string" over "String") will not be enforced.
1292+
- An option object:
1293+
- with the key `noDefaults` to insist that only the supplied option type
1294+
map is to be used, and that the default preferences (such as "string"
1295+
over "String") will not be enforced.
1296+
- with the key `unifyParentAndChildTypeChecks` to treat
1297+
`settings.jsdoc.preferredTypes` keys the same whether they are of the form
1298+
`SomeType` or `SomeType<>`. If this is `false` or unset, the former
1299+
will only apply to types which are not parent types/unions whereas the
1300+
latter will only apply for parent types/unions.
12901301

12911302
See also the documentation on `settings.jsdoc.preferredTypes` which impacts
12921303
the behavior of `check-types`.
12931304

1305+
1306+
12941307
<a name="eslint-plugin-jsdoc-rules-check-types-why-not-capital-case-everything"></a>
12951308
#### Why not capital case everything?
12961309

@@ -1334,7 +1347,7 @@ String | **string** | **string** | `("test") instanceof String` -> **`false`**
13341347
|Tags|`class`, `constant`, `enum`, `implements`, `member`, `module`, `namespace`, `param`, `property`, `returns`, `throws`, `type`, `typedef`, `yields`|
13351348
|Aliases|`constructor`, `const`, `var`, `arg`, `argument`, `prop`, `return`, `exception`|
13361349
|Closure-only|`package`, `private`, `protected`, `public`, `static`|
1337-
|Options|`noDefaults`|
1350+
|Options|`noDefaults`, `unifyParentAndChildTypeChecks`|
13381351
|Settings|`preferredTypes`|
13391352

13401353
The following patterns are considered problems:
@@ -1490,6 +1503,98 @@ function qux(foo, bar) {
14901503
}
14911504
// Settings: {"jsdoc":{"preferredTypes":{"abc":"Abc","string":"Str"}}}
14921505
// Message: Invalid JSDoc @param "foo" type "abc"; prefer: "Abc".
1506+
1507+
/**
1508+
* @param {Array} foo
1509+
*/
1510+
function quux (foo) {
1511+
1512+
}
1513+
// Settings: {"jsdoc":{"preferredTypes":{"Array":"GenericArray"}}}
1514+
// Message: Invalid JSDoc @param "foo" type "Array"; prefer: "GenericArray".
1515+
1516+
/**
1517+
* @param {Array} foo
1518+
*/
1519+
function quux (foo) {
1520+
1521+
}
1522+
// Settings: {"jsdoc":{"preferredTypes":{"Array":"GenericArray","Array<>":"GenericArray"}}}
1523+
// Message: Invalid JSDoc @param "foo" type "Array"; prefer: "GenericArray".
1524+
1525+
/**
1526+
* @param {Array.<string>} foo
1527+
*/
1528+
function quux (foo) {
1529+
1530+
}
1531+
// Settings: {"jsdoc":{"preferredTypes":{"Array<>":"GenericArray"}}}
1532+
// Message: Invalid JSDoc @param "foo" type "Array"; prefer: "GenericArray".
1533+
1534+
/**
1535+
* @param {string[]} foo
1536+
*/
1537+
function quux (foo) {
1538+
1539+
}
1540+
// Settings: {"jsdoc":{"preferredTypes":{"Array<>":"GenericArray"}}}
1541+
// Message: Invalid JSDoc @param "foo" type "Array"; prefer: "GenericArray".
1542+
1543+
/**
1544+
* @param {object} foo
1545+
*/
1546+
function quux (foo) {
1547+
1548+
}
1549+
// Settings: {"jsdoc":{"preferredTypes":{"object":"GenericObject"}}}
1550+
// Message: Invalid JSDoc @param "foo" type "object"; prefer: "GenericObject".
1551+
1552+
/**
1553+
* @param {object} foo
1554+
*/
1555+
function quux (foo) {
1556+
1557+
}
1558+
// Settings: {"jsdoc":{"preferredTypes":{"object":"GenericObject","object<>":"GenericObject"}}}
1559+
// Message: Invalid JSDoc @param "foo" type "object"; prefer: "GenericObject".
1560+
1561+
/**
1562+
* @param {object.<string>} foo
1563+
*/
1564+
function quux (foo) {
1565+
1566+
}
1567+
// Settings: {"jsdoc":{"preferredTypes":{"object<>":"GenericObject"}}}
1568+
// Message: Invalid JSDoc @param "foo" type "object"; prefer: "GenericObject".
1569+
1570+
/**
1571+
* @param {object.<string, number>} foo
1572+
*/
1573+
function quux (foo) {
1574+
1575+
}
1576+
// Settings: {"jsdoc":{"preferredTypes":{"object<>":"GenericObject"}}}
1577+
// Message: Invalid JSDoc @param "foo" type "object"; prefer: "GenericObject".
1578+
1579+
/**
1580+
* @param {object.<string>} foo
1581+
*/
1582+
function quux (foo) {
1583+
1584+
}
1585+
// Settings: {"jsdoc":{"preferredTypes":{"object":"GenericObject"}}}
1586+
// Options: [{"unifyParentAndChildTypeChecks":true}]
1587+
// Message: Invalid JSDoc @param "foo" type "object"; prefer: "GenericObject".
1588+
1589+
/**
1590+
* @param {object.<string, number>} foo
1591+
*/
1592+
function quux (foo) {
1593+
1594+
}
1595+
// Settings: {"jsdoc":{"preferredTypes":{"object":"GenericObject"}}}
1596+
// Options: [{"unifyParentAndChildTypeChecks":true}]
1597+
// Message: Invalid JSDoc @param "foo" type "object"; prefer: "GenericObject".
14931598
````
14941599

14951600
The following patterns are not considered problems:
@@ -1560,6 +1665,68 @@ function quux (foo) {
15601665

15611666
}
15621667
// Settings: {"jsdoc":{"preferredTypes":{"object":"Object"}}}
1668+
1669+
/**
1670+
* @param {Array} foo
1671+
*/
1672+
function quux (foo) {
1673+
1674+
}
1675+
1676+
/**
1677+
* @param {Array.<string>} foo
1678+
*/
1679+
function quux (foo) {
1680+
1681+
}
1682+
// Settings: {"jsdoc":{"preferredTypes":{"Array":"GenericArray"}}}
1683+
1684+
/**
1685+
* @param {string[]} foo
1686+
*/
1687+
function quux (foo) {
1688+
1689+
}
1690+
// Settings: {"jsdoc":{"preferredTypes":{"Array":"GenericArray"}}}
1691+
1692+
/**
1693+
* @param {Array} foo
1694+
*/
1695+
function quux (foo) {
1696+
1697+
}
1698+
// Settings: {"jsdoc":{"preferredTypes":{"Array<>":"GenericArray"}}}
1699+
1700+
/**
1701+
* @param {object} foo
1702+
*/
1703+
function quux (foo) {
1704+
1705+
}
1706+
1707+
/**
1708+
* @param {object.<string>} foo
1709+
*/
1710+
function quux (foo) {
1711+
1712+
}
1713+
// Settings: {"jsdoc":{"preferredTypes":{"object":"GenericObject"}}}
1714+
1715+
/**
1716+
* @param {object.<string, number>} foo
1717+
*/
1718+
function quux (foo) {
1719+
1720+
}
1721+
// Settings: {"jsdoc":{"preferredTypes":{"object":"GenericObject"}}}
1722+
1723+
/**
1724+
* @param {object} foo
1725+
*/
1726+
function quux (foo) {
1727+
1728+
}
1729+
// Settings: {"jsdoc":{"preferredTypes":{"object<>":"GenericObject"}}}
15631730
````
15641731

15651732

src/rules/checkTypes.js

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default iterateJsdoc(({
2929
const preferredTypes = _.get(context, 'settings.jsdoc.preferredTypes');
3030
const optionObj = context.options[0];
3131
const noDefaults = _.get(optionObj, 'noDefaults');
32+
const unifyParentAndChildTypeChecks = _.get(optionObj, 'unifyParentAndChildTypeChecks');
3233

3334
jsdocTags.forEach((jsdocTag) => {
3435
const invalidTypes = [];
@@ -40,12 +41,34 @@ export default iterateJsdoc(({
4041
return;
4142
}
4243

44+
const getPreferredTypeInfo = (type, nodeName) => {
45+
let hasMatchingPreferredType;
46+
let isGenericMatch;
47+
if (preferredTypes) {
48+
const nonparentType = type === 'ANY' || typeAst.type === 'NAME';
49+
isGenericMatch = _.get(preferredTypes, nodeName + '<>') !== undefined &&
50+
(unifyParentAndChildTypeChecks || !nonparentType);
51+
hasMatchingPreferredType =
52+
_.get(preferredTypes, nodeName) !== undefined &&
53+
(nonparentType || unifyParentAndChildTypeChecks) ||
54+
isGenericMatch;
55+
}
56+
57+
return [hasMatchingPreferredType, isGenericMatch];
58+
};
59+
4360
traverse(typeAst, (node) => {
44-
if (['NAME', 'ANY'].includes(node.type)) {
45-
const nodeName = node.type === 'ANY' ? '*' : node.name;
61+
const {type, name} = node;
62+
if (['NAME', 'ANY'].includes(type)) {
63+
const nodeName = type === 'ANY' ? '*' : name;
64+
65+
const [hasMatchingPreferredType, isGenericMatch] = getPreferredTypeInfo(type, nodeName);
66+
4667
let preferred;
47-
if (preferredTypes && _.get(preferredTypes, nodeName) !== undefined) {
48-
const preferredSetting = preferredTypes[nodeName];
68+
if (hasMatchingPreferredType) {
69+
const preferredSetting = preferredTypes[nodeName + (
70+
isGenericMatch ? '<>' : ''
71+
)];
4972

5073
if (!preferredSetting) {
5174
invalidTypes.push([nodeName]);
@@ -60,7 +83,7 @@ export default iterateJsdoc(({
6083
_.get(preferredSetting, 'message')
6184
]);
6285
}
63-
} else if (!noDefaults && node.type === 'NAME') {
86+
} else if (!noDefaults && type === 'NAME') {
6487
for (const strictNativeType of strictNativeTypes) {
6588
if (strictNativeType.toLowerCase() === nodeName.toLowerCase() &&
6689
strictNativeType !== nodeName &&
@@ -75,7 +98,7 @@ export default iterateJsdoc(({
7598
}
7699
}
77100
if (preferred) {
78-
if (node.type === 'ANY') {
101+
if (type === 'ANY') {
79102
node.type = 'NAME';
80103
}
81104
node.name = preferred;

0 commit comments

Comments
 (0)