Skip to content

Commit 8f414b0

Browse files
committed
feat(match-description): ensure @desc checked by default; check more desc-like tags by default; check non-empty tags; gajus#233
1 parent 3a5dd7d commit 8f414b0

File tree

6 files changed

+150
-78
lines changed

6 files changed

+150
-78
lines changed

.README/rules/match-description.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@ by our supported Node versions):
1010

1111
``^\n?([A-Z`\\d_][\\s\\S]*[.?!`]\\s*)?$``
1212

13-
Applies to the jsdoc block description and `@description` (or `@desc`)
14-
by default but the `tags` option (see below) may be used to match other tags.
13+
Applies by default to the jsdoc block description and to the following tags:
14+
15+
- `@description`/`@desc`
16+
- `@summary`
17+
- `@file`/`@fileoverview`/`@overview`
18+
- `@classdesc`
19+
20+
In addition, the `tags` option (see below) may be used to match other tags.
1521

1622
The default (and all regex options) defaults to using (only) the `u` flag, so
1723
to add your own flags, encapsulate your expression as a string, but like a
@@ -54,6 +60,21 @@ You may provide a custom default message by using the following format:
5460
This can be overridden per tag or for the main block description by setting
5561
`message` within `tags` or `mainDescription`, respectively.
5662

63+
### `nonemptyTags`
64+
65+
If not set to `false`, will enforce that the following tags have at least
66+
some content:
67+
68+
- `@copyright`
69+
- `@example`
70+
- `@see`
71+
- `@todo`
72+
- `@throws`/`@exception`
73+
- `@yields`/`@yield`
74+
75+
If you supply your own tag description for any of the above tags in `tags`,
76+
your description will take precedence.
77+
5778
### `tags`
5879

5980
If you want different regular expressions to apply to tags, you may use
@@ -161,7 +182,7 @@ section of our README for more on the expected format.
161182
|Aliases|`@desc`|
162183
|Recommended|false|
163184
|Settings||
164-
|Options|`contexts`, `mainDescription`, `matchDescription`, `message`, `tags`|
185+
|Options|`contexts`, `mainDescription`, `matchDescription`, `message`, `nonemptyTags`, `tags`|
165186

166187
## Failing examples
167188

