From bca796c4de5b51d0e1ffa9f6c84bd44622b22243 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Tue, 16 Sep 2025 16:31:23 +0800 Subject: [PATCH] feat(`require-param`): add `interfaceExemptsParamsCheck` option; fixes #1511 --- .README/rules/require-param.md | 11 ++- docs/rules/require-param.md | 50 ++++++++++- src/rules.d.ts | 1 + src/rules/requireParam.js | 20 +++++ test/rules/assertions/requireParam.js | 121 ++++++++++++++++++++++++++ 5 files changed, 201 insertions(+), 2 deletions(-) diff --git a/.README/rules/require-param.md b/.README/rules/require-param.md index 978f055c2..db665383c 100644 --- a/.README/rules/require-param.md +++ b/.README/rules/require-param.md @@ -374,6 +374,15 @@ supplied as default values. Defaults to `false`. Set to `true` to ignore reporting when all params are missing. Defaults to `false`. +### `interfaceExemptsParamsCheck` + +Set if you wish TypeScript interfaces to exempt checks for the existence of +`@param`'s. + +Will check for a type defining the function itself (on a variable +declaration) or if there is a single destructured object with a type. +Defaults to `false`. + ## Context and settings | | | @@ -382,7 +391,7 @@ Set to `true` to ignore reporting when all params are missing. Defaults to | Tags | `param` | | Aliases | `arg`, `argument` | |Recommended | true| -| Options |`autoIncrementBase`, `checkConstructors`, `checkDestructured`, `checkDestructuredRoots`, `checkGetters`, `checkRestProperty`, `checkSetters`, `checkTypesPattern`, `contexts`, `enableFixer`, `enableRestElementFixer`, `enableRootFixer`, `exemptedBy`, `ignoreWhenAllParamsMissing`, `unnamedRootBase`, `useDefaultObjectProperties`| +| Options |`autoIncrementBase`, `checkConstructors`, `checkDestructured`, `checkDestructuredRoots`, `checkGetters`, `checkRestProperty`, `checkSetters`, `checkTypesPattern`, `contexts`, `enableFixer`, `enableRestElementFixer`, `enableRootFixer`, `exemptedBy`, `ignoreWhenAllParamsMissing`, `interfaceExemptsParamsCheck`, `unnamedRootBase`, `useDefaultObjectProperties`| | Settings | `ignoreReplacesDocs`, `overrideReplacesDocs`, `augmentsExtendsReplacesDocs`, `implementsReplacesDocs`| ## Failing examples diff --git a/docs/rules/require-param.md b/docs/rules/require-param.md index 281c7190c..0dc07fd70 100644 --- a/docs/rules/require-param.md +++ b/docs/rules/require-param.md @@ -24,6 +24,7 @@ * [`checkDestructuredRoots`](#user-content-require-param-options-checkdestructuredroots) * [`useDefaultObjectProperties`](#user-content-require-param-options-usedefaultobjectproperties) * [`ignoreWhenAllParamsMissing`](#user-content-require-param-options-ignorewhenallparamsmissing) + * [`interfaceExemptsParamsCheck`](#user-content-require-param-options-interfaceexemptsparamscheck) * [Context and settings](#user-content-require-param-context-and-settings) * [Failing examples](#user-content-require-param-failing-examples) * [Passing examples](#user-content-require-param-passing-examples) @@ -445,6 +446,17 @@ supplied as default values. Defaults to `false`. Set to `true` to ignore reporting when all params are missing. Defaults to `false`. + + +### interfaceExemptsParamsCheck + +Set if you wish TypeScript interfaces to exempt checks for the existence of +`@param`'s. + +Will check for a type defining the function itself (on a variable +declaration) or if there is a single destructured object with a type. +Defaults to `false`. + ## Context and settings @@ -455,7 +467,7 @@ Set to `true` to ignore reporting when all params are missing. Defaults to | Tags | `param` | | Aliases | `arg`, `argument` | |Recommended | true| -| Options |`autoIncrementBase`, `checkConstructors`, `checkDestructured`, `checkDestructuredRoots`, `checkGetters`, `checkRestProperty`, `checkSetters`, `checkTypesPattern`, `contexts`, `enableFixer`, `enableRestElementFixer`, `enableRootFixer`, `exemptedBy`, `ignoreWhenAllParamsMissing`, `unnamedRootBase`, `useDefaultObjectProperties`| +| Options |`autoIncrementBase`, `checkConstructors`, `checkDestructured`, `checkDestructuredRoots`, `checkGetters`, `checkRestProperty`, `checkSetters`, `checkTypesPattern`, `contexts`, `enableFixer`, `enableRestElementFixer`, `enableRootFixer`, `exemptedBy`, `ignoreWhenAllParamsMissing`, `interfaceExemptsParamsCheck`, `unnamedRootBase`, `useDefaultObjectProperties`| | Settings | `ignoreReplacesDocs`, `overrideReplacesDocs`, `augmentsExtendsReplacesDocs`, `implementsReplacesDocs`| @@ -1185,6 +1197,25 @@ function quux (a, b) {} export type Test = (foo: number) => string; // "jsdoc/require-param": ["error"|"warn", {"contexts":["TSFunctionType"]}] // Message: Missing JSDoc @param "foo" declaration. + +/** + * + */ +const quux = function quux (foo) { +}; +// "jsdoc/require-param": ["error"|"warn", {"interfaceExemptsParamsCheck":true}] +// Message: Missing JSDoc @param "foo" declaration. + +/** + * + */ +function quux ({ + abc, + def +}) { +} +// "jsdoc/require-param": ["error"|"warn", {"interfaceExemptsParamsCheck":true}] +// Message: Missing JSDoc @param "root0" declaration. ```` @@ -1853,5 +1884,22 @@ function myFunction(foo: string): void; */ function myFunction(): void; function myFunction(foo?: string) {} + +/** + * + */ +const quux: FunctionInterface = function quux (foo) { +}; +// "jsdoc/require-param": ["error"|"warn", {"interfaceExemptsParamsCheck":true}] + +/** + * + */ +function quux ({ + abc, + def +}: FunctionInterface) { +} +// "jsdoc/require-param": ["error"|"warn", {"interfaceExemptsParamsCheck":true}] ```` diff --git a/src/rules.d.ts b/src/rules.d.ts index 2b9b822fe..53f21ec79 100644 --- a/src/rules.d.ts +++ b/src/rules.d.ts @@ -591,6 +591,7 @@ export interface Rules { enableRootFixer?: boolean; exemptedBy?: string[]; ignoreWhenAllParamsMissing?: boolean; + interfaceExemptsParamsCheck?: boolean; unnamedRootBase?: string[]; useDefaultObjectProperties?: boolean; } diff --git a/src/rules/requireParam.js b/src/rules/requireParam.js index c3f0bcc68..ed0294dcb 100644 --- a/src/rules/requireParam.js +++ b/src/rules/requireParam.js @@ -35,6 +35,7 @@ const rootNamer = (desiredRoots, currentIndex) => { export default iterateJsdoc(({ context, jsdoc, + node, utils, }) => { /* eslint-enable complexity -- Temporary */ @@ -57,12 +58,28 @@ export default iterateJsdoc(({ enableRestElementFixer = true, enableRootFixer = true, ignoreWhenAllParamsMissing = false, + interfaceExemptsParamsCheck = false, unnamedRootBase = [ 'root', ], useDefaultObjectProperties = false, } = context.options[0] || {}; + if (interfaceExemptsParamsCheck) { + if (node && 'params' in node && node.params.length === 1 && + node.params?.[0] && typeof node.params[0] === 'object' && + node.params[0].type === 'ObjectPattern' && + 'typeAnnotation' in node.params[0] && node.params[0].typeAnnotation + ) { + return; + } + + if (node && node.parent.type === 'VariableDeclarator' && + 'typeAnnotation' in node.parent.id && node.parent.id.typeAnnotation) { + return; + } + } + const preferredTagName = /** @type {string} */ (utils.getPreferredTagName({ tagName: 'param', })); @@ -579,6 +596,9 @@ export default iterateJsdoc(({ ignoreWhenAllParamsMissing: { type: 'boolean', }, + interfaceExemptsParamsCheck: { + type: 'boolean', + }, unnamedRootBase: { items: { type: 'string', diff --git a/test/rules/assertions/requireParam.js b/test/rules/assertions/requireParam.js index 5d4d13d57..11296fb89 100644 --- a/test/rules/assertions/requireParam.js +++ b/test/rules/assertions/requireParam.js @@ -2587,6 +2587,87 @@ export default /** @type {import('../index.js').TestCases} */ ({ export type Test = (foo: number) => string; `, }, + { + code: ` + /** + * + */ + const quux = function quux (foo) { + }; + `, + errors: [ + { + line: 2, + message: 'Missing JSDoc @param "foo" declaration.', + }, + ], + languageOptions: { + parser: typescriptEslintParser, + sourceType: 'module', + }, + options: [ + { + interfaceExemptsParamsCheck: true, + }, + ], + output: ` + /** + * + * @param foo + */ + const quux = function quux (foo) { + }; + `, + }, + + { + code: ` + /** + * + */ + function quux ({ + abc, + def + }) { + } + `, + errors: [ + { + line: 2, + message: 'Missing JSDoc @param "root0" declaration.', + }, + { + line: 2, + message: 'Missing JSDoc @param "root0.abc" declaration.', + }, + { + line: 2, + message: 'Missing JSDoc @param "root0.def" declaration.', + }, + ], + languageOptions: { + parser: typescriptEslintParser, + sourceType: 'module', + }, + options: [ + { + interfaceExemptsParamsCheck: true, + }, + ], + output: ` + /** + * + * @param root0 + * @param root0.abc + * @param root0.def + */ + function quux ({ + abc, + def + }) { + } + `, + }, ], valid: [ { @@ -3695,5 +3776,45 @@ export default /** @type {import('../index.js').TestCases} */ ({ sourceType: 'module', }, }, + { + code: ` + /** + * + */ + const quux: FunctionInterface = function quux (foo) { + }; + `, + languageOptions: { + parser: typescriptEslintParser, + sourceType: 'module', + }, + options: [ + { + interfaceExemptsParamsCheck: true, + }, + ], + }, + + { + code: ` + /** + * + */ + function quux ({ + abc, + def + }: FunctionInterface) { + } + `, + languageOptions: { + parser: typescriptEslintParser, + sourceType: 'module', + }, + options: [ + { + interfaceExemptsParamsCheck: true, + }, + ], + }, ], });