diff --git a/README.md b/README.md index 35ebe5a63..fd024ff4b 100644 --- a/README.md +++ b/README.md @@ -2427,6 +2427,15 @@ function quux () { } // Message: @implements used on a non-constructor function + +/** + * @implements {SomeClass} + */ +function quux () { + +} +// Settings: {"jsdoc":{"tagNamePreference":{"implements":false}}} +// Message: Unexpected tag `@implements` ```` The following patterns are not considered problems: @@ -2478,6 +2487,14 @@ const quux = class { function quux () { } + +/** + * + */ +function quux () { + +} +// Settings: {"jsdoc":{"tagNamePreference":{"implements":false}}} ```` @@ -2885,6 +2902,25 @@ const myObject = { }; // Options: [{"contexts":["Property"]}] // Message: JSDoc description does not satisfy the regex pattern. + +/** + * @param foo Foo bar + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"tagNamePreference":{"description":false}}} +// Options: [{"tags":{"param":true}}] +// Message: JSDoc description does not satisfy the regex pattern. + +/** + * Foo bar + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"tagNamePreference":{"description":false}}} +// Message: JSDoc description does not satisfy the regex pattern. ```` The following patterns are not considered problems: @@ -3149,6 +3185,22 @@ function quux (foo) { } // Options: [{"tags":{"prop":true}}] + +/** + * @param foo Foo bar. + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"tagNamePreference":{"description":false}}} + +/** + * + */ +function quux () { + +} +// Settings: {"jsdoc":{"tagNamePreference":{"description":false}}} ```` @@ -3900,6 +3952,16 @@ function quux () { } // Options: [{"tags":["see"]}] // Message: Sentence must end with a period. + +/** + * @param foo Foo bar + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"tagNamePreference":{"description":false}}} +// Options: [{"tags":["param"]}] +// Message: Sentence must end with a period. ```` The following patterns are not considered problems: @@ -4084,6 +4146,23 @@ function quux (foo) { } // Options: [{"tags":["param"]}] + +/** + * @param foo Foo bar. + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"tagNamePreference":{"description":false}}} +// Options: [{"tags":["param"]}] + +/** + * + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"tagNamePreference":{"description":false}}} ```` @@ -4233,6 +4312,16 @@ function quux () { // Settings: {"jsdoc":{"tagNamePreference":{"description":false}}} // Options: [{"descriptionStyle":"tag"}] // Message: Unexpected tag `@description` + +/** + * @description + */ +function quux () { + +} +// Settings: {"jsdoc":{"tagNamePreference":{"description":false}}} +// Options: [{"descriptionStyle":"any"}] +// Message: Missing JSDoc block description or @description declaration. ```` The following patterns are not considered problems: @@ -4346,6 +4435,14 @@ function quux () { } // Options: [{"descriptionStyle":"any"}] + +/** + * + */ +function quux () { + +} +// Settings: {"jsdoc":{"tagNamePreference":{"description":false}}} ```` diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index aa88f5a7b..c2fde7214 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -90,7 +90,7 @@ const getUtils = ( }; utils.getJsdocParameterNamesDeep = () => { - const param = utils.getPreferredTagName('param'); + const param = utils.getPreferredTagName({tagName: 'param'}); if (!param) { return false; } @@ -99,7 +99,7 @@ const getUtils = ( }; utils.getJsdocParameterNames = () => { - const param = utils.getPreferredTagName('param'); + const param = utils.getPreferredTagName({tagName: 'param'}); if (!param) { return false; } @@ -107,12 +107,18 @@ const getUtils = ( return jsdocUtils.getJsdocParameterNames(jsdoc, param); }; - utils.getPreferredTagName = (name, allowObjectReturn = false, defaultMessage = `Unexpected tag \`@${name}\``) => { - const ret = jsdocUtils.getPreferredTagName(name, tagNamePreference); + utils.getPreferredTagName = ({tagName, skipReportingBlockedTag = false, allowObjectReturn = false, defaultMessage = `Unexpected tag \`@${tagName}\``}) => { + const ret = jsdocUtils.getPreferredTagName(tagName, tagNamePreference); const isObject = ret && typeof ret === 'object'; - if (ret === false || isObject && !ret.replacement) { + if (utils.hasTag(tagName) && (ret === false || isObject && !ret.replacement)) { + if (skipReportingBlockedTag) { + return { + blocked: true, + tagName + }; + } const message = isObject && ret.message || defaultMessage; - report(message, null, utils.getTags(name)[0]); + report(message, null, utils.getTags(tagName)[0]); return false; } @@ -239,9 +245,14 @@ const getUtils = ( return classJsdoc && jsdocUtils.hasTag(classJsdoc, tagName); }; - utils.forEachPreferredTag = (tagName, arrayHandler) => { - const targetTagName = utils.getPreferredTagName(tagName); - if (!targetTagName) { + utils.forEachPreferredTag = (tagName, arrayHandler, skipReportingBlockedTag = false) => { + const targetTagName = utils.getPreferredTagName({ + skipReportingBlockedTag, + tagName + }); + if (!targetTagName || + skipReportingBlockedTag && targetTagName && typeof targetTagName === 'object' + ) { return; } const matchingJsdocTags = _.filter(jsdoc.tags || [], { diff --git a/src/rules/checkParamNames.js b/src/rules/checkParamNames.js index 2dfcfacec..f8e9c0201 100644 --- a/src/rules/checkParamNames.js +++ b/src/rules/checkParamNames.js @@ -100,7 +100,7 @@ export default iterateJsdoc(({ if (!jsdocParameterNamesDeep) { return; } - const targetTagName = utils.getPreferredTagName('param'); + const targetTagName = utils.getPreferredTagName({tagName: 'param'}); const isError = validateParameterNames(targetTagName, functionParameterNames, jsdoc, report); if (isError) { diff --git a/src/rules/checkTagNames.js b/src/rules/checkTagNames.js index 529901ee2..afa605930 100644 --- a/src/rules/checkTagNames.js +++ b/src/rules/checkTagNames.js @@ -45,11 +45,11 @@ export default iterateJsdoc(({ jsdoc.tags.forEach((jsdocTag) => { const tagName = jsdocTag.tag; if (utils.isValidTag(tagName, [...definedTags, ...definedPreferredTags, ...definedNonPreferredTags])) { - let preferredTagName = utils.getPreferredTagName( - tagName, - true, - `Blacklisted tag found (\`@${tagName}\`)` - ); + let preferredTagName = utils.getPreferredTagName({ + allowObjectReturn: true, + defaultMessage: `Blacklisted tag found (\`@${tagName}\`)`, + tagName + }); let message = `Invalid JSDoc tag (preference). Replace "${tagName}" JSDoc tag with "${preferredTagName}".`; if (!preferredTagName) { return; diff --git a/src/rules/matchDescription.js b/src/rules/matchDescription.js index fc594c83c..5d28e2002 100644 --- a/src/rules/matchDescription.js +++ b/src/rules/matchDescription.js @@ -60,7 +60,7 @@ export default iterateJsdoc(({ if (hasOptionTag(targetTagName)) { validateDescription(description, matchingJsdocTag); } - }); + }, true); const whitelistedTags = utils.filterTags(({tag: tagName}) => { return hasOptionTag(tagName); diff --git a/src/rules/requireDescription.js b/src/rules/requireDescription.js index 6ff3f0757..e60b77def 100644 --- a/src/rules/requireDescription.js +++ b/src/rules/requireDescription.js @@ -10,11 +10,23 @@ export default iterateJsdoc(({ if (utils.avoidDocs()) { return; } + const {descriptionStyle = 'body'} = context.options[0] || {}; - const targetTagName = utils.getPreferredTagName('description'); + let targetTagName = utils.getPreferredTagName({ + // We skip reporting except when `@description` is essential to the rule, + // so user can block the tag and still meaningfully use this rule + // even if the tag is present (and `check-tag-names` is the one to + // normally report the fact that it is blocked but present) + skipReportingBlockedTag: descriptionStyle !== 'tag', + tagName: 'description' + }); if (!targetTagName) { return; } + const isBlocked = typeof targetTagName === 'object' && targetTagName.blocked; + if (isBlocked) { + targetTagName = targetTagName.tagName; + } const checkDescription = (description) => { const exampleContent = _.compact(description.trim().split('\n')); @@ -22,8 +34,6 @@ export default iterateJsdoc(({ return exampleContent.length; }; - const {descriptionStyle = 'body'} = context.options[0] || {}; - if (descriptionStyle !== 'tag') { if (checkDescription(jsdoc.description || '')) { return; @@ -36,9 +46,11 @@ export default iterateJsdoc(({ } } - const functionExamples = _.filter(jsdoc.tags, { - tag: targetTagName - }); + const functionExamples = isBlocked ? + [] : + _.filter(jsdoc.tags, { + tag: targetTagName + }); if (!functionExamples.length) { report( diff --git a/src/rules/requireDescriptionCompleteSentence.js b/src/rules/requireDescriptionCompleteSentence.js index 1f0543eec..770e9ea6e 100644 --- a/src/rules/requireDescriptionCompleteSentence.js +++ b/src/rules/requireDescriptionCompleteSentence.js @@ -138,7 +138,7 @@ export default iterateJsdoc(({ utils.forEachPreferredTag('description', (matchingJsdocTag) => { const description = `${matchingJsdocTag.name} ${matchingJsdocTag.description}`.trim(); validateDescription(description, report, jsdocNode, sourceCode, matchingJsdocTag); - }); + }, true); const options = context.options[0] || {}; diff --git a/src/rules/requireParam.js b/src/rules/requireParam.js index 8221331ce..b83d9f81d 100644 --- a/src/rules/requireParam.js +++ b/src/rules/requireParam.js @@ -24,7 +24,7 @@ export default iterateJsdoc(({ const jsdocParameterName = jsdocParameterNames[index]; if (!jsdocParameterName) { - report(`Missing JSDoc @${utils.getPreferredTagName('param')} "${functionParameterName}" declaration.`); + report(`Missing JSDoc @${utils.getPreferredTagName({tagName: 'param'})} "${functionParameterName}" declaration.`); return true; } diff --git a/src/rules/requireReturns.js b/src/rules/requireReturns.js index 16cd44ec8..6127f6134 100644 --- a/src/rules/requireReturns.js +++ b/src/rules/requireReturns.js @@ -59,7 +59,7 @@ export default iterateJsdoc(({ forceReturnsWithAsync = false } = context.options[0] || {}; - const tagName = utils.getPreferredTagName('returns'); + const tagName = utils.getPreferredTagName({tagName: 'returns'}); if (!tagName) { return; } diff --git a/src/rules/requireReturnsCheck.js b/src/rules/requireReturnsCheck.js index 1b59e173a..60aeee673 100755 --- a/src/rules/requireReturnsCheck.js +++ b/src/rules/requireReturnsCheck.js @@ -26,7 +26,7 @@ export default iterateJsdoc(({ return; } - const tagName = utils.getPreferredTagName('returns'); + const tagName = utils.getPreferredTagName({tagName: 'returns'}); if (!tagName) { return; } diff --git a/test/rules/assertions/implementsOnClasses.js b/test/rules/assertions/implementsOnClasses.js index af119af2a..1460f9026 100644 --- a/test/rules/assertions/implementsOnClasses.js +++ b/test/rules/assertions/implementsOnClasses.js @@ -15,6 +15,29 @@ export default { message: '@implements used on a non-constructor function' } ] + }, + { + code: ` + /** + * @implements {SomeClass} + */ + function quux () { + + } + `, + errors: [ + { + line: 3, + message: 'Unexpected tag `@implements`' + } + ], + settings: { + jsdoc: { + tagNamePreference: { + implements: false + } + } + } } ], valid: [ @@ -79,6 +102,23 @@ export default { } ` + }, + { + code: ` + /** + * + */ + function quux () { + + } + `, + settings: { + jsdoc: { + tagNamePreference: { + implements: false + } + } + } } ] }; diff --git a/test/rules/assertions/matchDescription.js b/test/rules/assertions/matchDescription.js index e3f45d5ed..d8e09b479 100644 --- a/test/rules/assertions/matchDescription.js +++ b/test/rules/assertions/matchDescription.js @@ -680,6 +680,57 @@ export default { ] } ] + }, + { + code: ` + /** + * @param foo Foo bar + */ + function quux (foo) { + + } + `, + errors: [ + { + line: 3, + message: 'JSDoc description does not satisfy the regex pattern.' + } + ], + options: [{ + tags: { + param: true + } + }], + settings: { + jsdoc: { + tagNamePreference: { + description: false + } + } + } + }, + { + code: ` + /** + * Foo bar + */ + function quux (foo) { + + } + `, + errors: [ + { + line: 3, + message: 'JSDoc description does not satisfy the regex pattern.' + } + ], + settings: { + jsdoc: { + tagNamePreference: { + description: false + } + } + } } ], valid: [ @@ -1130,6 +1181,40 @@ export default { } } ] + }, + { + code: ` + /** + * @param foo Foo bar. + */ + function quux (foo) { + + } + `, + settings: { + jsdoc: { + tagNamePreference: { + description: false + } + } + } + }, + { + code: ` + /** + * + */ + function quux () { + + } + `, + settings: { + jsdoc: { + tagNamePreference: { + description: false + } + } + } } ] }; diff --git a/test/rules/assertions/requireDescription.js b/test/rules/assertions/requireDescription.js index 82ef85c82..5e533be40 100644 --- a/test/rules/assertions/requireDescription.js +++ b/test/rules/assertions/requireDescription.js @@ -269,6 +269,33 @@ export default { } } } + }, + { + code: ` + /** + * @description + */ + function quux () { + + } + `, + errors: [ + { + message: 'Missing JSDoc block description or @description declaration.' + } + ], + options: [ + { + descriptionStyle: 'any' + } + ], + settings: { + jsdoc: { + tagNamePreference: { + description: false + } + } + } } ], valid: [ @@ -468,6 +495,23 @@ export default { descriptionStyle: 'any' } ] + }, + { + code: ` + /** + * + */ + function quux () { + + } + `, + settings: { + jsdoc: { + tagNamePreference: { + description: false + } + } + } } ] }; diff --git a/test/rules/assertions/requireDescriptionCompleteSentence.js b/test/rules/assertions/requireDescriptionCompleteSentence.js index 256eeed1a..e02d39d2e 100644 --- a/test/rules/assertions/requireDescriptionCompleteSentence.js +++ b/test/rules/assertions/requireDescriptionCompleteSentence.js @@ -567,6 +567,32 @@ export default { tags: ['see'] } ] + }, + { + code: ` + /** + * @param foo Foo bar + */ + function quux (foo) { + + } + `, + errors: [ + { + line: 3, + message: 'Sentence must end with a period.' + } + ], + options: [{ + tags: ['param'] + }], + settings: { + jsdoc: { + tagNamePreference: { + description: false + } + } + } } ], valid: [ @@ -823,6 +849,43 @@ export default { tags: ['param'] } ] + }, + { + code: ` + /** + * @param foo Foo bar. + */ + function quux (foo) { + + } + `, + options: [{ + tags: ['param'] + }], + settings: { + jsdoc: { + tagNamePreference: { + description: false + } + } + } + }, + { + code: ` + /** + * + */ + function quux (foo) { + + } + `, + settings: { + jsdoc: { + tagNamePreference: { + description: false + } + } + } } ] };