diff --git a/docs/rules/check-template-names.md b/docs/rules/check-template-names.md index 8007ca1d..eecdc360 100644 --- a/docs/rules/check-template-names.md +++ b/docs/rules/check-template-names.md @@ -87,6 +87,14 @@ export type Extras = [D, U | undefined]; * @typedef {[D, U | undefined]} Extras */ // Message: @template V not in use + +/** + * @template D + * @template V + * @typedef Pairs + * @property {V} foo + */ +// Message: @template D not in use ```` @@ -130,5 +138,13 @@ export type Extras = [D, U, V | undefined]; * @typedef Foo * @prop {string} bar */ + +/** + * @template D + * @template V + * @typedef Pairs + * @property {D} foo + * @property {V} bar + */ ```` diff --git a/docs/rules/require-template.md b/docs/rules/require-template.md index 186c9f7a..a701606e 100644 --- a/docs/rules/require-template.md +++ b/docs/rules/require-template.md @@ -106,6 +106,14 @@ export type Pairs = [D, V | undefined]; */ // "jsdoc/require-template": ["error"|"warn", {"requireSeparateTemplates":true}] // Message: Missing separate @template for V + +/** + * @template X + * @typedef {object} Pairs + * @property {D} foo + * @property {X} bar + */ +// Message: Missing @template D ```` @@ -148,5 +156,13 @@ export type Extras = [D, U, V | undefined]; * @typedef Foo * @prop {string} bar */ + +/** + * @template D + * @template V + * @typedef {object} Pairs + * @property {D} foo + * @property {V} bar + */ ```` diff --git a/src/rules/checkTemplateNames.js b/src/rules/checkTemplateNames.js index c84516f9..a7480d0a 100644 --- a/src/rules/checkTemplateNames.js +++ b/src/rules/checkTemplateNames.js @@ -64,27 +64,40 @@ export default iterateJsdoc(({ return; } - const potentialType = typedefTags[0].type; + /** + * @param {string} potentialType + */ + const checkForUsedTypes = (potentialType) => { + let parsedType; + try { + parsedType = mode === 'permissive' ? + tryParseType(/** @type {string} */ (potentialType)) : + parseType(/** @type {string} */ (potentialType), mode); + } catch { + return; + } - let parsedType; - try { - parsedType = mode === 'permissive' ? - tryParseType(/** @type {string} */ (potentialType)) : - parseType(/** @type {string} */ (potentialType), mode) - } catch { - // Todo: Should handle types in @prop/erty - return; - } + traverse(parsedType, (nde) => { + const { + type, + value, + } = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde); + if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) { + usedNames.add(value); + } + }); + }; - traverse(parsedType, (nde) => { - const { - type, - value, - } = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde); - if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) { - usedNames.add(value); - } - }); + const potentialTypedefType = typedefTags[0].type; + checkForUsedTypes(potentialTypedefType); + + const tagName = /** @type {string} */ (utils.getPreferredTagName({ + tagName: 'property', + })); + const propertyTags = utils.getTags(tagName); + for (const propertyTag of propertyTags) { + checkForUsedTypes(propertyTag.type); + } for (const tag of templateTags) { const {name} = tag; diff --git a/src/rules/requireTemplate.js b/src/rules/requireTemplate.js index 0f0730d1..6d4bf3e8 100644 --- a/src/rules/requireTemplate.js +++ b/src/rules/requireTemplate.js @@ -75,32 +75,50 @@ export default iterateJsdoc(({ return; } - const potentialType = typedefTags[0].type; + const usedNameToTag = new Map(); - let parsedType; - try { - parsedType = mode === 'permissive' ? - tryParseType(/** @type {string} */ (potentialType)) : - parseType(/** @type {string} */ (potentialType), mode) - } catch { - // Todo: Should handle types in @prop/erty - return; - } - - traverse(parsedType, (nde) => { - const { - type, - value, - } = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde); - if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) { - usedNames.add(value); + /** + * @param {import('comment-parser').Spec} potentialTag + */ + const checkForUsedTypes = (potentialTag) => { + let parsedType; + try { + parsedType = mode === 'permissive' ? + tryParseType(/** @type {string} */ (potentialTag.type)) : + parseType(/** @type {string} */ (potentialTag.type), mode) + } catch { + return; } - }); + + traverse(parsedType, (nde) => { + const { + type, + value, + } = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde); + if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) { + usedNames.add(value); + if (!usedNameToTag.has(value)) { + usedNameToTag.set(value, potentialTag); + } + } + }); + }; + + const potentialTypedef = typedefTags[0]; + checkForUsedTypes(potentialTypedef); + + const tagName = /** @type {string} */ (utils.getPreferredTagName({ + tagName: 'property', + })); + const propertyTags = utils.getTags(tagName); + for (const propertyTag of propertyTags) { + checkForUsedTypes(propertyTag); + } // Could check against whitelist/blacklist for (const usedName of usedNames) { if (!templateNames.includes(usedName)) { - report(`Missing @template ${usedName}`, null, typedefTags[0]); + report(`Missing @template ${usedName}`, null, usedNameToTag.get(usedName)); } } }, { diff --git a/test/rules/assertions/checkTemplateNames.js b/test/rules/assertions/checkTemplateNames.js index a9bdb794..4d8167a6 100644 --- a/test/rules/assertions/checkTemplateNames.js +++ b/test/rules/assertions/checkTemplateNames.js @@ -142,6 +142,22 @@ export default { }, ], }, + { + code: ` + /** + * @template D + * @template V + * @typedef Pairs + * @property {V} foo + */ + `, + errors: [ + { + line: 3, + message: '@template D not in use', + }, + ], + }, ], valid: [ { @@ -201,5 +217,16 @@ export default { */ `, }, + { + code: ` + /** + * @template D + * @template V + * @typedef Pairs + * @property {D} foo + * @property {V} bar + */ + `, + }, ], }; diff --git a/test/rules/assertions/requireTemplate.js b/test/rules/assertions/requireTemplate.js index b3ef8c4b..c4cc57c9 100644 --- a/test/rules/assertions/requireTemplate.js +++ b/test/rules/assertions/requireTemplate.js @@ -155,6 +155,22 @@ export default { } ], }, + { + code: ` + /** + * @template X + * @typedef {object} Pairs + * @property {D} foo + * @property {X} bar + */ + `, + errors: [ + { + line: 5, + message: 'Missing @template D', + }, + ], + }, ], valid: [ { @@ -213,5 +229,16 @@ export default { */ `, }, + { + code: ` + /** + * @template D + * @template V + * @typedef {object} Pairs + * @property {D} foo + * @property {V} bar + */ + `, + }, ], };