Skip to content

Commit 47299db

Browse files
committed
feat(required-tags): add new rule; fixes #1235
1 parent d9ca0ac commit 47299db

File tree

12 files changed

+430
-95
lines changed

12 files changed

+430
-95
lines changed

.README/rules/required-tags.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# `required-tags`
2+
3+
Requires tags be present, optionally for specific contexts.
4+
5+
## Options
6+
7+
{"gitdown": "options"}
8+
9+
|||
10+
|---|---|
11+
|Context|everywhere|
12+
|Tags|(Any)|
13+
|Recommended|false|
14+
|Settings||
15+
|Options|`tags`|
16+
17+
## Failing examples
18+
19+
<!-- assertions-failing requiredTags -->
20+
21+
## Passing examples
22+
23+
<!-- assertions-passing requiredTags -->

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ non-default-recommended fixer).
487487
|:heavy_check_mark:|| [require-yields-check](./docs/rules/require-yields-check.md#readme) | Ensures that if a `@yields` is present that a `yield` (or `yield` with a value) is present in the function body (or that if a `@next` is present that there is a yield with a return value present). |
488488
||| [require-yields-description](./docs/rules/require-yields-description.md#readme) | Requires a description for `@yields` tags |
489489
|:heavy_check_mark:|| [require-yields-type](./docs/rules/require-yields-type.md#readme) | Requires a type for `@yields` tags |
490+
||| [required-tags](./docs/rules/required-tags.md#readme) | Requires tags be present, optionally for specific contexts |
490491
||:wrench:| [sort-tags](./docs/rules/sort-tags.md#readme) | Sorts tags by a specified sequence according to tag name, optionally adding line breaks between tag groups. |
491492
|:heavy_check_mark:|:wrench:| [tag-lines](./docs/rules/tag-lines.md#readme) | Enforces lines (or no lines) between tags. |
492493
||:wrench:| [text-escaping](./docs/rules/text-escaping.md#readme) | Auto-escape certain characters that are input within block and tag descriptions. |

docs/rules/required-tags.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<a name="user-content-required-tags"></a>
2+
<a name="required-tags"></a>
3+
# <code>required-tags</code>
4+
5+
Requires tags be present, optionally for specific contexts.
6+
7+
<a name="user-content-required-tags-options"></a>
8+
<a name="required-tags-options"></a>
9+
## Options
10+
11+
A single options object has the following properties.
12+
13+
<a name="user-content-required-tags-options-tags"></a>
14+
<a name="required-tags-options-tags"></a>
15+
### <code>tags</code>
16+
17+
May be an array of either strings or objects with
18+
a string `tag` property and `context` string property.
19+
20+
21+
|||
22+
|---|---|
23+
|Context|everywhere|
24+
|Tags|(Any)|
25+
|Recommended|false|
26+
|Settings||
27+
|Options|`tags`|
28+
29+
<a name="user-content-required-tags-failing-examples"></a>
30+
<a name="required-tags-failing-examples"></a>
31+
## Failing examples
32+
33+
The following patterns are considered problems:
34+
35+
````ts
36+
/**
37+
*
38+
*/
39+
function quux () {}
40+
// "jsdoc/required-tags": ["error"|"warn", {"tags":["see"]}]
41+
// Message: Missing required tag "see"
42+
43+
/**
44+
*
45+
*/
46+
function quux () {}
47+
// "jsdoc/required-tags": ["error"|"warn", {"tags":[{"context":"FunctionDeclaration","tag":"see"}]}]
48+
// Message: Missing required tag "see"
49+
50+
/**
51+
* @type {SomeType}
52+
*/
53+
function quux () {}
54+
// "jsdoc/required-tags": ["error"|"warn", {"tags":[{"context":"FunctionDeclaration","tag":"see"}]}]
55+
// Message: Missing required tag "see"
56+
57+
/**
58+
* @type {SomeType}
59+
*/
60+
function quux () {}
61+
// Message: Rule `required-tags` is missing a `tags` option.
62+
````
63+
64+
65+
66+
<a name="user-content-required-tags-passing-examples"></a>
67+
<a name="required-tags-passing-examples"></a>
68+
## Passing examples
69+
70+
The following patterns are not considered problems:
71+
72+
````ts
73+
/**
74+
* @see
75+
*/
76+
function quux () {}
77+
// "jsdoc/required-tags": ["error"|"warn", {"tags":["see"]}]
78+
79+
/**
80+
*
81+
*/
82+
class Quux {}
83+
// "jsdoc/required-tags": ["error"|"warn", {"tags":[{"context":"FunctionDeclaration","tag":"see"}]}]
84+
````
85+

src/bin/generateRule.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ export default iterateJsdoc(({
117117

118118
const ruleReadmeTemplate = `# \`${ruleName}\`
119119
120+
## Options
121+
122+
{"gitdown": "options"}
123+
120124
|||
121125
|---|---|
122126
|Context|everywhere|

src/buildForbidRuleDefinition.js

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
import iterateJsdoc from './iterateJsdoc.js';
22

3+
/**
4+
* @typedef {(string|{
5+
* comment: string,
6+
* context: string,
7+
* message?: string
8+
* })[]} Contexts
9+
*/
10+
311
/**
412
* @param {{
5-
* contexts: (string|{
6-
* comment: string,
7-
* context: string,
8-
* message: string
9-
* })[],
13+
* contexts?: Contexts,
1014
* description?: string,
11-
* contextName?: string
15+
* getContexts?: (
16+
* ctxt: import('eslint').Rule.RuleContext,
17+
* report: import('./iterateJsdoc.js').Report
18+
* ) => Contexts|false,
19+
* contextName?: string,
20+
* modifyContext?: (context: import('eslint').Rule.RuleContext) => import('eslint').Rule.RuleContext,
21+
* schema?: import('eslint').Rule.RuleMetaData['schema']
1222
* url?: string,
1323
* }} cfg
1424
* @returns {import('@eslint/core').RuleDefinition<
@@ -17,22 +27,35 @@ import iterateJsdoc from './iterateJsdoc.js';
1727
*/
1828
export const buildForbidRuleDefinition = ({
1929
contextName,
20-
contexts,
30+
contexts: cntxts,
2131
description,
32+
getContexts,
33+
modifyContext,
34+
schema,
2235
url,
2336
}) => {
2437
return iterateJsdoc(({
25-
// context,
38+
context,
2639
info: {
2740
comment,
2841
},
2942
report,
3043
utils,
3144
}) => {
45+
/** @type {Contexts|boolean|undefined} */
46+
let contexts = cntxts;
47+
48+
if (getContexts) {
49+
contexts = getContexts(context, report);
50+
if (!contexts) {
51+
return;
52+
}
53+
}
54+
3255
const {
3356
contextStr,
3457
foundContext,
35-
} = utils.findContext(contexts, comment);
58+
} = utils.findContext(/** @type {Contexts} */ (contexts), comment);
3659

3760
// We are not on the *particular* matching context/comment, so don't assume
3861
// we need reporting
@@ -59,10 +82,10 @@ export const buildForbidRuleDefinition = ({
5982
description: description ?? contextName ?? 'Reports when certain comment structures are present.',
6083
url: url ?? 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/advanced.md#user-content-advanced-creating-your-own-rules',
6184
},
62-
schema: [],
85+
schema: schema ?? [],
6386
type: 'suggestion',
6487
},
65-
modifyContext: (context) => {
88+
modifyContext: modifyContext ?? (getContexts ? undefined : (context) => {
6689
// Reproduce context object with our own `contexts`
6790
const propertyDescriptors = Object.getOwnPropertyDescriptors(context);
6891
return Object.create(
@@ -73,13 +96,13 @@ export const buildForbidRuleDefinition = ({
7396
...propertyDescriptors.options,
7497
value: [
7598
{
76-
contexts,
99+
contexts: cntxts,
77100
},
78101
],
79102
},
80103
},
81104
);
82-
},
105+
}),
83106
nonGlobalSettings: true,
84107
});
85108
};

src/index-cjs.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import noUndefinedTypes from './rules/noUndefinedTypes.js';
4040
import requireAsteriskPrefix from './rules/requireAsteriskPrefix.js';
4141
import requireDescription from './rules/requireDescription.js';
4242
import requireDescriptionCompleteSentence from './rules/requireDescriptionCompleteSentence.js';
43+
import requiredTags from './rules/requiredTags.js';
4344
import requireExample from './rules/requireExample.js';
4445
import requireFileOverview from './rules/requireFileOverview.js';
4546
import requireHyphenBeforeParamDescription from './rules/requireHyphenBeforeParamDescription.js';
@@ -226,6 +227,7 @@ index.rules = {
226227
description: 'Requires a type for `@yields` tags',
227228
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-yields-type.md#repos-sticky-header',
228229
}),
230+
'required-tags': requiredTags,
229231
'sort-tags': sortTags,
230232
'tag-lines': tagLines,
231233
'text-escaping': textEscaping,
@@ -312,6 +314,7 @@ const createRecommendedRuleset = (warnOrError, flatName) => {
312314
'jsdoc/require-yields-check': warnOrError,
313315
'jsdoc/require-yields-description': 'off',
314316
'jsdoc/require-yields-type': warnOrError,
317+
'jsdoc/required-tags': 'off',
315318
'jsdoc/sort-tags': 'off',
316319
'jsdoc/tag-lines': warnOrError,
317320
'jsdoc/text-escaping': 'off',

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import noUndefinedTypes from './rules/noUndefinedTypes.js';
4646
import requireAsteriskPrefix from './rules/requireAsteriskPrefix.js';
4747
import requireDescription from './rules/requireDescription.js';
4848
import requireDescriptionCompleteSentence from './rules/requireDescriptionCompleteSentence.js';
49+
import requiredTags from './rules/requiredTags.js';
4950
import requireExample from './rules/requireExample.js';
5051
import requireFileOverview from './rules/requireFileOverview.js';
5152
import requireHyphenBeforeParamDescription from './rules/requireHyphenBeforeParamDescription.js';
@@ -232,6 +233,7 @@ index.rules = {
232233
description: 'Requires a type for `@yields` tags',
233234
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-yields-type.md#repos-sticky-header',
234235
}),
236+
'required-tags': requiredTags,
235237
'sort-tags': sortTags,
236238
'tag-lines': tagLines,
237239
'text-escaping': textEscaping,
@@ -318,6 +320,7 @@ const createRecommendedRuleset = (warnOrError, flatName) => {
318320
'jsdoc/require-yields-check': warnOrError,
319321
'jsdoc/require-yields-description': 'off',
320322
'jsdoc/require-yields-type': warnOrError,
323+
'jsdoc/required-tags': 'off',
321324
'jsdoc/sort-tags': 'off',
322325
'jsdoc/tag-lines': warnOrError,
323326
'jsdoc/text-escaping': 'off',

src/rules.d.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2580,6 +2580,26 @@ export interface Rules {
25802580
/** Requires a type for `@yields` tags */
25812581
"jsdoc/require-yields-type": [];
25822582

2583+
/** Requires tags be present, optionally for specific contexts */
2584+
"jsdoc/required-tags":
2585+
| []
2586+
| [
2587+
{
2588+
/**
2589+
* May be an array of either strings or objects with
2590+
* a string `tag` property and `context` string property.
2591+
*/
2592+
tags?: (
2593+
| string
2594+
| {
2595+
context?: string;
2596+
tag?: string;
2597+
[k: string]: unknown;
2598+
}
2599+
)[];
2600+
}
2601+
];
2602+
25832603
/** Sorts tags by a specified sequence according to tag name, optionally adding line breaks between tag groups. */
25842604
"jsdoc/sort-tags":
25852605
| []

0 commit comments

Comments
 (0)