Skip to content

Adding a used-types rule #1058

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
2 changes: 2 additions & 0 deletions .README/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Finally, enable all of the rules that you would like to use.
"jsdoc/empty-tags": 1, // Recommended
"jsdoc/implements-on-classes": 1, // Recommended
"jsdoc/informative-docs": 1,
"jsdoc/used-types": 1,
"jsdoc/match-description": 1,
"jsdoc/multiline-blocks": 1, // Recommended
"jsdoc/no-bad-blocks": 1,
Expand Down Expand Up @@ -240,4 +241,5 @@ Problems reported by rules which have a wrench :wrench: below can be fixed autom
|||[sort-tags](./docs/rules/sort-tags.md#readme)|Sorts tags by a specified sequence according to tag name, optionally adding line breaks between tag groups|
|:heavy_check_mark:|:wrench:|[tag-lines](./docs/rules/tag-lines.md#readme)|Enforces lines (or no lines) between tags|
||:wrench:|[text-escaping](./docs/rules/text-escaping.md#readme)|This rule can auto-escape certain characters that are input within block and tag descriptions|
|||[used-types](./docs/rules/used-types.md#readme)|Marks all types referenced from JSDoc tags as used.|
|:heavy_check_mark:||[valid-types](./docs/rules/valid-types.md#readme)|Requires all types/namepaths to be valid JSDoc, Closure compiler, or TypeScript types (configurable in settings)|
27 changes: 27 additions & 0 deletions .README/rules/used-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# `used-types`

{"gitdown": "contents", "rootId": "used-types"}

Marks all types referenced from JSDoc tags as used.

## Fixer

Not applicable.

#### Options

|||
|---|---|
|Context|everywhere|
|Tags|N/A|
|Recommended|false|
|Settings||
|Options||

## Failing examples

<!-- assertions-failing usedTypes -->

## Passing examples

<!-- assertions-passing usedTypes -->
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Finally, enable all of the rules that you would like to use.
"jsdoc/empty-tags": 1, // Recommended
"jsdoc/implements-on-classes": 1, // Recommended
"jsdoc/informative-docs": 1,
"jsdoc/used-types": 1,
"jsdoc/match-description": 1,
"jsdoc/multiline-blocks": 1, // Recommended
"jsdoc/no-bad-blocks": 1,
Expand Down Expand Up @@ -261,4 +262,5 @@ Problems reported by rules which have a wrench :wrench: below can be fixed autom
|||[sort-tags](./docs/rules/sort-tags.md#readme)|Sorts tags by a specified sequence according to tag name, optionally adding line breaks between tag groups|
|:heavy_check_mark:|:wrench:|[tag-lines](./docs/rules/tag-lines.md#readme)|Enforces lines (or no lines) between tags|
||:wrench:|[text-escaping](./docs/rules/text-escaping.md#readme)|This rule can auto-escape certain characters that are input within block and tag descriptions|
|||[used-types](./docs/rules/used-types.md#readme)|Marks all types referenced from JSDoc tags as used.|
|:heavy_check_mark:||[valid-types](./docs/rules/valid-types.md#readme)|Requires all types/namepaths to be valid JSDoc, Closure compiler, or TypeScript types (configurable in settings)|
59 changes: 59 additions & 0 deletions docs/rules/used-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<a name="user-content-used-types"></a>
<a name="used-types"></a>
# <code>used-types</code>

* [Fixer](#user-content-used-types-fixer)
* [Failing examples](#user-content-used-types-failing-examples)
* [Passing examples](#user-content-used-types-passing-examples)


Marks all types referenced from JSDoc tags as used.

<a name="user-content-used-types-fixer"></a>
<a name="used-types-fixer"></a>
## Fixer

Not applicable.

<a name="user-content-used-types-fixer-options"></a>
<a name="used-types-fixer-options"></a>
#### Options

|||
|---|---|
|Context|everywhere|
|Tags|N/A|
|Recommended|false|
|Settings||
|Options||

<a name="user-content-used-types-failing-examples"></a>
<a name="used-types-failing-examples"></a>
## Failing examples

The following patterns are considered problems:

````js

````



<a name="user-content-used-types-passing-examples"></a>
<a name="used-types-passing-examples"></a>
## Passing examples

The following patterns are not considered problems:

````js
class Foo {}
/** @param {Foo} */
function foo() {}
foo();

class Foo {}
/** @returns {Foo} */
function foo() {}
foo();
````

2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"debug": "^4.3.4",
"escape-string-regexp": "^4.0.0",
"esquery": "^1.5.0",
"jsdoc-type-pratt-parser": "^4.0.0",
"semver": "^7.5.0",
"spdx-expression-parse": "^3.0.1"
},
Expand Down Expand Up @@ -41,7 +42,6 @@
"gitdown": "^3.1.5",
"glob": "^10.2.2",
"husky": "^8.0.3",
"jsdoc-type-pratt-parser": "^4.0.0",
"lint-staged": "^13.2.2",
"lodash.defaultsdeep": "^4.6.1",
"mocha": "^10.2.0",
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import requireYieldsCheck from './rules/requireYieldsCheck';
import sortTags from './rules/sortTags';
import tagLines from './rules/tagLines';
import textEscaping from './rules/textEscaping';
import usedTypes from './rules/usedTypes';
import validTypes from './rules/validTypes';

const index = {
Expand Down Expand Up @@ -105,6 +106,7 @@ const index = {
'sort-tags': sortTags,
'tag-lines': tagLines,
'text-escaping': textEscaping,
'used-types': usedTypes,
'valid-types': validTypes,
},
};
Expand Down
100 changes: 100 additions & 0 deletions src/rules/usedTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
parse,
} from 'jsdoc-type-pratt-parser';
import iterateJsdoc from '../iterateJsdoc';

/**
* Extracts the type names from parsed type declaration.
*/
const extractTypeNames = (parsed) => { // eslint-disable-line complexity
if (typeof parsed !== 'object') {
return [];
}

switch (parsed.type) {
case 'JsdocTypeName':
return [
parsed.value,
];
case 'JsdocTypeOptional':
case 'JsdocTypeNullable':
case 'JsdocTypeNotNullable':
case 'JsdocTypeTypeof':
case 'JsdocTypeKeyof':
case 'JsdocTypeParenthesis':
case 'JsdocTypeVariadic':
return extractTypeNames(parsed.element);
case 'JsdocTypeUnion':
case 'JsdocTypeObject':
case 'JsdocTypeTuple':
case 'JsdocTypeIntersection':
return parsed.elements.flatMap(extractTypeNames);
case 'JsdocTypeGeneric':
return [
...extractTypeNames(parsed.left),
...parsed.elements.flatMap(extractTypeNames),
];
case 'JsdocTypeFunction':
return [
...parsed.parameters.flatMap(extractTypeNames),
...extractTypeNames(parsed.returnType),
];
case 'JsdocTypeNamePath':
return extractTypeNames(parsed.left);
case 'JsdocTypePredicate':
// We purposefully don't consider the left (subject of the predicate) used
return extractTypeNames(parsed.right);
case 'JsdocTypeObjectField':
return [
...extractTypeNames(parsed.key),
...extractTypeNames(parsed.right),
];
case 'JsdocTypeJsdocObjectField':
return [
...extractTypeNames(parsed.left),
...extractTypeNames(parsed.right),
];
case 'JsdocTypeKeyValue':
case 'JsdocTypeIndexSignature':
case 'JsdocTypeMappedType':
return extractTypeNames(parsed.right);
default:
return [];
}
};

export default iterateJsdoc(({
jsdoc,
jsdocNode,
context,
settings,
}) => {
const {
mode,
} = settings;

const sourceCode = context.getSourceCode();
for (const tag of jsdoc.tags) {
const parsedType = parse(tag.type, mode);
const typeNames = extractTypeNames(parsedType);
for (const typeName of typeNames) {
sourceCode.markVariableAsUsed(typeName, jsdocNode);
}
}
}, {
iterateAllJsdocs: true,
meta: {
docs: {
description: 'Marks all types referenced from JSDoc tags as used.',
url: 'https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-used-types',
},
fixable: 'code',
schema: [
{
additionalProperties: false,
properties: {},
},
],
type: 'suggestion',
},
});
68 changes: 68 additions & 0 deletions test/rules/assertions/usedTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
export default {
invalid: [],
valid: [
// {
// code: `
// const foo = "bar";
// /** This thing uses {@link foo} for something */
// `,
// /*
// rules: {
// 'no-unused-vars': 'error',
// },
// */
// },
{
code: `
class Foo {}
/** @param {Foo} */
function foo() {}
foo();
`,
rules: {
'no-unused-vars': 'error',
},
},
{
code: `
class Foo {}
/** @returns {Foo} */
function foo() {}
foo();
`,
rules: {
'no-unused-vars': 'error',
},
},
{
code: `
class Foo {}
class Bar {}
class Baz {}
class Qux {}
/** @type {(!Foo|?Bar|...Baz|Qux[]|foo=|obj["level1"]|{Foo?: Foo}|function(this:Foo))|external:something} */
let foo = null;
`,
ignoreReadme: true,
rules: {
'no-unused-vars': 'error',
},
},
{
code: `
class Foo {}
/** @type {typeof foo|import("some-package")|new(number, string): Foo|foo is Foo|{foo: Foo}} */
let foo = null;
`,
ignoreReadme: true,
rules: {
'no-unused-vars': 'error',
},
settings: {
jsdoc: {
mode: 'typescript',
},
},
},
],
};
1 change: 1 addition & 0 deletions test/rules/ruleNames.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"empty-tags",
"implements-on-classes",
"informative-docs",
"used-types",
"match-description",
"match-name",
"multiline-blocks",
Expand Down