Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .README/rules/no-undefined-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`|


Expand Down
22 changes: 20 additions & 2 deletions docs/rules/no-undefined-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# <code>no-undefined-types</code>

* [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)
Expand Down Expand Up @@ -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.

<a name="user-content-no-undefined-types-options-checkusedtypedefs"></a>
<a name="no-undefined-types-options-checkusedtypedefs"></a>
### <code>checkUsedTypedefs</code>

Whether to check typedefs for use within the file
<a name="user-content-no-undefined-types-options-definedtypes"></a>
<a name="no-undefined-types-options-definedtypes"></a>
### <code>definedTypes</code>
Expand All @@ -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.
<a name="user-content-no-undefined-types-options-markvariablesasused"></a>
<a name="no-undefined-types-options-markvariablesasused"></a>
### <code>markVariablesAsUsed</code>
Expand All @@ -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`|


Expand Down Expand Up @@ -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
````


Expand Down Expand Up @@ -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}]
````

43 changes: 24 additions & 19 deletions src/iterateJsdoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@
* parseClosureTemplateTag: ParseClosureTemplateTag,
* getPreferredTagNameObject: GetPreferredTagNameObject,
* pathDoesNotBeginWith: import('./jsdocUtils.js').PathDoesNotBeginWith
* isNamepathDefiningTag: IsNamepathX,
* isNamepathReferencingTag: IsNamepathX,
* isNamepathOrUrlReferencingTag: IsNamepathX,
* tagMightHaveNamepath: IsNamepathX,
* }} BasicUtils
*/

Expand Down Expand Up @@ -566,7 +570,8 @@
* hasNonComment: number,
* hasNonCommentBeforeTag: {
* [key: string]: boolean|number
* }
* },
* foundTypedefValues: string[]
* }} StateObject
*/

Expand Down Expand Up @@ -599,6 +604,24 @@
/** @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({
Expand Down Expand Up @@ -1109,7 +1132,7 @@
src.number = firstNumber + /** @type {Integer} */ (lastIndex) + idx;
}

// Todo: Once rewiring of tags may be fixed in comment-parser to reflect

Check warning on line 1135 in src/iterateJsdoc.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected 'todo' comment: 'Todo: Once rewiring of tags may be fixed...'
// missing tags, this step should be added here (so that, e.g.,
// if accessing `jsdoc.tags`, such as to add a new tag, the
// correct information will be available)
Expand Down Expand Up @@ -1543,24 +1566,6 @@
};
}

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);
Expand Down
6 changes: 5 additions & 1 deletion src/rules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.).
Expand All @@ -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;
/**
Expand Down
88 changes: 82 additions & 6 deletions src/rules/noUndefinedTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,12 @@ export default iterateJsdoc(({
report,
settings,
sourceCode,
state,
utils,
}) => {
/** @type {string[]} */
const foundTypedefValues = [];

const {
scopeManager,
} = sourceCode;
Expand All @@ -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,
Expand Down Expand Up @@ -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;
});
Expand Down Expand Up @@ -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';
});
};
Expand Down Expand Up @@ -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: {
Expand All @@ -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.).
Expand All @@ -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: {
Expand Down
31 changes: 31 additions & 0 deletions test/rules/assertions/noUndefinedTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
{
Expand Down Expand Up @@ -1770,5 +1789,17 @@ export default /** @type {import('../index.js').TestCases} */ ({
function a (b, c) {}
`,
},
{
code: `
/** @typedef {string} SomeType */

/** @type {SomeType} */
`,
options: [
{
checkUsedTypedefs: true,
},
],
},
],
});
Loading