diff --git a/.README/rules/no-undefined-types.md b/.README/rules/no-undefined-types.md
index 9c0828b91..9fe06e459 100644
--- a/.README/rules/no-undefined-types.md
+++ b/.README/rules/no-undefined-types.md
@@ -58,7 +58,7 @@ array's items will be considered as defined for the purposes of that tag.
|Aliases|`constructor`, `const`, `extends`, `var`, `arg`, `argument`, `prop`, `return`, `exception`, `yield`|
|Closure-only|`package`, `private`, `protected`, `public`, `static`|
|Recommended|true|
-|Options|`definedTypes`, `disableReporting`, `markVariablesAsUsed`|
+|Options|`checkUsedTypedefs`, `definedTypes`, `disableReporting`, `markVariablesAsUsed`|
|Settings|`preferredTypes`, `mode`, `structuredTags`|
diff --git a/docs/rules/no-undefined-types.md b/docs/rules/no-undefined-types.md
index 5c9c6734c..bf45b23ae 100644
--- a/docs/rules/no-undefined-types.md
+++ b/docs/rules/no-undefined-types.md
@@ -3,6 +3,7 @@
# no-undefined-types
* [Options](#user-content-no-undefined-types-options)
+ * [`checkUsedTypedefs`](#user-content-no-undefined-types-options-checkusedtypedefs)
* [`definedTypes`](#user-content-no-undefined-types-options-definedtypes)
* [`disableReporting`](#user-content-no-undefined-types-options-disablereporting)
* [`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.
A single options object has the following properties.
+
+
+### checkUsedTypedefs
+
+Whether to check typedefs for use within the file
### definedTypes
@@ -73,7 +79,7 @@ Defaults to an empty array.
Whether to disable reporting of errors. Defaults to
`false`. This may be set to `true` in order to take advantage of only
-marking defined variables as used.
+marking defined variables as used or checking used typedefs.
### markVariablesAsUsed
@@ -95,7 +101,7 @@ importing types unless used in code.
|Aliases|`constructor`, `const`, `extends`, `var`, `arg`, `argument`, `prop`, `return`, `exception`, `yield`|
|Closure-only|`package`, `private`, `protected`, `public`, `static`|
|Recommended|true|
-|Options|`definedTypes`, `disableReporting`, `markVariablesAsUsed`|
+|Options|`checkUsedTypedefs`, `definedTypes`, `disableReporting`, `markVariablesAsUsed`|
|Settings|`preferredTypes`, `mode`, `structuredTags`|
@@ -368,6 +374,13 @@ class Filler {
methodThree() {}
}
// Message: The type 'Filler.methodTwo' is undefined.
+
+/** @typedef {string} SomeType */
+/** @typedef {number} AnotherType */
+
+/** @type {AnotherType} */
+// "jsdoc/no-undefined-types": ["error"|"warn", {"checkUsedTypedefs":true}]
+// Message: This typedef was not used within the file
````
@@ -1029,5 +1042,10 @@ function f(a) {}
* @param {helperError} c
*/
function a (b, c) {}
+
+/** @typedef {string} SomeType */
+
+/** @type {SomeType} */
+// "jsdoc/no-undefined-types": ["error"|"warn", {"checkUsedTypedefs":true}]
````
diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js
index 91d5b0f5d..b4c116ad7 100644
--- a/src/iterateJsdoc.js
+++ b/src/iterateJsdoc.js
@@ -92,6 +92,10 @@ import esquery from 'esquery';
* parseClosureTemplateTag: ParseClosureTemplateTag,
* getPreferredTagNameObject: GetPreferredTagNameObject,
* pathDoesNotBeginWith: import('./jsdocUtils.js').PathDoesNotBeginWith
+ * isNamepathDefiningTag: IsNamepathX,
+ * isNamepathReferencingTag: IsNamepathX,
+ * isNamepathOrUrlReferencingTag: IsNamepathX,
+ * tagMightHaveNamepath: IsNamepathX,
* }} BasicUtils
*/
@@ -566,7 +570,8 @@ const {
* hasNonComment: number,
* hasNonCommentBeforeTag: {
* [key: string]: boolean|number
- * }
+ * },
+ * foundTypedefValues: string[]
* }} StateObject
*/
@@ -599,6 +604,24 @@ const getBasicUtils = (context, {
/** @type {BasicUtils} */
const utils = {};
+ for (const method of [
+ 'isNamepathDefiningTag',
+ 'isNamepathReferencingTag',
+ 'isNamepathOrUrlReferencingTag',
+ 'tagMightHaveNamepath',
+ ]) {
+ /** @type {IsNamepathX} */
+ utils[
+ /** @type {"isNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNamepath"} */ (
+ method
+ )] = (tagName) => {
+ return jsdocUtils[
+ /** @type {"isNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNamepath"} */
+ (method)
+ ](tagName);
+ };
+ }
+
/** @type {ReportSettings} */
utils.reportSettings = (message) => {
context.report({
@@ -1543,24 +1566,6 @@ const getUtils = (
};
}
- for (const method of [
- 'isNamepathDefiningTag',
- 'isNamepathReferencingTag',
- 'isNamepathOrUrlReferencingTag',
- 'tagMightHaveNamepath',
- ]) {
- /** @type {IsNamepathX} */
- utils[
- /** @type {"isNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNamepath"} */ (
- method
- )] = (tagName) => {
- return jsdocUtils[
- /** @type {"isNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNamepath"} */
- (method)
- ](tagName);
- };
- }
-
/** @type {GetTagStructureForMode} */
utils.getTagStructureForMode = (mde) => {
return jsdocUtils.getTagStructureForMode(mde, settings.structuredTags);
diff --git a/src/rules.d.ts b/src/rules.d.ts
index eb187a673..728c09221 100644
--- a/src/rules.d.ts
+++ b/src/rules.d.ts
@@ -1250,6 +1250,10 @@ export interface Rules {
| []
| [
{
+ /**
+ * Whether to check typedefs for use within the file
+ */
+ checkUsedTypedefs?: boolean;
/**
* This array can be populated to indicate other types which
* are automatically considered as defined (in addition to globals, etc.).
@@ -1259,7 +1263,7 @@ export interface Rules {
/**
* Whether to disable reporting of errors. Defaults to
* `false`. This may be set to `true` in order to take advantage of only
- * marking defined variables as used.
+ * marking defined variables as used or checking used typedefs.
*/
disableReporting?: boolean;
/**
diff --git a/src/rules/noUndefinedTypes.js b/src/rules/noUndefinedTypes.js
index 3ef70fb14..8ba892379 100644
--- a/src/rules/noUndefinedTypes.js
+++ b/src/rules/noUndefinedTypes.js
@@ -59,8 +59,12 @@ export default iterateJsdoc(({
report,
settings,
sourceCode,
+ state,
utils,
}) => {
+ /** @type {string[]} */
+ const foundTypedefValues = [];
+
const {
scopeManager,
} = sourceCode;
@@ -73,11 +77,13 @@ export default iterateJsdoc(({
const
/**
* @type {{
+ * checkUsedTypedefs: boolean
* definedTypes: string[],
* disableReporting: boolean,
- * markVariablesAsUsed: boolean
+ * markVariablesAsUsed: boolean,
* }}
*/ {
+ checkUsedTypedefs = false,
definedTypes = [],
disableReporting = false,
markVariablesAsUsed = true,
@@ -128,14 +134,16 @@ export default iterateJsdoc(({
return commentNode.value.replace(/^\s*globals/v, '').trim().split(/,\s*/v);
}).concat(Object.keys(context.languageOptions.globals ?? []));
- const typedefDeclarations = comments
+ const typedefs = comments
.flatMap((doc) => {
return doc.tags.filter(({
tag,
}) => {
return utils.isNamepathDefiningTag(tag);
});
- })
+ });
+
+ const typedefDeclarations = typedefs
.map((tag) => {
return tag.name;
});
@@ -204,9 +212,9 @@ export default iterateJsdoc(({
return [];
}
- const jsdoc = parseComment(commentNode, '');
+ const jsdc = parseComment(commentNode, '');
- return jsdoc.tags.filter((tag) => {
+ return jsdc.tags.filter((tag) => {
return tag.tag === 'template';
});
};
@@ -510,10 +518,74 @@ export default iterateJsdoc(({
context.markVariableAsUsed(val);
}
}
+
+ if (checkUsedTypedefs && typedefDeclarations.includes(val)) {
+ foundTypedefValues.push(val);
+ }
}
});
}
+
+ state.foundTypedefValues = foundTypedefValues;
}, {
+ // We use this method rather than checking at end of handler above because
+ // in that case, it is invoked too many times and would thus report errors
+ // too many times.
+ exit ({
+ context,
+ state,
+ utils,
+ }) {
+ const {
+ checkUsedTypedefs = false,
+ } = context.options[0] || {};
+
+ if (!checkUsedTypedefs) {
+ return;
+ }
+
+ const allComments = context.sourceCode.getAllComments();
+ const comments = allComments
+ .filter((comment) => {
+ return (/^\*(?!\*)/v).test(comment.value);
+ })
+ .map((commentNode) => {
+ return {
+ doc: parseComment(commentNode, ''),
+ loc: commentNode.loc,
+ };
+ });
+ const typedefs = comments
+ .flatMap(({
+ doc,
+ loc,
+ }) => {
+ const tags = doc.tags.filter(({
+ tag,
+ }) => {
+ return utils.isNamepathDefiningTag(tag);
+ });
+ if (!tags.length) {
+ return [];
+ }
+
+ return {
+ loc,
+ tags,
+ };
+ });
+
+ for (const typedef of typedefs) {
+ if (
+ !state.foundTypedefValues.includes(typedef.tags[0].name)
+ ) {
+ context.report({
+ loc: /** @type {import('@eslint/core').SourceLocation} */ (typedef.loc),
+ message: 'This typedef was not used within the file',
+ });
+ }
+ }
+ },
iterateAllJsdocs: true,
meta: {
docs: {
@@ -524,6 +596,10 @@ export default iterateJsdoc(({
{
additionalProperties: false,
properties: {
+ checkUsedTypedefs: {
+ description: 'Whether to check typedefs for use within the file',
+ type: 'boolean',
+ },
definedTypes: {
description: `This array can be populated to indicate other types which
are automatically considered as defined (in addition to globals, etc.).
@@ -536,7 +612,7 @@ Defaults to an empty array.`,
disableReporting: {
description: `Whether to disable reporting of errors. Defaults to
\`false\`. This may be set to \`true\` in order to take advantage of only
-marking defined variables as used.`,
+marking defined variables as used or checking used typedefs.`,
type: 'boolean',
},
markVariablesAsUsed: {
diff --git a/test/rules/assertions/noUndefinedTypes.js b/test/rules/assertions/noUndefinedTypes.js
index e8f381004..2c9517cea 100644
--- a/test/rules/assertions/noUndefinedTypes.js
+++ b/test/rules/assertions/noUndefinedTypes.js
@@ -651,6 +651,25 @@ export default /** @type {import('../index.js').TestCases} */ ({
],
ignoreReadme: true,
},
+ {
+ code: `
+ /** @typedef {string} SomeType */
+ /** @typedef {number} AnotherType */
+
+ /** @type {AnotherType} */
+ `,
+ errors: [
+ {
+ line: 2,
+ message: 'This typedef was not used within the file',
+ },
+ ],
+ options: [
+ {
+ checkUsedTypedefs: true,
+ },
+ ],
+ },
],
valid: [
{
@@ -1770,5 +1789,17 @@ export default /** @type {import('../index.js').TestCases} */ ({
function a (b, c) {}
`,
},
+ {
+ code: `
+ /** @typedef {string} SomeType */
+
+ /** @type {SomeType} */
+ `,
+ options: [
+ {
+ checkUsedTypedefs: true,
+ },
+ ],
+ },
],
});