From 8d1652677e57e626bcabc0ce9723d6c8240ff981 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Thu, 1 Nov 2018 18:18:14 +0800 Subject: [PATCH 1/2] - Enhancement: Add new boolean options `allowImplementsWithoutParam` and `allowAugmentsExtendsWithoutParam` and apply to `requireParam` (fixes #100) --- src/iterateJsdoc.js | 22 ++++- src/rules/requireParam.js | 12 +++ test/rules/assertions/requireParam.js | 123 ++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 2 deletions(-) diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index acea30020..8615663fe 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -2,7 +2,11 @@ import _ from 'lodash'; import commentParser from 'comment-parser'; import jsdocUtils from './jsdocUtils'; -const curryUtils = (functionNode, jsdoc, tagNamePreference, additionalTagNames, allowOverrideWithoutParam) => { +const curryUtils = ( + functionNode, jsdoc, tagNamePreference, additionalTagNames, + allowOverrideWithoutParam, allowImplementsWithoutParam, + allowAugmentsExtendsWithoutParam +) => { const utils = {}; utils.getFunctionParameterNames = () => { @@ -33,6 +37,14 @@ const curryUtils = (functionNode, jsdoc, tagNamePreference, additionalTagNames, return allowOverrideWithoutParam; }; + utils.isImplementsAllowedWithoutParam = () => { + return allowImplementsWithoutParam; + }; + + utils.isAugmentsExtendsAllowedWithoutParam = () => { + return allowAugmentsExtendsWithoutParam; + }; + return utils; }; @@ -61,6 +73,8 @@ export default (iterator) => { const tagNamePreference = _.get(context, 'settings.jsdoc.tagNamePreference') || {}; const additionalTagNames = _.get(context, 'settings.jsdoc.additionalTagNames') || {}; const allowOverrideWithoutParam = Boolean(_.get(context, 'settings.jsdoc.allowOverrideWithoutParam')); + const allowImplementsWithoutParam = Boolean(_.get(context, 'settings.jsdoc.allowImplementsWithoutParam')); + const allowAugmentsExtendsWithoutParam = Boolean(_.get(context, 'settings.jsdoc.allowAugmentsExtendsWithoutParam')); const checkJsdoc = (functionNode) => { const jsdocNode = sourceCode.getJSDocComment(functionNode); @@ -100,7 +114,11 @@ export default (iterator) => { } }; - const utils = curryUtils(functionNode, jsdoc, tagNamePreference, additionalTagNames, allowOverrideWithoutParam); + const utils = curryUtils( + functionNode, jsdoc, tagNamePreference, additionalTagNames, + allowOverrideWithoutParam, allowImplementsWithoutParam, + allowAugmentsExtendsWithoutParam + ); iterator({ context, diff --git a/src/rules/requireParam.js b/src/rules/requireParam.js index d8f275bfa..52d3b8b15 100644 --- a/src/rules/requireParam.js +++ b/src/rules/requireParam.js @@ -19,6 +19,18 @@ export default iterateJsdoc(({ return; } + // When settings.jsdoc.allowImplementsWithoutParam is true, implements implies that all documentation is inherited. + // See https://github.com/gajus/eslint-plugin-jsdoc/issues/100 + if (utils.hasTag('implements') && utils.isImplementsAllowedWithoutParam()) { + return; + } + + // When settings.jsdoc.allowAugmentsExtendsWithoutParam is true, augments or extends implies that all documentation is inherited. + // See https://github.com/gajus/eslint-plugin-jsdoc/issues/100 + if ((utils.hasTag('augments') || utils.hasTag('extends')) && utils.isAugmentsExtendsAllowedWithoutParam()) { + return; + } + _.some(functionParameterNames, (functionParameterName, index) => { const jsdocParameterName = jsdocParameterNames[index]; diff --git a/test/rules/assertions/requireParam.js b/test/rules/assertions/requireParam.js index 79039aaf9..3b14cf7e1 100644 --- a/test/rules/assertions/requireParam.js +++ b/test/rules/assertions/requireParam.js @@ -66,6 +66,51 @@ export default { message: 'Missing JSDoc @param "foo" declaration.' } ] + }, + { + code: ` + /** + * @implements + */ + function quux (foo) { + + } + `, + errors: [ + { + message: 'Missing JSDoc @param "foo" declaration.' + } + ] + }, + { + code: ` + /** + * @augments + */ + function quux (foo) { + + } + `, + errors: [ + { + message: 'Missing JSDoc @param "foo" declaration.' + } + ] + }, + { + code: ` + /** + * @extends + */ + function quux (foo) { + + } + `, + errors: [ + { + message: 'Missing JSDoc @param "foo" declaration.' + } + ] } ], valid: [ @@ -131,6 +176,84 @@ export default { allowOverrideWithoutParam: true } } + }, + { + code: ` + /** + * @implements + */ + function quux (foo) { + + } + `, + settings: { + jsdoc: { + allowImplementsWithoutParam: true + } + } + }, + { + code: ` + /** + * @implements + * @param foo + */ + function quux (foo) { + + } + ` + }, + { + code: ` + /** + * @augments + */ + function quux (foo) { + + } + `, + settings: { + jsdoc: { + allowAugmentsExtendsWithoutParam: true + } + } + }, + { + code: ` + /** + * @augments + * @param foo + */ + function quux (foo) { + + } + ` + }, + { + code: ` + /** + * @extends + */ + function quux (foo) { + + } + `, + settings: { + jsdoc: { + allowAugmentsExtendsWithoutParam: true + } + } + }, + { + code: ` + /** + * @extends + * @param foo + */ + function quux (foo) { + + } + ` } ] }; From d60d5be08ab03a3f5cb445ad021bc397b56d48db Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Fri, 2 Nov 2018 12:48:33 +0800 Subject: [PATCH 2/2] - Expand loosening brought by param checking with `is(*)AllowedWithoutParam` options to include methods whose class has the tag instead (i.e., @override, @implements, @augments, or @extends) --- README.md | 224 ++++++++++++++++++++++++++ src/iterateJsdoc.js | 81 +++++++--- src/rules/requireParam.js | 8 +- test/rules/assertions/requireParam.js | 220 +++++++++++++++++++++++++ 4 files changed, 507 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 9434c381a..461154450 100644 --- a/README.md +++ b/README.md @@ -1350,6 +1350,82 @@ function quux (foo) { } // Message: Missing JSDoc @param "foo" declaration. + +/** + * @implements + */ +function quux (foo) { + +} +// Message: Missing JSDoc @param "foo" declaration. + +/** + * @augments + */ +function quux (foo) { + +} +// Message: Missing JSDoc @param "foo" declaration. + +/** + * @extends + */ +function quux (foo) { + +} +// Message: Missing JSDoc @param "foo" declaration. + +/** + * @override + */ +class A { + /** + * + */ + quux (foo) { + + } +} +// Message: Missing JSDoc @param "foo" declaration. + +/** + * @implements + */ +class A { + /** + * + */ + quux (foo) { + + } +} +// Message: Missing JSDoc @param "foo" declaration. + +/** + * @augments + */ +class A { + /** + * + */ + quux (foo) { + + } +} +// Message: Missing JSDoc @param "foo" declaration. + +/** + * @extends + */ +class A { + /** + * + */ + quux (foo) { + + } +} +// Message: Missing JSDoc @param "foo" declaration. ``` The following patterns are not considered problems: @@ -1392,6 +1468,154 @@ function quux (foo) { } // Settings: {"jsdoc":{"allowOverrideWithoutParam":true}} + +/** + * @implements + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"allowImplementsWithoutParam":true}} + +/** + * @implements + * @param foo + */ +function quux (foo) { + +} + +/** + * @augments + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"allowAugmentsExtendsWithoutParam":true}} + +/** + * @augments + * @param foo + */ +function quux (foo) { + +} + +/** + * @extends + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"allowAugmentsExtendsWithoutParam":true}} + +/** + * @extends + * @param foo + */ +function quux (foo) { + +} + +/** + * @override + */ +class A { + /** + * @param foo + */ + quux (foo) { + + } +} + +/** + * @override + */ +class A { + /** + * + */ + quux (foo) { + + } +} +// Settings: {"jsdoc":{"allowOverrideWithoutParam":true}} + +/** + * @implements + */ +class A { + /** + * + */ + quux (foo) { + + } +} +// Settings: {"jsdoc":{"allowImplementsWithoutParam":true}} + +/** + * @implements + */ +class A { + /** + * @param foo + */ + quux (foo) { + + } +} + +/** + * @augments + */ +class A { + /** + * + */ + quux (foo) { + + } +} +// Settings: {"jsdoc":{"allowAugmentsExtendsWithoutParam":true}} + +/** + * @augments + */ +class A { + /** + * @param foo + */ + quux (foo) { + + } +} + +/** + * @extends + */ +class A { + /** + * + */ + quux (foo) { + + } +} +// Settings: {"jsdoc":{"allowAugmentsExtendsWithoutParam":true}} + +/** + * @extends + */ +class A { + /** + * @param foo + */ + quux (foo) { + + } +} ``` diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index 8615663fe..899fa7a33 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -2,10 +2,35 @@ import _ from 'lodash'; import commentParser from 'comment-parser'; import jsdocUtils from './jsdocUtils'; +const parseComment = (commentNode, indent) => { + // Preserve JSDoc block start/end indentation. + return commentParser(indent + '/*' + commentNode.value + indent + '*/', { + // @see https://github.com/yavorskiy/comment-parser/issues/21 + parsers: [ + commentParser.PARSERS.parse_tag, + commentParser.PARSERS.parse_type, + (str, data) => { + if (_.includes(['return', 'returns'], data.tag)) { + return null; + } + + return commentParser.PARSERS.parse_name(str, data); + }, + commentParser.PARSERS.parse_description + ] + })[0] || {}; +}; + const curryUtils = ( - functionNode, jsdoc, tagNamePreference, additionalTagNames, - allowOverrideWithoutParam, allowImplementsWithoutParam, - allowAugmentsExtendsWithoutParam + functionNode, + jsdoc, + tagNamePreference, + additionalTagNames, + allowOverrideWithoutParam, + allowImplementsWithoutParam, + allowAugmentsExtendsWithoutParam, + ancestors, + sourceCode ) => { const utils = {}; @@ -45,26 +70,28 @@ const curryUtils = ( return allowAugmentsExtendsWithoutParam; }; + utils.classHasTag = (tagName) => { + const greatGrandParent = ancestors.slice(-3)[0]; + const greatGrandParentValue = greatGrandParent && sourceCode.getFirstToken(greatGrandParent).value; + + if (greatGrandParentValue === 'class') { + const classJsdocNode = sourceCode.getJSDocComment(greatGrandParent); + const indent = _.repeat(' ', classJsdocNode.loc.start.column); + const classJsdoc = parseComment(classJsdocNode, indent); + + if (jsdocUtils.hasTag(classJsdoc, tagName)) { + return true; + } + } + + return false; + }; + return utils; }; -export const parseComment = (commentNode, indent) => { - // Preserve JSDoc block start/end indentation. - return commentParser(indent + '/*' + commentNode.value + indent + '*/', { - // @see https://github.com/yavorskiy/comment-parser/issues/21 - parsers: [ - commentParser.PARSERS.parse_tag, - commentParser.PARSERS.parse_type, - (str, data) => { - if (_.includes(['return', 'returns'], data.tag)) { - return null; - } - - return commentParser.PARSERS.parse_name(str, data); - }, - commentParser.PARSERS.parse_description - ] - })[0] || {}; +export { + parseComment }; export default (iterator) => { @@ -83,6 +110,8 @@ export default (iterator) => { return; } + const ancestors = context.getAncestors(); + const indent = _.repeat(' ', jsdocNode.loc.start.column); const jsdoc = parseComment(jsdocNode, indent); @@ -115,9 +144,15 @@ export default (iterator) => { }; const utils = curryUtils( - functionNode, jsdoc, tagNamePreference, additionalTagNames, - allowOverrideWithoutParam, allowImplementsWithoutParam, - allowAugmentsExtendsWithoutParam + functionNode, + jsdoc, + tagNamePreference, + additionalTagNames, + allowOverrideWithoutParam, + allowImplementsWithoutParam, + allowAugmentsExtendsWithoutParam, + ancestors, + sourceCode ); iterator({ diff --git a/src/rules/requireParam.js b/src/rules/requireParam.js index 52d3b8b15..b4e4f047a 100644 --- a/src/rules/requireParam.js +++ b/src/rules/requireParam.js @@ -1,6 +1,7 @@ import _ from 'lodash'; import iterateJsdoc from '../iterateJsdoc'; +// eslint-disable-next-line complexity export default iterateJsdoc(({ report, utils @@ -15,19 +16,20 @@ export default iterateJsdoc(({ // When settings.jsdoc.allowOverrideWithoutParam is true, override implies that all documentation is inherited. // See https://github.com/gajus/eslint-plugin-jsdoc/issues/73 - if (utils.hasTag('override') && utils.isOverrideAllowedWithoutParam()) { + if ((utils.hasTag('override') || utils.classHasTag('override')) && utils.isOverrideAllowedWithoutParam()) { return; } // When settings.jsdoc.allowImplementsWithoutParam is true, implements implies that all documentation is inherited. // See https://github.com/gajus/eslint-plugin-jsdoc/issues/100 - if (utils.hasTag('implements') && utils.isImplementsAllowedWithoutParam()) { + if ((utils.hasTag('implements') || utils.classHasTag('implements')) && utils.isImplementsAllowedWithoutParam()) { return; } // When settings.jsdoc.allowAugmentsExtendsWithoutParam is true, augments or extends implies that all documentation is inherited. // See https://github.com/gajus/eslint-plugin-jsdoc/issues/100 - if ((utils.hasTag('augments') || utils.hasTag('extends')) && utils.isAugmentsExtendsAllowedWithoutParam()) { + if ((utils.hasTag('augments') || utils.hasTag('extends') || + utils.classHasTag('augments') || utils.classHasTag('extends')) && utils.isAugmentsExtendsAllowedWithoutParam()) { return; } diff --git a/test/rules/assertions/requireParam.js b/test/rules/assertions/requireParam.js index 3b14cf7e1..b7f4a1856 100644 --- a/test/rules/assertions/requireParam.js +++ b/test/rules/assertions/requireParam.js @@ -111,6 +111,86 @@ export default { message: 'Missing JSDoc @param "foo" declaration.' } ] + }, + { + code: ` + /** + * @override + */ + class A { + /** + * + */ + quux (foo) { + + } + } + `, + errors: [ + { + message: 'Missing JSDoc @param "foo" declaration.' + } + ] + }, + { + code: ` + /** + * @implements + */ + class A { + /** + * + */ + quux (foo) { + + } + } + `, + errors: [ + { + message: 'Missing JSDoc @param "foo" declaration.' + } + ] + }, + { + code: ` + /** + * @augments + */ + class A { + /** + * + */ + quux (foo) { + + } + } + `, + errors: [ + { + message: 'Missing JSDoc @param "foo" declaration.' + } + ] + }, + { + code: ` + /** + * @extends + */ + class A { + /** + * + */ + quux (foo) { + + } + } + `, + errors: [ + { + message: 'Missing JSDoc @param "foo" declaration.' + } + ] } ], valid: [ @@ -254,6 +334,146 @@ export default { } ` + }, + { + code: ` + /** + * @override + */ + class A { + /** + * @param foo + */ + quux (foo) { + + } + } + ` + }, + { + code: ` + /** + * @override + */ + class A { + /** + * + */ + quux (foo) { + + } + } + `, + settings: { + jsdoc: { + allowOverrideWithoutParam: true + } + } + }, + { + code: ` + /** + * @implements + */ + class A { + /** + * + */ + quux (foo) { + + } + } + `, + settings: { + jsdoc: { + allowImplementsWithoutParam: true + } + } + }, + { + code: ` + /** + * @implements + */ + class A { + /** + * @param foo + */ + quux (foo) { + + } + } + ` + }, + { + code: ` + /** + * @augments + */ + class A { + /** + * + */ + quux (foo) { + + } + } + `, + settings: { + jsdoc: { + allowAugmentsExtendsWithoutParam: true + } + } + }, + { + code: ` + /** + * @augments + */ + class A { + /** + * @param foo + */ + quux (foo) { + + } + } + ` + }, + { + code: ` + /** + * @extends + */ + class A { + /** + * + */ + quux (foo) { + + } + } + `, + settings: { + jsdoc: { + allowAugmentsExtendsWithoutParam: true + } + } + }, + { + code: ` + /** + * @extends + */ + class A { + /** + * @param foo + */ + quux (foo) { + + } + } + ` } ] };