From 0eb7a0c3bf18ac92a4cde392ee64f29a30804639 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Sat, 13 Jul 2019 19:42:04 -0700 Subject: [PATCH] feat(require-description-complete-sentence): limit checking to certain default tags likely to have descriptions or by `tags` array for additional choices; fixes #337 Tags that are checked for description by default are: 'param', 'arg', 'argument', 'property', 'prop', 'returns', 'return', 'summary', 'file', 'fileoverview', 'overview', 'classdesc', 'todo', 'deprecated', 'throws', 'exception', 'yields', 'yield'. Note that `see` and `copyright` are not included by default because of potentially allowing a non-description or potential sensitivity, respectively. --- .../require-description-complete-sentence.md | 29 ++++- README.md | 111 +++++++++++++++--- src/jsdocUtils.js | 5 +- .../requireDescriptionCompleteSentence.js | 39 +++++- .../requireDescriptionCompleteSentence.js | 104 +++++++++++++++- 5 files changed, 267 insertions(+), 21 deletions(-) diff --git a/.README/rules/require-description-complete-sentence.md b/.README/rules/require-description-complete-sentence.md index 30a016ebf..eed1985cb 100644 --- a/.README/rules/require-description-complete-sentence.md +++ b/.README/rules/require-description-complete-sentence.md @@ -9,10 +9,33 @@ tag descriptions are written in complete sentences, i.e., * Every line in a paragraph (except the first) which starts with an uppercase character must be preceded by a line ending with a period. +#### Options + +##### `tags` + +If you want additional tags to be checked for their descriptions, you may +add them within this option. + +```js +{ + 'jsdoc/require-description-complete-sentence': ['error', {tags: ['see', 'copyright']}] +} +``` + +The tags `@param`/`@arg`/`@argument` and `@property`/`@prop` will be properly +parsed to ensure that the checked "description" text includes only the text +after the name. + +All other tags will treat the text following the tag name, a space, and +an optional curly-bracketed type expression (and another space) as part of +its "description" (e.g., for `@returns {someType} some description`, the +description is `some description` while for `@some-tag xyz`, the description +is `xyz`). + ||| |---|---| |Context|everywhere| -|Tags|`param`, `returns`, `description`| -|Aliases|`arg`, `argument`, `return`, `desc`| - +|Tags|doc block, `param`, `returns`, `description`, `property`, `summary`, `file`, `classdesc`, `todo`, `deprecated`, `throws`, 'yields' and others added by `tags`| +|Aliases|`arg`, `argument`, `return`, `desc`, `prop`, `fileoverview`, `overview`, `exception`, `yield`| +|Options|`tags`| diff --git a/README.md b/README.md index 160047f38..35ebe5a63 100644 --- a/README.md +++ b/README.md @@ -3666,12 +3666,37 @@ tag descriptions are written in complete sentences, i.e., * Every line in a paragraph (except the first) which starts with an uppercase character must be preceded by a line ending with a period. + +#### Options + + +##### tags + +If you want additional tags to be checked for their descriptions, you may +add them within this option. + +```js +{ + 'jsdoc/require-description-complete-sentence': ['error', {tags: ['see', 'copyright']}] +} +``` + +The tags `@param`/`@arg`/`@argument` and `@property`/`@prop` will be properly +parsed to ensure that the checked "description" text includes only the text +after the name. + +All other tags will treat the text following the tag name, a space, and +an optional curly-bracketed type expression (and another space) as part of +its "description" (e.g., for `@returns {someType} some description`, the +description is `some description` while for `@some-tag xyz`, the description +is `xyz`). + ||| |---|---| |Context|everywhere| -|Tags|`param`, `returns`, `description`| -|Aliases|`arg`, `argument`, `return`, `desc`| - +|Tags|doc block, `param`, `returns`, `description`, `property`, `summary`, `file`, `classdesc`, `todo`, `deprecated`, `throws`, 'yields' and others added by `tags`| +|Aliases|`arg`, `argument`, `return`, `desc`, `prop`, `fileoverview`, `overview`, `exception`, `yield`| +|Options|`tags`| The following patterns are considered problems: ````js @@ -3846,10 +3871,35 @@ function quux (foo) { // Message: Sentence should start with an uppercase character. /** - * @typedef {Object} Hello World + * @throws {Object} Hello World * hello world */ // Message: Sentence must end with a period. + +/** + * @summary Foo + */ +function quux () { + +} +// Message: Sentence must end with a period. + +/** + * @throws {SomeType} Foo + */ +function quux () { + +} +// Message: Sentence must end with a period. + +/** + * @see Foo + */ +function quux () { + +} +// Options: [{"tags":["see"]}] +// Message: Sentence must end with a period. ```` The following patterns are not considered problems: @@ -4001,6 +4051,39 @@ function quux () { function quux () { } + +/** + * @example Foo + */ +function quux () { + +} + +/** + * @see Foo + */ +function quux () { + +} + +/** + * Foo. + * + * @param foo Foo. + */ +function quux (foo) { + +} + +/** + * Foo. + * + * @param foo Foo. + */ +function quux (foo) { + +} +// Options: [{"tags":["param"]}] ```` @@ -4015,7 +4098,7 @@ Requires that all functions have a description. `"tag"`) must have a non-empty description that explains the purpose of the method. - + #### Options An options object may have any of the following properties: @@ -4274,25 +4357,25 @@ Requires that all functions have examples. * All functions must have one or more `@example` tags. * Every example tag must have a non-empty description that explains the method's usage. - + #### Options This rule has an object option. - + ##### exemptedBy Array of tags (e.g., `['type']`) whose presence on the document block avoids the need for an `@example`. Defaults to an empty array. - + ##### avoidExampleOnConstructors Set to `true` to avoid the need for an example on a constructor (whether indicated as such by a jsdoc tag or by being within an ES6 `class`). Defaults to `false`. - + ##### contexts Set this to an array of strings representing the AST context @@ -4456,7 +4539,7 @@ function quux () { Requires a hyphen before the `@param` description. - + #### Options This rule takes one optional string argument. If it is `"always"` then a problem is raised when there is no hyphen before the description. If it is `"never"` then a problem is raised when there is a hyphen before the description. The default value is `"always"`. @@ -4562,7 +4645,7 @@ function quux () { Checks for presence of jsdoc comments, on class declarations as well as functions. - + #### Options Accepts one optional options object with the following optional keys. @@ -5605,7 +5688,7 @@ function quux (foo) { Requires that all function parameters are documented. - + #### Options An options object accepts one optional property: @@ -6536,7 +6619,7 @@ Requires returns are documented. Will also report if multiple `@returns` tags are present. - + #### Options - `exemptedBy` - Array of tags (e.g., `['type']`) whose presence on the document @@ -6992,7 +7075,7 @@ Also impacts behaviors on namepath (or event)-defining and pointing tags: allow `#`, `.`, or `~` at the end (which is not allowed at the end of normal paths). - + #### Options - `allowEmptyNamepaths` (default: true) - Set to `false` to disallow diff --git a/src/jsdocUtils.js b/src/jsdocUtils.js index b8abfc52a..b1624aff0 100644 --- a/src/jsdocUtils.js +++ b/src/jsdocUtils.js @@ -525,7 +525,10 @@ const filterTags = (tags = [], filter) => { }; const tagsWithNamesAndDescriptions = [ - 'param', 'arg', 'argument', 'property', 'prop', 'returns', 'return' + 'param', 'arg', 'argument', 'property', 'prop', + + // These two are parsed by our custom parser as though having a `name` + 'returns', 'return' ]; const getTagsByType = (tags, tagPreference) => { diff --git a/src/rules/requireDescriptionCompleteSentence.js b/src/rules/requireDescriptionCompleteSentence.js index c6542c71c..1f0543eec 100644 --- a/src/rules/requireDescriptionCompleteSentence.js +++ b/src/rules/requireDescriptionCompleteSentence.js @@ -124,6 +124,7 @@ export default iterateJsdoc(({ jsdoc, report, jsdocNode, + context, utils }) => { if (!jsdoc.tags || @@ -139,7 +140,27 @@ export default iterateJsdoc(({ validateDescription(description, report, jsdocNode, sourceCode, matchingJsdocTag); }); - const {tagsWithNames, tagsWithoutNames} = utils.getTagsByType(jsdoc.tags); + const options = context.options[0] || {}; + + const hasOptionTag = (tagName) => { + return Boolean(options.tags && options.tags.includes(tagName)); + }; + + const {tagsWithNames} = utils.getTagsByType(jsdoc.tags); + const tagsWithoutNames = utils.filterTags(({tag: tagName}) => { + return [ + // 'copyright' and 'see' might be good addition, but as the former may be + // sensitive text, and the latter may have just a link, they are not + // included by default + 'summary', 'file', 'fileoverview', 'overview', 'classdesc', 'todo', + 'deprecated', 'throws', 'exception', 'yields', 'yield' + ].includes(tagName) || + hasOptionTag(tagName) && !tagsWithNames.some(({tag}) => { + // If user accidentally adds tags with names (or like `returns` + // get parsed as having names), do not add to this list + return tag === tagName; + }); + }); tagsWithNames.some((tag) => { const description = _.trimStart(tag.description, '- '); @@ -148,7 +169,7 @@ export default iterateJsdoc(({ }); tagsWithoutNames.some((tag) => { - const description = (tag.name + ' ' + tag.description).trim(); + const description = `${tag.name} ${tag.description}`.trim(); return validateDescription(description, report, jsdocNode, sourceCode, tag); }); @@ -156,6 +177,20 @@ export default iterateJsdoc(({ iterateAllJsdocs: true, meta: { fixable: 'code', + schema: [ + { + additionalProperties: false, + properties: { + tags: { + items: { + type: 'string' + }, + type: 'array' + } + }, + type: 'object' + } + ], type: 'suggestion' } }); diff --git a/test/rules/assertions/requireDescriptionCompleteSentence.js b/test/rules/assertions/requireDescriptionCompleteSentence.js index 1505f692a..256eeed1a 100644 --- a/test/rules/assertions/requireDescriptionCompleteSentence.js +++ b/test/rules/assertions/requireDescriptionCompleteSentence.js @@ -504,7 +504,7 @@ export default { { code: ` /** - * @typedef {Object} Hello World + * @throws {Object} Hello World * hello world */ `, @@ -514,6 +514,59 @@ export default { message: 'Sentence must end with a period.' } ] + }, + { + code: ` + /** + * @summary Foo + */ + function quux () { + + } + `, + errors: [ + { + line: 3, + message: 'Sentence must end with a period.' + } + ] + }, + { + code: ` + /** + * @throws {SomeType} Foo + */ + function quux () { + + } + `, + errors: [ + { + line: 3, + message: 'Sentence must end with a period.' + } + ] + }, + { + code: ` + /** + * @see Foo + */ + function quux () { + + } + `, + errors: [ + { + line: 3, + message: 'Sentence must end with a period.' + } + ], + options: [ + { + tags: ['see'] + } + ] } ], valid: [ @@ -721,6 +774,55 @@ export default { } ` + }, + { + code: ` + /** + * @example Foo + */ + function quux () { + + } + ` + }, + { + code: ` + /** + * @see Foo + */ + function quux () { + + } + ` + }, + { + code: ` + /** + * Foo. + * + * @param foo Foo. + */ + function quux (foo) { + + } + ` + }, + { + code: ` + /** + * Foo. + * + * @param foo Foo. + */ + function quux (foo) { + + } + `, + options: [ + { + tags: ['param'] + } + ] } ] };