Skip to content

Commit

Permalink
feat(check-template-names): add rule; fixes #1120
Browse files Browse the repository at this point in the history
  • Loading branch information
brettz9 committed Jul 13, 2024
1 parent 7e311ab commit 962d99d
Show file tree
Hide file tree
Showing 6 changed files with 471 additions and 0 deletions.
40 changes: 40 additions & 0 deletions .README/rules/check-template-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# `check-template-names`

Checks that any `@template` names are actually used in the connected
`@typedef` or type alias.

Currently checks `TSTypeAliasDeclaration` such as:

```ts
/**
* @template D
* @template V
*/
export type Pairs<D, V> = [D, V | undefined];
```

or

```js
/**
* @template D
* @template V
* @typedef {[D, V | undefined]} Pairs
*/
```

|||
|---|---|
|Context|everywhere|
|Tags|`@template`|
|Recommended|false|
|Settings||
|Options||

## Failing examples

<!-- assertions-failing checkTemplateNames -->

## Passing examples

<!-- assertions-passing checkTemplateNames -->
129 changes: 129 additions & 0 deletions docs/rules/check-template-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<a name="user-content-check-template-names"></a>
<a name="check-template-names"></a>
# <code>check-template-names</code>

Checks that any `@template` names are actually used in the connected
`@typedef` or type alias.

Currently checks `TSTypeAliasDeclaration` such as:

```ts
/**
* @template D
* @template V
*/
export type Pairs<D, V> = [D, V | undefined];
```

or

```js
/**
* @template D
* @template V
* @typedef {[D, V | undefined]} Pairs
*/
```

|||
|---|---|
|Context|everywhere|
|Tags|`@template`|
|Recommended|false|
|Settings||
|Options||

<a name="user-content-check-template-names-failing-examples"></a>
<a name="check-template-names-failing-examples"></a>
## Failing examples

The following patterns are considered problems:

````js
/**
* @template D
* @template V
*/
type Pairs<X, Y> = [X, Y | undefined];
// Message: @template D not in use

/**
* @template D
* @template V
*/
export type Pairs<X, Y> = [X, Y | undefined];
// Message: @template D not in use

/**
* @template D
* @template V
* @typedef {[X, Y | undefined]} Pairs
*/
// Message: @template D not in use

/**
* @template D
* @template V
*/
export type Pairs = [number, undefined];
// Message: @template D not in use

/**
* @template D
* @template V
* @typedef {[undefined]} Pairs
*/
// Settings: {"jsdoc":{"mode":"permissive"}}
// Message: @template D not in use

/**
* @template D, U, V
*/
export type Extras<D, U> = [D, U | undefined];
// Message: @template V not in use

/**
* @template D, U, V
* @typedef {[D, U | undefined]} Extras
*/
// Message: @template V not in use
````



<a name="user-content-check-template-names-passing-examples"></a>
<a name="check-template-names-passing-examples"></a>
## Passing examples

The following patterns are not considered problems:

````js
/**
* @template D
* @template V
*/
export type Pairs<D, V> = [D, V | undefined];

/**
* @template D
* @template V
* @typedef {[D, V | undefined]} Pairs
*/

/**
* @template D, U, V
*/
export type Extras<D, U, V> = [D, U, V | undefined];

/**
* @template D, U, V
* @typedef {[D, U, V | undefined]} Extras
*/

/**
* @template X
* @typedef {[D, U, V | undefined]} Extras
* @typedef {[D, U, V | undefined]} Extras
*/
````

3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import checkParamNames from './rules/checkParamNames.js';
import checkPropertyNames from './rules/checkPropertyNames.js';
import checkSyntax from './rules/checkSyntax.js';
import checkTagNames from './rules/checkTagNames.js';
import checkTemplateNames from './rules/checkTemplateNames.js';
import checkTypes from './rules/checkTypes.js';
import checkValues from './rules/checkValues.js';
import convertToJsdocComments from './rules/convertToJsdocComments.js';
Expand Down Expand Up @@ -81,6 +82,7 @@ const index = {
'check-property-names': checkPropertyNames,
'check-syntax': checkSyntax,
'check-tag-names': checkTagNames,
'check-template-names': checkTemplateNames,
'check-types': checkTypes,
'check-values': checkValues,
'convert-to-jsdoc-comments': convertToJsdocComments,
Expand Down Expand Up @@ -155,6 +157,7 @@ const createRecommendedRuleset = (warnOrError, flatName) => {
'jsdoc/check-property-names': warnOrError,
'jsdoc/check-syntax': 'off',
'jsdoc/check-tag-names': warnOrError,
'jsdoc/check-template-names': 'off',
'jsdoc/check-types': warnOrError,
'jsdoc/check-values': warnOrError,
'jsdoc/convert-to-jsdoc-comments': 'off',
Expand Down
101 changes: 101 additions & 0 deletions src/rules/checkTemplateNames.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
parse as parseType,
traverse,
tryParse as tryParseType,
} from '@es-joy/jsdoccomment';
import iterateJsdoc from '../iterateJsdoc.js';

export default iterateJsdoc(({
context,
utils,
node,
settings,
report,
}) => {
const {
mode
} = settings;

const templateTags = utils.getTags('template');

const usedNames = new Set();
/**
* @param {import('@typescript-eslint/types').TSESTree.TSTypeAliasDeclaration} aliasDeclaration
*/
const checkParameters = (aliasDeclaration) => {
/* c8 ignore next -- Guard */
const {params} = aliasDeclaration.typeParameters ?? {params: []};
for (const {name: {name}} of params) {
usedNames.add(name);
}
for (const tag of templateTags) {
const {name} = tag;
const names = name.split(/,\s*/);
for (const name of names) {
if (!usedNames.has(name)) {
report(`@template ${name} not in use`, null, tag);
}
}
}
};

const handleTypeAliases = () => {
const nde = /** @type {import('@typescript-eslint/types').TSESTree.Node} */ (
node
);
if (!nde) {
return;
}
switch (nde.type) {
case 'ExportNamedDeclaration':
if (nde.declaration?.type === 'TSTypeAliasDeclaration') {
checkParameters(nde.declaration);
}
break;
case 'TSTypeAliasDeclaration':
checkParameters(nde);
break;
}
};

const typedefTags = utils.getTags('typedef');
if (!typedefTags.length || typedefTags.length >= 2) {
handleTypeAliases();
return;
}

const potentialType = typedefTags[0].type;
const parsedType = mode === 'permissive' ?
tryParseType(/** @type {string} */ (potentialType)) :
parseType(/** @type {string} */ (potentialType), mode)

traverse(parsedType, (nde) => {
const {
type,
value,
} = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde);
if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) {
usedNames.add(value);
}
});

for (const tag of templateTags) {
const {name} = tag;
const names = name.split(/,\s*/);
for (const name of names) {
if (!usedNames.has(name)) {
report(`@template ${name} not in use`, null, tag);
}
}
}
}, {
iterateAllJsdocs: true,
meta: {
docs: {
description: 'Checks that any `@template` names are actually used in the connected `@typedef` or type alias.',
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-template.md#repos-sticky-header',
},
schema: [],
type: 'suggestion',
},
});
Loading

0 comments on commit 962d99d

Please sign in to comment.