Skip to content

Commit 69c3e49

Browse files
committed
feat(no-undefined-types): checkUsedTypedefs option; fixes #1165
1 parent 376d583 commit 69c3e49

File tree

6 files changed

+163
-29
lines changed

6 files changed

+163
-29
lines changed

.README/rules/no-undefined-types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ array's items will be considered as defined for the purposes of that tag.
5858
|Aliases|`constructor`, `const`, `extends`, `var`, `arg`, `argument`, `prop`, `return`, `exception`, `yield`|
5959
|Closure-only|`package`, `private`, `protected`, `public`, `static`|
6060
|Recommended|true|
61-
|Options|`definedTypes`, `disableReporting`, `markVariablesAsUsed`|
61+
|Options|`checkUsedTypedefs`, `definedTypes`, `disableReporting`, `markVariablesAsUsed`|
6262
|Settings|`preferredTypes`, `mode`, `structuredTags`|
6363

6464

docs/rules/no-undefined-types.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# <code>no-undefined-types</code>
44

55
* [Options](#user-content-no-undefined-types-options)
6+
* [`checkUsedTypedefs`](#user-content-no-undefined-types-options-checkusedtypedefs)
67
* [`definedTypes`](#user-content-no-undefined-types-options-definedtypes)
78
* [`disableReporting`](#user-content-no-undefined-types-options-disablereporting)
89
* [`markVariablesAsUsed`](#user-content-no-undefined-types-options-markvariablesasused)
@@ -60,6 +61,11 @@ array's items will be considered as defined for the purposes of that tag.
6061

6162
A single options object has the following properties.
6263

64+
<a name="user-content-no-undefined-types-options-checkusedtypedefs"></a>
65+
<a name="no-undefined-types-options-checkusedtypedefs"></a>
66+
### <code>checkUsedTypedefs</code>
67+
68+
Whether to check typedefs for use within the file
6369
<a name="user-content-no-undefined-types-options-definedtypes"></a>
6470
<a name="no-undefined-types-options-definedtypes"></a>
6571
### <code>definedTypes</code>
@@ -73,7 +79,7 @@ Defaults to an empty array.
7379

7480
Whether to disable reporting of errors. Defaults to
7581
`false`. This may be set to `true` in order to take advantage of only
76-
marking defined variables as used.
82+
marking defined variables as used or checking used typedefs.
7783
<a name="user-content-no-undefined-types-options-markvariablesasused"></a>
7884
<a name="no-undefined-types-options-markvariablesasused"></a>
7985
### <code>markVariablesAsUsed</code>
@@ -95,7 +101,7 @@ importing types unless used in code.
95101
|Aliases|`constructor`, `const`, `extends`, `var`, `arg`, `argument`, `prop`, `return`, `exception`, `yield`|
96102
|Closure-only|`package`, `private`, `protected`, `public`, `static`|
97103
|Recommended|true|
98-
|Options|`definedTypes`, `disableReporting`, `markVariablesAsUsed`|
104+
|Options|`checkUsedTypedefs`, `definedTypes`, `disableReporting`, `markVariablesAsUsed`|
99105
|Settings|`preferredTypes`, `mode`, `structuredTags`|
100106

101107

@@ -368,6 +374,13 @@ class Filler {
368374
methodThree() {}
369375
}
370376
// Message: The type 'Filler.methodTwo' is undefined.
377+
378+
/** @typedef {string} SomeType */
379+
/** @typedef {number} AnotherType */
380+
381+
/** @type {AnotherType} */
382+
// "jsdoc/no-undefined-types": ["error"|"warn", {"checkUsedTypedefs":true}]
383+
// Message: This typedef was not used within the file
371384
````
372385

373386

@@ -1029,5 +1042,10 @@ function f(a) {}
10291042
* @param {helperError} c
10301043
*/
10311044
function a (b, c) {}
1045+
1046+
/** @typedef {string} SomeType */
1047+
1048+
/** @type {SomeType} */
1049+
// "jsdoc/no-undefined-types": ["error"|"warn", {"checkUsedTypedefs":true}]
10321050
````
10331051

src/iterateJsdoc.js

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ import esquery from 'esquery';
9292
* parseClosureTemplateTag: ParseClosureTemplateTag,
9393
* getPreferredTagNameObject: GetPreferredTagNameObject,
9494
* pathDoesNotBeginWith: import('./jsdocUtils.js').PathDoesNotBeginWith
95+
* isNamepathDefiningTag: IsNamepathX,
96+
* isNamepathReferencingTag: IsNamepathX,
97+
* isNamepathOrUrlReferencingTag: IsNamepathX,
98+
* tagMightHaveNamepath: IsNamepathX,
9599
* }} BasicUtils
96100
*/
97101

@@ -566,7 +570,8 @@ const {
566570
* hasNonComment: number,
567571
* hasNonCommentBeforeTag: {
568572
* [key: string]: boolean|number
569-
* }
573+
* },
574+
* foundTypedefValues: string[]
570575
* }} StateObject
571576
*/
572577

@@ -599,6 +604,24 @@ const getBasicUtils = (context, {
599604
/** @type {BasicUtils} */
600605
const utils = {};
601606

607+
for (const method of [
608+
'isNamepathDefiningTag',
609+
'isNamepathReferencingTag',
610+
'isNamepathOrUrlReferencingTag',
611+
'tagMightHaveNamepath',
612+
]) {
613+
/** @type {IsNamepathX} */
614+
utils[
615+
/** @type {"isNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNamepath"} */ (
616+
method
617+
)] = (tagName) => {
618+
return jsdocUtils[
619+
/** @type {"isNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNamepath"} */
620+
(method)
621+
](tagName);
622+
};
623+
}
624+
602625
/** @type {ReportSettings} */
603626
utils.reportSettings = (message) => {
604627
context.report({
@@ -1543,24 +1566,6 @@ const getUtils = (
15431566
};
15441567
}
15451568

1546-
for (const method of [
1547-
'isNamepathDefiningTag',
1548-
'isNamepathReferencingTag',
1549-
'isNamepathOrUrlReferencingTag',
1550-
'tagMightHaveNamepath',
1551-
]) {
1552-
/** @type {IsNamepathX} */
1553-
utils[
1554-
/** @type {"isNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNamepath"} */ (
1555-
method
1556-
)] = (tagName) => {
1557-
return jsdocUtils[
1558-
/** @type {"isNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNamepath"} */
1559-
(method)
1560-
](tagName);
1561-
};
1562-
}
1563-
15641569
/** @type {GetTagStructureForMode} */
15651570
utils.getTagStructureForMode = (mde) => {
15661571
return jsdocUtils.getTagStructureForMode(mde, settings.structuredTags);

src/rules.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1250,6 +1250,10 @@ export interface Rules {
12501250
| []
12511251
| [
12521252
{
1253+
/**
1254+
* Whether to check typedefs for use within the file
1255+
*/
1256+
checkUsedTypedefs?: boolean;
12531257
/**
12541258
* This array can be populated to indicate other types which
12551259
* are automatically considered as defined (in addition to globals, etc.).
@@ -1259,7 +1263,7 @@ export interface Rules {
12591263
/**
12601264
* Whether to disable reporting of errors. Defaults to
12611265
* `false`. This may be set to `true` in order to take advantage of only
1262-
* marking defined variables as used.
1266+
* marking defined variables as used or checking used typedefs.
12631267
*/
12641268
disableReporting?: boolean;
12651269
/**

src/rules/noUndefinedTypes.js

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,12 @@ export default iterateJsdoc(({
5959
report,
6060
settings,
6161
sourceCode,
62+
state,
6263
utils,
6364
}) => {
65+
/** @type {string[]} */
66+
const foundTypedefValues = [];
67+
6468
const {
6569
scopeManager,
6670
} = sourceCode;
@@ -73,11 +77,13 @@ export default iterateJsdoc(({
7377
const
7478
/**
7579
* @type {{
80+
* checkUsedTypedefs: boolean
7681
* definedTypes: string[],
7782
* disableReporting: boolean,
78-
* markVariablesAsUsed: boolean
83+
* markVariablesAsUsed: boolean,
7984
* }}
8085
*/ {
86+
checkUsedTypedefs = false,
8187
definedTypes = [],
8288
disableReporting = false,
8389
markVariablesAsUsed = true,
@@ -128,14 +134,16 @@ export default iterateJsdoc(({
128134
return commentNode.value.replace(/^\s*globals/v, '').trim().split(/,\s*/v);
129135
}).concat(Object.keys(context.languageOptions.globals ?? []));
130136

131-
const typedefDeclarations = comments
137+
const typedefs = comments
132138
.flatMap((doc) => {
133139
return doc.tags.filter(({
134140
tag,
135141
}) => {
136142
return utils.isNamepathDefiningTag(tag);
137143
});
138-
})
144+
});
145+
146+
const typedefDeclarations = typedefs
139147
.map((tag) => {
140148
return tag.name;
141149
});
@@ -204,9 +212,9 @@ export default iterateJsdoc(({
204212
return [];
205213
}
206214

207-
const jsdoc = parseComment(commentNode, '');
215+
const jsdc = parseComment(commentNode, '');
208216

209-
return jsdoc.tags.filter((tag) => {
217+
return jsdc.tags.filter((tag) => {
210218
return tag.tag === 'template';
211219
});
212220
};
@@ -510,10 +518,74 @@ export default iterateJsdoc(({
510518
context.markVariableAsUsed(val);
511519
}
512520
}
521+
522+
if (checkUsedTypedefs && typedefDeclarations.includes(val)) {
523+
foundTypedefValues.push(val);
524+
}
513525
}
514526
});
515527
}
528+
529+
state.foundTypedefValues = foundTypedefValues;
516530
}, {
531+
// We use this method rather than checking at end of handler above because
532+
// in that case, it is invoked too many times and would thus report errors
533+
// too many times.
534+
exit ({
535+
context,
536+
state,
537+
utils,
538+
}) {
539+
const {
540+
checkUsedTypedefs = false,
541+
} = context.options[0] || {};
542+
543+
if (!checkUsedTypedefs) {
544+
return;
545+
}
546+
547+
const allComments = context.sourceCode.getAllComments();
548+
const comments = allComments
549+
.filter((comment) => {
550+
return (/^\*(?!\*)/v).test(comment.value);
551+
})
552+
.map((commentNode) => {
553+
return {
554+
doc: parseComment(commentNode, ''),
555+
loc: commentNode.loc,
556+
};
557+
});
558+
const typedefs = comments
559+
.flatMap(({
560+
doc,
561+
loc,
562+
}) => {
563+
const tags = doc.tags.filter(({
564+
tag,
565+
}) => {
566+
return utils.isNamepathDefiningTag(tag);
567+
});
568+
if (!tags.length) {
569+
return [];
570+
}
571+
572+
return {
573+
loc,
574+
tags,
575+
};
576+
});
577+
578+
for (const typedef of typedefs) {
579+
if (
580+
!state.foundTypedefValues.includes(typedef.tags[0].name)
581+
) {
582+
context.report({
583+
loc: /** @type {import('@eslint/core').SourceLocation} */ (typedef.loc),
584+
message: 'This typedef was not used within the file',
585+
});
586+
}
587+
}
588+
},
517589
iterateAllJsdocs: true,
518590
meta: {
519591
docs: {
@@ -524,6 +596,10 @@ export default iterateJsdoc(({
524596
{
525597
additionalProperties: false,
526598
properties: {
599+
checkUsedTypedefs: {
600+
description: 'Whether to check typedefs for use within the file',
601+
type: 'boolean',
602+
},
527603
definedTypes: {
528604
description: `This array can be populated to indicate other types which
529605
are automatically considered as defined (in addition to globals, etc.).
@@ -536,7 +612,7 @@ Defaults to an empty array.`,
536612
disableReporting: {
537613
description: `Whether to disable reporting of errors. Defaults to
538614
\`false\`. This may be set to \`true\` in order to take advantage of only
539-
marking defined variables as used.`,
615+
marking defined variables as used or checking used typedefs.`,
540616
type: 'boolean',
541617
},
542618
markVariablesAsUsed: {

test/rules/assertions/noUndefinedTypes.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,25 @@ export default /** @type {import('../index.js').TestCases} */ ({
651651
],
652652
ignoreReadme: true,
653653
},
654+
{
655+
code: `
656+
/** @typedef {string} SomeType */
657+
/** @typedef {number} AnotherType */
658+
659+
/** @type {AnotherType} */
660+
`,
661+
errors: [
662+
{
663+
line: 2,
664+
message: 'This typedef was not used within the file',
665+
},
666+
],
667+
options: [
668+
{
669+
checkUsedTypedefs: true,
670+
},
671+
],
672+
},
654673
],
655674
valid: [
656675
{
@@ -1770,5 +1789,17 @@ export default /** @type {import('../index.js').TestCases} */ ({
17701789
function a (b, c) {}
17711790
`,
17721791
},
1792+
{
1793+
code: `
1794+
/** @typedef {string} SomeType */
1795+
1796+
/** @type {SomeType} */
1797+
`,
1798+
options: [
1799+
{
1800+
checkUsedTypedefs: true,
1801+
},
1802+
],
1803+
},
17731804
],
17741805
});

0 commit comments

Comments
 (0)