diff --git a/.README/rules/match-description.md b/.README/rules/match-description.md
index e829014ed..33108fc82 100644
--- a/.README/rules/match-description.md
+++ b/.README/rules/match-description.md
@@ -10,8 +10,14 @@ by our supported Node versions):
``^\n?([A-Z`\\d_][\\s\\S]*[.?!`]\\s*)?$``
-Applies to the jsdoc block description and `@description` (or `@desc`)
-by default but the `tags` option (see below) may be used to match other tags.
+Applies by default to the jsdoc block description and to the following tags:
+
+- `@description`/`@desc`
+- `@summary`
+- `@file`/`@fileoverview`/`@overview`
+- `@classdesc`
+
+In addition, the `tags` option (see below) may be used to match other tags.
The default (and all regex options) defaults to using (only) the `u` flag, so
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:
This can be overridden per tag or for the main block description by setting
`message` within `tags` or `mainDescription`, respectively.
+### `nonemptyTags`
+
+If not set to `false`, will enforce that the following tags have at least
+some content:
+
+- `@copyright`
+- `@example`
+- `@see`
+- `@todo`
+- `@throws`/`@exception`
+- `@yields`/`@yield`
+
+If you supply your own tag description for any of the above tags in `tags`,
+your description will take precedence.
+
### `tags`
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.
|Aliases|`@desc`|
|Recommended|false|
|Settings||
-|Options|`contexts`, `mainDescription`, `matchDescription`, `message`, `tags`|
+|Options|`contexts`, `mainDescription`, `matchDescription`, `message`, `nonemptyTags`, `tags`|
## Failing examples
diff --git a/.README/rules/require-description.md b/.README/rules/require-description.md
index 0582936cd..af9337d4c 100644
--- a/.README/rules/require-description.md
+++ b/.README/rules/require-description.md
@@ -2,7 +2,8 @@
{"gitdown": "contents", "rootId": "require-description"}
-Requires that all functions have a description.
+Requires that all functions (or optionally other structures) with a JSDoc block
+have a description.
* All functions must have an implicit description (e.g., text above tags) or
have the option `descriptionStyle` set to `tag` (requiring `@description`
diff --git a/docs/rules/match-description.md b/docs/rules/match-description.md
index 1f198523d..a2f02187b 100644
--- a/docs/rules/match-description.md
+++ b/docs/rules/match-description.md
@@ -5,6 +5,7 @@
* [Options](#user-content-match-description-options)
* [`matchDescription`](#user-content-match-description-options-matchdescription)
* [`message`](#user-content-match-description-options-message)
+ * [`nonemptyTags`](#user-content-match-description-options-nonemptytags)
* [`tags`](#user-content-match-description-options-tags)
* [`mainDescription`](#user-content-match-description-options-maindescription)
* [`contexts`](#user-content-match-description-options-contexts)
@@ -21,8 +22,14 @@ by our supported Node versions):
``^\n?([A-Z`\\d_][\\s\\S]*[.?!`]\\s*)?$``
-Applies to the jsdoc block description and `@description` (or `@desc`)
-by default but the `tags` option (see below) may be used to match other tags.
+Applies by default to the jsdoc block description and to the following tags:
+
+- `@description`/`@desc`
+- `@summary`
+- `@file`/`@fileoverview`/`@overview`
+- `@classdesc`
+
+In addition, the `tags` option (see below) may be used to match other tags.
The default (and all regex options) defaults to using (only) the `u` flag, so
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:
This can be overridden per tag or for the main block description by setting
`message` within `tags` or `mainDescription`, respectively.
+
+
+### nonemptyTags
+
+If not set to `false`, will enforce that the following tags have at least
+some content:
+
+- `@copyright`
+- `@example`
+- `@see`
+- `@todo`
+- `@throws`/`@exception`
+- `@yields`/`@yield`
+
+If you supply your own tag description for any of the above tags in `tags`,
+your description will take precedence.
+
### tags
@@ -186,7 +210,7 @@ section of our README for more on the expected format.
|Aliases|`@desc`|
|Recommended|false|
|Settings||
-|Options|`contexts`, `mainDescription`, `matchDescription`, `message`, `tags`|
+|Options|`contexts`, `mainDescription`, `matchDescription`, `message`, `nonemptyTags`, `tags`|
@@ -335,7 +359,6 @@ function quux (foo) {
function quux () {
}
-// "jsdoc/match-description": ["error"|"warn", {"tags":{"summary":true}}]
// Message: JSDoc description does not satisfy the regex pattern.
/**
@@ -368,7 +391,6 @@ function quux () {
function quux (foo) {
}
-// "jsdoc/match-description": ["error"|"warn", {"tags":{"description":true}}]
// Message: JSDoc description does not satisfy the regex pattern.
/**
@@ -602,6 +624,13 @@ function quux () {
function foo(): string;
// "jsdoc/match-description": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock[endLine=0]"}],"matchDescription":"^\\S[\\s\\S]*\\S$"}]
// Message: JSDoc description does not satisfy the regex pattern.
+
+/**
+ * @copyright
+ */
+function quux () {
+}
+// Message: JSDoc description must not be empty.
````
@@ -722,7 +751,6 @@ function quux () {
function quux () {
}
-// "jsdoc/match-description": ["error"|"warn", {"tags":{"description":true}}]
/**
* @description Foo
@@ -732,13 +760,11 @@ function quux () {
function quux () {
}
-// "jsdoc/match-description": ["error"|"warn", {"tags":{"description":true}}]
/** @description Foo bar. */
function quux () {
}
-// "jsdoc/match-description": ["error"|"warn", {"tags":{"description":true}}]
/**
* @description Foo
@@ -747,7 +773,6 @@ function quux () {
function quux () {
}
-// "jsdoc/match-description": ["error"|"warn", {"tags":{"description":true}}]
/**
* Foo. {@see Math.sin}.
@@ -872,7 +897,7 @@ const q = {
// "jsdoc/match-description": ["error"|"warn", {"contexts":[]}]
/**
- * @description foo.
+ * @deprecated foo.
*/
function quux () {
@@ -887,7 +912,6 @@ function quux () {
function quux () {
}
-// "jsdoc/match-description": ["error"|"warn", {"tags":{"summary":true}}]
/**
* Foo.
@@ -975,5 +999,12 @@ function foo(): string;
*/
function foo(): void;
// "jsdoc/match-description": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock[endLine!=0]:not(:has(JsdocTag))"}],"matchDescription":"^\\S[\\s\\S]*\\S$"}]
+
+/**
+ * @copyright
+ */
+function quux () {
+}
+// "jsdoc/match-description": ["error"|"warn", {"nonemptyTags":false}]
````
diff --git a/docs/rules/match-name.md b/docs/rules/match-name.md
index c5fe1abce..fd25834f5 100644
--- a/docs/rules/match-name.md
+++ b/docs/rules/match-name.md
@@ -178,6 +178,12 @@ function quux () {}
*/
// "jsdoc/match-name": ["error"|"warn", {"match":[{"disallowName":"/^opt_/i","replacement":""}]}]
// Message: Only allowing names not matching `/^opt_/i` but found "opt_a".
+
+/**
+ * @template
+ */
+// "jsdoc/match-name": ["error"|"warn", {"match":[{"disallowName":"/^$/","tags":["template"]}]}]
+// Message: Only allowing names not matching `/^$/u` but found "".
````
diff --git a/docs/rules/no-restricted-syntax.md b/docs/rules/no-restricted-syntax.md
index e4622248f..d8f7ec248 100644
--- a/docs/rules/no-restricted-syntax.md
+++ b/docs/rules/no-restricted-syntax.md
@@ -373,5 +373,11 @@ class Test {
abstract Test(): void;
}
// "jsdoc/no-restricted-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:not(*:has(JsdocTag[tag=/returns/]))","context":"TSEmptyBodyFunctionExpression[returnType.typeAnnotation.type!=/TSVoidKeyword|TSUndefinedKeyword/]","message":"methods with non-void return types must have a @returns tag"}]}]
+
+/**
+ * @private
+ */
+function quux () {}
+// "jsdoc/no-restricted-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:not(JsdocBlock:has(JsdocTag[tag=/private|protected|public/]))","context":"any","message":"Access modifier tags must be present"}]}]
````
diff --git a/docs/rules/require-description.md b/docs/rules/require-description.md
index 7a344a453..7d2143da1 100644
--- a/docs/rules/require-description.md
+++ b/docs/rules/require-description.md
@@ -8,7 +8,8 @@
* [Passing examples](#user-content-require-description-passing-examples)
-Requires that all functions have a description.
+Requires that all functions (or optionally other structures) with a JSDoc block
+have a description.
* All functions must have an implicit description (e.g., text above tags) or
have the option `descriptionStyle` set to `tag` (requiring `@description`
diff --git a/src/getDefaultTagStructureForMode.js b/src/getDefaultTagStructureForMode.js
index 5f473b59b..44cd80ce9 100644
--- a/src/getDefaultTagStructureForMode.js
+++ b/src/getDefaultTagStructureForMode.js
@@ -838,6 +838,10 @@ const getDefaultTagStructureForMode = (mode) => {
'namepathRole', isJsdoc ? 'text' : 'namepath-referencing',
],
+ [
+ 'nameRequired', !isJsdoc,
+ ],
+
// Though defines `namepathRole: 'namepath-defining'` in a sense, it is
// not parseable in the same way for template (e.g., allowing commas),
// so not adding
diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js
index f32690e3e..e6a57ec44 100644
--- a/src/iterateJsdoc.js
+++ b/src/iterateJsdoc.js
@@ -1644,7 +1644,7 @@ const getUtils = (
/** @type {GetTagsByType} */
utils.getTagsByType = (tags) => {
- return jsdocUtils.getTagsByType(context, mode, tags, tagNamePreference);
+ return jsdocUtils.getTagsByType(context, mode, tags);
};
/** @type {HasOptionTag} */
diff --git a/src/jsdocUtils.js b/src/jsdocUtils.js
index 49fbcd7c7..10a9f6a98 100644
--- a/src/jsdocUtils.js
+++ b/src/jsdocUtils.js
@@ -1422,14 +1422,12 @@ const tagsWithNamesAndDescriptions = new Set([
* @param {import('eslint').Rule.RuleContext} context
* @param {ParserMode|undefined} mode
* @param {import('comment-parser').Spec[]} tags
- * @param {TagNamePreference} tagPreference
* @returns {{
* tagsWithNames: import('comment-parser').Spec[],
* tagsWithoutNames: import('comment-parser').Spec[]
* }}
*/
-const getTagsByType = (context, mode, tags, tagPreference) => {
- const descName = getPreferredTagName(context, mode, 'description', tagPreference);
+const getTagsByType = (context, mode, tags) => {
/**
* @type {import('comment-parser').Spec[]}
*/
@@ -1439,7 +1437,7 @@ const getTagsByType = (context, mode, tags, tagPreference) => {
tag: tagName,
} = tag;
const tagWithName = tagsWithNamesAndDescriptions.has(tagName);
- if (!tagWithName && tagName !== descName) {
+ if (!tagWithName) {
tagsWithoutNames.push(tag);
}
diff --git a/src/rules/matchDescription.js b/src/rules/matchDescription.js
index 0f1a181c0..3205d7655 100644
--- a/src/rules/matchDescription.js
+++ b/src/rules/matchDescription.js
@@ -25,7 +25,8 @@ export default iterateJsdoc(({
mainDescription,
matchDescription,
message,
- tags,
+ nonemptyTags = true,
+ tags = {},
} = context.options[0] || {};
/**
@@ -81,7 +82,52 @@ export default iterateJsdoc(({
validateDescription(description);
}
- if (!tags || !Object.keys(tags).length) {
+ /**
+ * @param {string} tagName
+ * @returns {boolean}
+ */
+ const hasNoTag = (tagName) => {
+ return !tags[tagName];
+ };
+
+ for (const tag of [
+ 'description',
+ 'summary',
+ 'file',
+ 'classdesc',
+ ]) {
+ utils.forEachPreferredTag(tag, (matchingJsdocTag, targetTagName) => {
+ const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim();
+ if (hasNoTag(targetTagName)) {
+ validateDescription(desc, matchingJsdocTag);
+ }
+ }, true);
+ }
+
+ if (nonemptyTags) {
+ for (const tag of [
+ 'copyright',
+ 'example',
+ 'see',
+ 'throws',
+ 'todo',
+ 'yields',
+ ]) {
+ utils.forEachPreferredTag(tag, (matchingJsdocTag, targetTagName) => {
+ const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim();
+
+ if (hasNoTag(targetTagName) && !(/.+/u).test(desc)) {
+ report(
+ 'JSDoc description must not be empty.',
+ null,
+ matchingJsdocTag,
+ );
+ }
+ });
+ }
+ }
+
+ if (!Object.keys(tags).length) {
return;
}
@@ -93,13 +139,6 @@ export default iterateJsdoc(({
return Boolean(tags[tagName]);
};
- utils.forEachPreferredTag('description', (matchingJsdocTag, targetTagName) => {
- const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim();
- if (hasOptionTag(targetTagName)) {
- validateDescription(desc, matchingJsdocTag);
- }
- }, true);
-
const whitelistedTags = utils.filterTags(({
tag: tagName,
}) => {
@@ -195,6 +234,9 @@ export default iterateJsdoc(({
message: {
type: 'string',
},
+ nonemptyTags: {
+ type: 'boolean',
+ },
tags: {
patternProperties: {
'.*': {
diff --git a/test/rules/assertions/matchDescription.js b/test/rules/assertions/matchDescription.js
index d25817cc2..776dedb62 100644
--- a/test/rules/assertions/matchDescription.js
+++ b/test/rules/assertions/matchDescription.js
@@ -344,13 +344,6 @@ export default {
message: 'JSDoc description does not satisfy the regex pattern.',
},
],
- options: [
- {
- tags: {
- summary: true,
- },
- },
- ],
},
{
code: `
@@ -419,13 +412,6 @@ export default {
message: 'JSDoc description does not satisfy the regex pattern.',
},
],
- options: [
- {
- tags: {
- description: true,
- },
- },
- ],
},
{
code: `
@@ -1001,6 +987,21 @@ export default {
],
parser: require.resolve('@typescript-eslint/parser'),
},
+ {
+ code: `
+ /**
+ * @copyright
+ */
+ function quux () {
+ }
+ `,
+ errors: [
+ {
+ line: 3,
+ message: 'JSDoc description must not be empty.',
+ },
+ ],
+ },
],
valid: [
{
@@ -1192,13 +1193,6 @@ export default {
}
`,
- options: [
- {
- tags: {
- description: true,
- },
- },
- ],
},
{
code: `
@@ -1211,13 +1205,6 @@ export default {
}
`,
- options: [
- {
- tags: {
- description: true,
- },
- },
- ],
},
{
code: `
@@ -1226,13 +1213,6 @@ export default {
}
`,
- options: [
- {
- tags: {
- description: true,
- },
- },
- ],
},
{
code: `
@@ -1244,13 +1224,6 @@ export default {
}
`,
- options: [
- {
- tags: {
- description: true,
- },
- },
- ],
},
{
code: `
@@ -1456,7 +1429,7 @@ export default {
{
code: `
/**
- * @description foo.
+ * @deprecated foo.
*/
function quux () {
@@ -1481,13 +1454,6 @@ export default {
}
`,
- options: [
- {
- tags: {
- summary: true,
- },
- },
- ],
},
{
code: `
@@ -1697,5 +1663,19 @@ export default {
],
parser: require.resolve('@typescript-eslint/parser'),
},
+ {
+ code: `
+ /**
+ * @copyright
+ */
+ function quux () {
+ }
+ `,
+ options: [
+ {
+ nonemptyTags: false,
+ },
+ ],
+ },
],
};
diff --git a/test/rules/assertions/matchName.js b/test/rules/assertions/matchName.js
index 8c933da70..9cc7eec58 100644
--- a/test/rules/assertions/matchName.js
+++ b/test/rules/assertions/matchName.js
@@ -357,6 +357,31 @@ export default {
*/
`,
},
+ {
+ code: `
+ /**
+ * @template
+ */
+ `,
+ errors: [
+ {
+ line: 3,
+ message: 'Only allowing names not matching `/^$/u` but found "".',
+ },
+ ],
+ options: [
+ {
+ match: [
+ {
+ disallowName: '/^$/',
+ tags: [
+ 'template',
+ ],
+ },
+ ],
+ },
+ ],
+ },
],
valid: [
{
diff --git a/test/rules/assertions/noRestrictedSyntax.js b/test/rules/assertions/noRestrictedSyntax.js
index d49395e75..459d1905e 100644
--- a/test/rules/assertions/noRestrictedSyntax.js
+++ b/test/rules/assertions/noRestrictedSyntax.js
@@ -1042,5 +1042,24 @@ export default {
],
parser: require.resolve('@typescript-eslint/parser'),
},
+ {
+ code: `
+ /**
+ * @private
+ */
+ function quux () {}
+ `,
+ options: [
+ {
+ contexts: [
+ {
+ comment: 'JsdocBlock:not(JsdocBlock:has(JsdocTag[tag=/private|protected|public/]))',
+ context: 'any',
+ message: 'Access modifier tags must be present',
+ },
+ ],
+ },
+ ],
+ },
],
};