From 4dda0d80a93dc03bca8e695b8809405bad9a06ab Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Sat, 6 Dec 2025 18:15:24 -0700 Subject: [PATCH] feat(`check-tag-names`, `require-template`, `check-template-names`): make `typeParam` a non-preferred alias for `template` --- docs/rules/check-tag-names.md | 5 +++ docs/rules/check-template-names.md | 6 +++ docs/rules/require-template.md | 19 +++++++++ src/rules/checkTemplateNames.js | 11 ++++- src/rules/requireTemplate.js | 17 ++++++-- src/tagNames.js | 7 +++- test/rules/assertions/checkTagNames.js | 14 +++++++ test/rules/assertions/checkTemplateNames.js | 20 +++++++++ test/rules/assertions/requireTemplate.js | 45 +++++++++++++++++++++ 9 files changed, 136 insertions(+), 8 deletions(-) diff --git a/docs/rules/check-tag-names.md b/docs/rules/check-tag-names.md index dc76ac6da..ef0b3d703 100644 --- a/docs/rules/check-tag-names.md +++ b/docs/rules/check-tag-names.md @@ -1157,5 +1157,10 @@ interface WebTwain { * @param {AnotherType} anotherName And yet {@another} */ // "jsdoc/check-tag-names": ["error"|"warn", {"inlineTags":["inline","another","inlineTag","link"]}] + +/** + * @typeParam T + */ +// Settings: {"jsdoc":{"tagNamePreference":{"template":"typeParam"}}} ```` diff --git a/docs/rules/check-template-names.md b/docs/rules/check-template-names.md index 794f75d4d..345022a68 100644 --- a/docs/rules/check-template-names.md +++ b/docs/rules/check-template-names.md @@ -214,6 +214,12 @@ export default class { * @param {[X, Y | undefined]} someParam */ // Message: @template D not in use + +/** + * @template + */ +// Settings: {"jsdoc":{"tagNamePreference":{"template":false}}} +// Message: Unexpected tag `@template` ```` diff --git a/docs/rules/require-template.md b/docs/rules/require-template.md index f5c16d92e..5de090018 100644 --- a/docs/rules/require-template.md +++ b/docs/rules/require-template.md @@ -236,6 +236,12 @@ function foo(bar: T, baz: number | boolean): T { return bar; } // Message: Missing @template T + +/** + * @template + */ +// Settings: {"jsdoc":{"tagNamePreference":{"template":false}}} +// Message: Unexpected tag `@template` ```` @@ -399,5 +405,18 @@ type Pairs = [D, V | undefined]; * @typedef {[D, V | undefined]} Pairs */ // "jsdoc/require-template": ["error"|"warn", {"exemptedBy":["inheritdoc"]}] + +/** + * Test interface for type definitions. + * + * @typeParam Foo - dummy type param + */ +export interface Test { + /** + * + */ + bar: Foo; +} +// Settings: {"jsdoc":{"tagNamePreference":{"template":"typeParam"}}} ```` diff --git a/src/rules/checkTemplateNames.js b/src/rules/checkTemplateNames.js index f43dc7248..640cc55c5 100644 --- a/src/rules/checkTemplateNames.js +++ b/src/rules/checkTemplateNames.js @@ -23,7 +23,14 @@ export default iterateJsdoc(({ mode, } = settings; - const templateTags = utils.getTags('template'); + const tgName = /** @type {string} */ (utils.getPreferredTagName({ + tagName: 'template', + })); + if (!tgName) { + return; + } + + const templateTags = utils.getTags(tgName); const usedNames = new Set(); /** @@ -73,7 +80,7 @@ export default iterateJsdoc(({ const names = utils.parseClosureTemplateTag(tag); for (const nme of names) { if (!usedNames.has(nme)) { - report(`@template ${nme} not in use`, null, tag); + report(`@${tgName} ${nme} not in use`, null, tag); } } } diff --git a/src/rules/requireTemplate.js b/src/rules/requireTemplate.js index 8f4d42f42..543c594be 100644 --- a/src/rules/requireTemplate.js +++ b/src/rules/requireTemplate.js @@ -25,7 +25,16 @@ export default iterateJsdoc(({ } = settings; const usedNames = new Set(); - const templateTags = utils.getTags('template'); + + const tgName = /** @type {string} */ (utils.getPreferredTagName({ + tagName: 'template', + })); + if (!tgName) { + return; + } + + const templateTags = utils.getTags(tgName); + const templateNames = templateTags.flatMap((tag) => { return utils.parseClosureTemplateTag(tag); }); @@ -34,7 +43,7 @@ export default iterateJsdoc(({ for (const tag of templateTags) { const names = utils.parseClosureTemplateTag(tag); if (names.length > 1) { - report(`Missing separate @template for ${names[1]}`, null, tag); + report(`Missing separate @${tgName} for ${names[1]}`, null, tag); } } } @@ -64,7 +73,7 @@ export default iterateJsdoc(({ for (const usedName of usedNames) { if (!templateNames.includes(usedName)) { - report(`Missing @template ${usedName}`); + report(`Missing @${tgName} ${usedName}`); } } }; @@ -156,7 +165,7 @@ export default iterateJsdoc(({ // Could check against whitelist/blacklist for (const usedName of usedNames) { if (!templateNames.includes(usedName)) { - report(`Missing @template ${usedName}`, null, usedNameToTag.get(usedName)); + report(`Missing @${tgName} ${usedName}`, null, usedNameToTag.get(usedName)); } } }; diff --git a/src/tagNames.js b/src/tagNames.js index 6476f501d..81942364d 100644 --- a/src/tagNames.js +++ b/src/tagNames.js @@ -150,8 +150,11 @@ const typeScriptTags = { satisfies: [], // `@template` is also in TypeScript per: - // https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html#supported-jsdoc - template: [], + // https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template + template: [ + // Alias as per https://typedoc.org/documents/Tags._typeParam.html + 'typeParam', + ], }; /** diff --git a/test/rules/assertions/checkTagNames.js b/test/rules/assertions/checkTagNames.js index bc6837839..730f50182 100644 --- a/test/rules/assertions/checkTagNames.js +++ b/test/rules/assertions/checkTagNames.js @@ -1499,5 +1499,19 @@ export default /** @type {import('../index.js').TestCases} */ ({ }, ], }, + { + code: ` + /** + * @typeParam T + */ + `, + settings: { + jsdoc: { + tagNamePreference: { + template: 'typeParam', + }, + }, + }, + }, ], }); diff --git a/test/rules/assertions/checkTemplateNames.js b/test/rules/assertions/checkTemplateNames.js index b5b113954..0d0cd005d 100644 --- a/test/rules/assertions/checkTemplateNames.js +++ b/test/rules/assertions/checkTemplateNames.js @@ -464,6 +464,26 @@ export default /** @type {import('../index.js').TestCases} */ ({ }, ], }, + { + code: ` + /** + * @template + */ + `, + errors: [ + { + line: 3, + message: 'Unexpected tag `@template`', + }, + ], + settings: { + jsdoc: { + tagNamePreference: { + template: false, + }, + }, + }, + }, ], valid: [ { diff --git a/test/rules/assertions/requireTemplate.js b/test/rules/assertions/requireTemplate.js index 53fb24c0c..fa563c949 100644 --- a/test/rules/assertions/requireTemplate.js +++ b/test/rules/assertions/requireTemplate.js @@ -426,6 +426,26 @@ export default /** @type {import('../index.js').TestCases} */ ({ parser: typescriptEslintParser, }, }, + { + code: ` + /** + * @template + */ + `, + errors: [ + { + line: 3, + message: 'Unexpected tag `@template`', + }, + ], + settings: { + jsdoc: { + tagNamePreference: { + template: false, + }, + }, + }, + }, ], valid: [ { @@ -701,5 +721,30 @@ export default /** @type {import('../index.js').TestCases} */ ({ }, ], }, + { + code: ` + /** + * Test interface for type definitions. + * + * @typeParam Foo - dummy type param + */ + export interface Test { + /** + * + */ + bar: Foo; + } + `, + languageOptions: { + parser: typescriptEslintParser, + }, + settings: { + jsdoc: { + tagNamePreference: { + template: 'typeParam', + }, + }, + }, + }, ], });