Skip to content

Commit 962d99d

Browse files
committed
feat(check-template-names): add rule; fixes #1120
1 parent 7e311ab commit 962d99d

File tree

6 files changed

+471
-0
lines changed

6 files changed

+471
-0
lines changed

.README/rules/check-template-names.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# `check-template-names`
2+
3+
Checks that any `@template` names are actually used in the connected
4+
`@typedef` or type alias.
5+
6+
Currently checks `TSTypeAliasDeclaration` such as:
7+
8+
```ts
9+
/**
10+
* @template D
11+
* @template V
12+
*/
13+
export type Pairs<D, V> = [D, V | undefined];
14+
```
15+
16+
or
17+
18+
```js
19+
/**
20+
* @template D
21+
* @template V
22+
* @typedef {[D, V | undefined]} Pairs
23+
*/
24+
```
25+
26+
|||
27+
|---|---|
28+
|Context|everywhere|
29+
|Tags|`@template`|
30+
|Recommended|false|
31+
|Settings||
32+
|Options||
33+
34+
## Failing examples
35+
36+
<!-- assertions-failing checkTemplateNames -->
37+
38+
## Passing examples
39+
40+
<!-- assertions-passing checkTemplateNames -->

docs/rules/check-template-names.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<a name="user-content-check-template-names"></a>
2+
<a name="check-template-names"></a>
3+
# <code>check-template-names</code>
4+
5+
Checks that any `@template` names are actually used in the connected
6+
`@typedef` or type alias.
7+
8+
Currently checks `TSTypeAliasDeclaration` such as:
9+
10+
```ts
11+
/**
12+
* @template D
13+
* @template V
14+
*/
15+
export type Pairs<D, V> = [D, V | undefined];
16+
```
17+
18+
or
19+
20+
```js
21+
/**
22+
* @template D
23+
* @template V
24+
* @typedef {[D, V | undefined]} Pairs
25+
*/
26+
```
27+
28+
|||
29+
|---|---|
30+
|Context|everywhere|
31+
|Tags|`@template`|
32+
|Recommended|false|
33+
|Settings||
34+
|Options||
35+
36+
<a name="user-content-check-template-names-failing-examples"></a>
37+
<a name="check-template-names-failing-examples"></a>
38+
## Failing examples
39+
40+
The following patterns are considered problems:
41+
42+
````js
43+
/**
44+
* @template D
45+
* @template V
46+
*/
47+
type Pairs<X, Y> = [X, Y | undefined];
48+
// Message: @template D not in use
49+
50+
/**
51+
* @template D
52+
* @template V
53+
*/
54+
export type Pairs<X, Y> = [X, Y | undefined];
55+
// Message: @template D not in use
56+
57+
/**
58+
* @template D
59+
* @template V
60+
* @typedef {[X, Y | undefined]} Pairs
61+
*/
62+
// Message: @template D not in use
63+
64+
/**
65+
* @template D
66+
* @template V
67+
*/
68+
export type Pairs = [number, undefined];
69+
// Message: @template D not in use
70+
71+
/**
72+
* @template D
73+
* @template V
74+
* @typedef {[undefined]} Pairs
75+
*/
76+
// Settings: {"jsdoc":{"mode":"permissive"}}
77+
// Message: @template D not in use
78+
79+
/**
80+
* @template D, U, V
81+
*/
82+
export type Extras<D, U> = [D, U | undefined];
83+
// Message: @template V not in use
84+
85+
/**
86+
* @template D, U, V
87+
* @typedef {[D, U | undefined]} Extras
88+
*/
89+
// Message: @template V not in use
90+
````
91+
92+
93+
94+
<a name="user-content-check-template-names-passing-examples"></a>
95+
<a name="check-template-names-passing-examples"></a>
96+
## Passing examples
97+
98+
The following patterns are not considered problems:
99+
100+
````js
101+
/**
102+
* @template D
103+
* @template V
104+
*/
105+
export type Pairs<D, V> = [D, V | undefined];
106+
107+
/**
108+
* @template D
109+
* @template V
110+
* @typedef {[D, V | undefined]} Pairs
111+
*/
112+
113+
/**
114+
* @template D, U, V
115+
*/
116+
export type Extras<D, U, V> = [D, U, V | undefined];
117+
118+
/**
119+
* @template D, U, V
120+
* @typedef {[D, U, V | undefined]} Extras
121+
*/
122+
123+
/**
124+
* @template X
125+
* @typedef {[D, U, V | undefined]} Extras
126+
* @typedef {[D, U, V | undefined]} Extras
127+
*/
128+
````
129+

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import checkParamNames from './rules/checkParamNames.js';
77
import checkPropertyNames from './rules/checkPropertyNames.js';
88
import checkSyntax from './rules/checkSyntax.js';
99
import checkTagNames from './rules/checkTagNames.js';
10+
import checkTemplateNames from './rules/checkTemplateNames.js';
1011
import checkTypes from './rules/checkTypes.js';
1112
import checkValues from './rules/checkValues.js';
1213
import convertToJsdocComments from './rules/convertToJsdocComments.js';
@@ -81,6 +82,7 @@ const index = {
8182
'check-property-names': checkPropertyNames,
8283
'check-syntax': checkSyntax,
8384
'check-tag-names': checkTagNames,
85+
'check-template-names': checkTemplateNames,
8486
'check-types': checkTypes,
8587
'check-values': checkValues,
8688
'convert-to-jsdoc-comments': convertToJsdocComments,
@@ -155,6 +157,7 @@ const createRecommendedRuleset = (warnOrError, flatName) => {
155157
'jsdoc/check-property-names': warnOrError,
156158
'jsdoc/check-syntax': 'off',
157159
'jsdoc/check-tag-names': warnOrError,
160+
'jsdoc/check-template-names': 'off',
158161
'jsdoc/check-types': warnOrError,
159162
'jsdoc/check-values': warnOrError,
160163
'jsdoc/convert-to-jsdoc-comments': 'off',

src/rules/checkTemplateNames.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {
2+
parse as parseType,
3+
traverse,
4+
tryParse as tryParseType,
5+
} from '@es-joy/jsdoccomment';
6+
import iterateJsdoc from '../iterateJsdoc.js';
7+
8+
export default iterateJsdoc(({
9+
context,
10+
utils,
11+
node,
12+
settings,
13+
report,
14+
}) => {
15+
const {
16+
mode
17+
} = settings;
18+
19+
const templateTags = utils.getTags('template');
20+
21+
const usedNames = new Set();
22+
/**
23+
* @param {import('@typescript-eslint/types').TSESTree.TSTypeAliasDeclaration} aliasDeclaration
24+
*/
25+
const checkParameters = (aliasDeclaration) => {
26+
/* c8 ignore next -- Guard */
27+
const {params} = aliasDeclaration.typeParameters ?? {params: []};
28+
for (const {name: {name}} of params) {
29+
usedNames.add(name);
30+
}
31+
for (const tag of templateTags) {
32+
const {name} = tag;
33+
const names = name.split(/,\s*/);
34+
for (const name of names) {
35+
if (!usedNames.has(name)) {
36+
report(`@template ${name} not in use`, null, tag);
37+
}
38+
}
39+
}
40+
};
41+
42+
const handleTypeAliases = () => {
43+
const nde = /** @type {import('@typescript-eslint/types').TSESTree.Node} */ (
44+
node
45+
);
46+
if (!nde) {
47+
return;
48+
}
49+
switch (nde.type) {
50+
case 'ExportNamedDeclaration':
51+
if (nde.declaration?.type === 'TSTypeAliasDeclaration') {
52+
checkParameters(nde.declaration);
53+
}
54+
break;
55+
case 'TSTypeAliasDeclaration':
56+
checkParameters(nde);
57+
break;
58+
}
59+
};
60+
61+
const typedefTags = utils.getTags('typedef');
62+
if (!typedefTags.length || typedefTags.length >= 2) {
63+
handleTypeAliases();
64+
return;
65+
}
66+
67+
const potentialType = typedefTags[0].type;
68+
const parsedType = mode === 'permissive' ?
69+
tryParseType(/** @type {string} */ (potentialType)) :
70+
parseType(/** @type {string} */ (potentialType), mode)
71+
72+
traverse(parsedType, (nde) => {
73+
const {
74+
type,
75+
value,
76+
} = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde);
77+
if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) {
78+
usedNames.add(value);
79+
}
80+
});
81+
82+
for (const tag of templateTags) {
83+
const {name} = tag;
84+
const names = name.split(/,\s*/);
85+
for (const name of names) {
86+
if (!usedNames.has(name)) {
87+
report(`@template ${name} not in use`, null, tag);
88+
}
89+
}
90+
}
91+
}, {
92+
iterateAllJsdocs: true,
93+
meta: {
94+
docs: {
95+
description: 'Checks that any `@template` names are actually used in the connected `@typedef` or type alias.',
96+
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-template.md#repos-sticky-header',
97+
},
98+
schema: [],
99+
type: 'suggestion',
100+
},
101+
});

0 commit comments

Comments
 (0)