docs/rules/match-description.md

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* [Options](#user-content-match-description-options)
66
* [`matchDescription`](#user-content-match-description-options-matchdescription)
77
* [`message`](#user-content-match-description-options-message)
8+
* [`nonemptyTags`](#user-content-match-description-options-nonemptytags)
89
* [`tags`](#user-content-match-description-options-tags)
910
* [`mainDescription`](#user-content-match-description-options-maindescription)
1011
* [`contexts`](#user-content-match-description-options-contexts)
@@ -21,8 +22,14 @@ by our supported Node versions):
2122

2223
``^\n?([A-Z`\\d_][\\s\\S]*[.?!`]\\s*)?$``
2324

24-
Applies to the jsdoc block description and `@description` (or `@desc`)
25-
by default but the `tags` option (see below) may be used to match other tags.
25+
Applies by default to the jsdoc block description and to the following tags:
26+
27+
- `@description`/`@desc`
28+
- `@summary`
29+
- `@file`/`@fileoverview`/`@overview`
30+
- `@classdesc`
31+
32+
In addition, the `tags` option (see below) may be used to match other tags.
2633

2734
The default (and all regex options) defaults to using (only) the `u` flag, so
2835
to add your own flags, encapsulate your expression as a string, but like a
@@ -71,6 +78,23 @@ You may provide a custom default message by using the following format:
7178
This can be overridden per tag or for the main block description by setting
7279
`message` within `tags` or `mainDescription`, respectively.
7380

81+
<a name="user-content-match-description-options-nonemptytags"></a>
82+
<a name="match-description-options-nonemptytags"></a>
83+
### <code>nonemptyTags</code>
84+
85+
If not set to `false`, will enforce that the following tags have at least
86+
some content:
87+
88+
- `@copyright`
89+
- `@example`
90+
- `@see`
91+
- `@todo`
92+
- `@throws`/`@exception`
93+
- `@yields`/`@yield`
94+
95+
If you supply your own tag description for any of the above tags in `tags`,
96+
your description will take precedence.
97+
7498
<a name="user-content-match-description-options-tags"></a>
7599
<a name="match-description-options-tags"></a>
76100
### <code>tags</code>
@@ -186,7 +210,7 @@ section of our README for more on the expected format.
186210
|Aliases|`@desc`|
187211
|Recommended|false|
188212
|Settings||
189-
|Options|`contexts`, `mainDescription`, `matchDescription`, `message`, `tags`|
213+
|Options|`contexts`, `mainDescription`, `matchDescription`, `message`, `nonemptyTags`, `tags`|
190214

191215
<a name="user-content-match-description-failing-examples"></a>
192216
<a name="match-description-failing-examples"></a>
@@ -335,7 +359,6 @@ function quux (foo) {
335359
function quux () {
336360

337361
}
338-
// "jsdoc/match-description": ["error"|"warn", {"tags":{"summary":true}}]
339362
// Message: JSDoc description does not satisfy the regex pattern.
340363

341364
/**
@@ -368,7 +391,6 @@ function quux () {
368391
function quux (foo) {
369392

370393
}
371-
// "jsdoc/match-description": ["error"|"warn", {"tags":{"description":true}}]
372394
// Message: JSDoc description does not satisfy the regex pattern.
373395

374396
/**
@@ -602,6 +624,13 @@ function quux () {
602624
function foo(): string;
603625
// "jsdoc/match-description": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock[endLine=0]"}],"matchDescription":"^\\S[\\s\\S]*\\S$"}]
604626
// Message: JSDoc description does not satisfy the regex pattern.
627+
628+
/**
629+
* @copyright
630+
*/
631+
function quux () {
632+
}
633+
// Message: JSDoc description must not be empty.
605634
````
606635

607636

@@ -722,7 +751,6 @@ function quux () {
722751
function quux () {
723752

724753
}
725-
// "jsdoc/match-description": ["error"|"warn", {"tags":{"description":true}}]
726754

727755
/**
728756
* @description Foo
@@ -732,13 +760,11 @@ function quux () {
732760
function quux () {
733761

734762
}
735-
// "jsdoc/match-description": ["error"|"warn", {"tags":{"description":true}}]
736763

737764
/** @description Foo bar. */
738765
function quux () {
739766

740767
}
741-
// "jsdoc/match-description": ["error"|"warn", {"tags":{"description":true}}]
742768

743769
/**
744770
* @description Foo
@@ -747,7 +773,6 @@ function quux () {
747773
function quux () {
748774

749775
}
750-
// "jsdoc/match-description": ["error"|"warn", {"tags":{"description":true}}]
751776

752777
/**
753778
* Foo. {@see Math.sin}.
@@ -872,7 +897,7 @@ const q = {
872897
// "jsdoc/match-description": ["error"|"warn", {"contexts":[]}]
873898

874899
/**
875-
* @description foo.
900+
* @deprecated foo.
876901
*/
877902
function quux () {
878903

@@ -887,7 +912,6 @@ function quux () {
887912
function quux () {
888913

889914
}
890-
// "jsdoc/match-description": ["error"|"warn", {"tags":{"summary":true}}]
891915

892916
/**
893917
* Foo.
@@ -975,5 +999,12 @@ function foo(): string;
975999
*/
9761000
function foo(): void;
9771001
// "jsdoc/match-description": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock[endLine!=0]:not(:has(JsdocTag))"}],"matchDescription":"^\\S[\\s\\S]*\\S$"}]
1002+
1003+
/**
1004+
* @copyright
1005+
*/
1006+
function quux () {
1007+
}
1008+
// "jsdoc/match-description": ["error"|"warn", {"nonemptyTags":false}]
9781009
````
9791010

src/iterateJsdoc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1644,7 +1644,7 @@ const getUtils = (
16441644

16451645
/** @type {GetTagsByType} */
16461646
utils.getTagsByType = (tags) => {
1647-
return jsdocUtils.getTagsByType(context, mode, tags, tagNamePreference);
1647+
return jsdocUtils.getTagsByType(context, mode, tags);
16481648
};
16491649

16501650
/** @type {HasOptionTag} */

src/jsdocUtils.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,14 +1422,12 @@ const tagsWithNamesAndDescriptions = new Set([
14221422
* @param {import('eslint').Rule.RuleContext} context
14231423
* @param {ParserMode|undefined} mode
14241424
* @param {import('comment-parser').Spec[]} tags
1425-
* @param {TagNamePreference} tagPreference
14261425
* @returns {{
14271426
* tagsWithNames: import('comment-parser').Spec[],
14281427
* tagsWithoutNames: import('comment-parser').Spec[]
14291428
* }}
14301429
*/
1431-
const getTagsByType = (context, mode, tags, tagPreference) => {
1432-
const descName = getPreferredTagName(context, mode, 'description', tagPreference);
1430+
const getTagsByType = (context, mode, tags) => {
14331431
/**
14341432
* @type {import('comment-parser').Spec[]}
14351433
*/
@@ -1439,7 +1437,7 @@ const getTagsByType = (context, mode, tags, tagPreference) => {
14391437
tag: tagName,
14401438
} = tag;
14411439
const tagWithName = tagsWithNamesAndDescriptions.has(tagName);
1442-
if (!tagWithName && tagName !== descName) {
1440+
if (!tagWithName) {
14431441
tagsWithoutNames.push(tag);
14441442
}
14451443

src/rules/matchDescription.js

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export default iterateJsdoc(({
2525
mainDescription,
2626
matchDescription,
2727
message,
28-
tags,
28+
nonemptyTags = true,
29+
tags = {},
2930
} = context.options[0] || {};
3031

3132
/**
@@ -81,7 +82,52 @@ export default iterateJsdoc(({
8182
validateDescription(description);
8283
}
8384

84-
if (!tags || !Object.keys(tags).length) {
85+
/**
86+
* @param {string} tagName
87+
* @returns {boolean}
88+
*/
89+
const hasNoTag = (tagName) => {
90+
return !tags[tagName];
91+
};
92+
93+
for (const tag of [
94+
'description',
95+
'summary',
96+
'file',
97+
'classdesc',
98+
]) {
99+
utils.forEachPreferredTag(tag, (matchingJsdocTag, targetTagName) => {
100+
const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim();
101+
if (hasNoTag(targetTagName)) {
102+
validateDescription(desc, matchingJsdocTag);
103+
}
104+
}, true);
105+
}
106+
107+
if (nonemptyTags) {
108+
for (const tag of [
109+
'copyright',
110+
'example',
111+
'see',
112+
'throws',
113+
'todo',
114+
'yields',
115+
]) {
116+
utils.forEachPreferredTag(tag, (matchingJsdocTag, targetTagName) => {
117+
const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim();
118+
119+
if (hasNoTag(targetTagName) && !(/.+/u).test(desc)) {
120+
report(
121+
'JSDoc description must not be empty.',
122+
null,
123+
matchingJsdocTag,
124+
);
125+
}
126+
});
127+
}
128+
}
129+
130+
if (!Object.keys(tags).length) {
85131
return;
86132
}
87133

@@ -93,13 +139,6 @@ export default iterateJsdoc(({
93139
return Boolean(tags[tagName]);
94140
};
95141

96-
utils.forEachPreferredTag('description', (matchingJsdocTag, targetTagName) => {
97-
const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim();
98-
if (hasOptionTag(targetTagName)) {
99-
validateDescription(desc, matchingJsdocTag);
100-
}
101-
}, true);
102-
103142
const whitelistedTags = utils.filterTags(({
104143
tag: tagName,
105144
}) => {
@@ -195,6 +234,9 @@ export default iterateJsdoc(({
195234
message: {
196235
type: 'string',
197236
},
237+
nonemptyTags: {
238+
type: 'boolean',
239+
},
198240
tags: {
199241
patternProperties: {
200242
'.*': {

0 commit comments

Comments
 (0)