Skip to content

Commit 0adfbe6

Browse files
committed
feat(require-returns): per-context forceRequireReturn; fixes gajus#757
1 parent 034ade1 commit 0adfbe6

File tree

6 files changed

+167
-69
lines changed

6 files changed

+167
-69
lines changed

docs/rules/require-returns.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,20 @@ export default async function demo() {
634634
return true;
635635
}
636636
// Message: Missing JSDoc @returns declaration.
637+
638+
/**
639+
*
640+
*/
641+
function quux () {}
642+
643+
class Test {
644+
/**
645+
*
646+
*/
647+
abstract Test(): string;
648+
}
649+
// "jsdoc/require-returns": ["error"|"warn", {"contexts":["FunctionDeclaration",{"context":"TSEmptyBodyFunctionExpression","forceRequireReturn":true}]}]
650+
// Message: Missing JSDoc @returns declaration.
637651
````
638652

639653

@@ -1160,5 +1174,18 @@ export function readFixture(path: string): void;
11601174
* Reads a test fixture.
11611175
*/
11621176
export function readFixture(path: string);
1177+
1178+
/**
1179+
*
1180+
*/
1181+
function quux () {}
1182+
1183+
class Test {
1184+
/**
1185+
* @returns {string} The test value
1186+
*/
1187+
abstract Test(): string;
1188+
}
1189+
// "jsdoc/require-returns": ["error"|"warn", {"contexts":["FunctionDeclaration",{"context":"TSEmptyBodyFunctionExpression","forceRequireReturn":true}]}]
11631190
````
11641191

src/iterateJsdoc.js

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
stringify as commentStringify,
99
util,
1010
} from 'comment-parser';
11+
import esquery from 'esquery';
1112

1213
/**
1314
* @typedef {number} Integer
@@ -26,7 +27,8 @@ import {
2627
* tags?: string[],
2728
* replacement?: string,
2829
* minimum?: Integer,
29-
* message?: string
30+
* message?: string,
31+
* forceRequireReturn?: boolean
3032
* }} ContextObject
3133
*/
3234
/**
@@ -467,6 +469,16 @@ import {
467469
* @returns {boolean}
468470
*/
469471

472+
/**
473+
* @callback FindContext
474+
* @param {Context[]} contexts
475+
* @param {string|undefined} comment
476+
* @returns {{
477+
* foundContext: Context|undefined,
478+
* contextStr: string
479+
* }}
480+
*/
481+
470482
/**
471483
* @typedef {BasicUtils & {
472484
* isIteratingFunction: IsIteratingFunction,
@@ -526,7 +538,8 @@ import {
526538
* hasOptionTag: HasOptionTag,
527539
* getClassNode: GetClassNode,
528540
* getClassJsdoc: GetClassJsdoc,
529-
* classHasTag: ClassHasTag
541+
* classHasTag: ClassHasTag,
542+
* findContext: FindContext
530543
* }} Utils
531544
*/
532545

@@ -1712,6 +1725,39 @@ const getUtils = (
17121725
}
17131726
};
17141727

1728+
/** @type {FindContext} */
1729+
utils.findContext = (contexts, comment) => {
1730+
const foundContext = contexts.find((cntxt) => {
1731+
return typeof cntxt === 'string' ?
1732+
esquery.matches(
1733+
/** @type {Node} */ (node),
1734+
esquery.parse(cntxt),
1735+
undefined,
1736+
{
1737+
visitorKeys: sourceCode.visitorKeys,
1738+
},
1739+
) :
1740+
(!cntxt.context || cntxt.context === 'any' ||
1741+
esquery.matches(
1742+
/** @type {Node} */ (node),
1743+
esquery.parse(cntxt.context),
1744+
undefined,
1745+
{
1746+
visitorKeys: sourceCode.visitorKeys,
1747+
},
1748+
)) && comment === cntxt.comment;
1749+
});
1750+
1751+
const contextStr = typeof foundContext === 'object' ?
1752+
foundContext.context ?? 'any' :
1753+
String(foundContext);
1754+
1755+
return {
1756+
contextStr,
1757+
foundContext,
1758+
};
1759+
};
1760+
17151761
return utils;
17161762
};
17171763

@@ -1938,7 +1984,6 @@ const makeReport = (context, commentNode) => {
19381984
* @param {JsdocBlockWithInline} jsdoc
19391985
* @param {RuleConfig} ruleConfig
19401986
* @param {import('eslint').Rule.RuleContext} context
1941-
* @param {string[]} lines
19421987
* @param {import('@es-joy/jsdoccomment').Token} jsdocNode
19431988
* @param {Node|null} node
19441989
* @param {Settings} settings
@@ -1951,7 +1996,7 @@ const makeReport = (context, commentNode) => {
19511996
const iterate = (
19521997
info,
19531998
indent, jsdoc,
1954-
ruleConfig, context, lines, jsdocNode, node, settings,
1999+
ruleConfig, context, jsdocNode, node, settings,
19552000
sourceCode, iterator, state, iteratingAll,
19562001
) => {
19572002
const jsdocNde = /** @type {unknown} */ (jsdocNode);
@@ -2145,7 +2190,6 @@ const iterateAllJsdocs = (iterator, ruleConfig, contexts, additiveCommentContext
21452190
jsdoc,
21462191
ruleConfig,
21472192
context,
2148-
lines,
21492193
jsdocNode,
21502194
/** @type {Node} */
21512195
(node),
@@ -2188,7 +2232,6 @@ const iterateAllJsdocs = (iterator, ruleConfig, contexts, additiveCommentContext
21882232
jsdoc,
21892233
ruleConfig,
21902234
context,
2191-
lines,
21922235
jsdocNode,
21932236
node,
21942237
/** @type {Settings} */
@@ -2448,7 +2491,6 @@ export default function iterateJsdoc (iterator, ruleConfig) {
24482491
jsdoc,
24492492
ruleConfig,
24502493
context,
2451-
lines,
24522494
jsdocNode,
24532495
node,
24542496
settings,

src/rules/noMissingSyntax.js

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import iterateJsdoc from '../iterateJsdoc';
2-
import esquery from 'esquery';
32

43
/**
54
* @typedef {{
@@ -44,12 +43,11 @@ const incrementSelector = (state, selector, comment) => {
4443

4544
export default iterateJsdoc(({
4645
context,
47-
node,
4846
info: {
4947
comment,
5048
},
51-
sourceCode,
5249
state,
50+
utils,
5351
}) => {
5452
if (!context.options[0]) {
5553
// Handle error later
@@ -61,30 +59,9 @@ export default iterateJsdoc(({
6159
*/
6260
const contexts = context.options[0].contexts;
6361

64-
const foundContext = contexts.find((cntxt) => {
65-
return typeof cntxt === 'string' ?
66-
esquery.matches(
67-
/** @type {import('../iterateJsdoc.js').Node} */ (node),
68-
esquery.parse(cntxt),
69-
undefined,
70-
{
71-
visitorKeys: sourceCode.visitorKeys,
72-
},
73-
) :
74-
(!cntxt.context || cntxt.context === 'any' ||
75-
esquery.matches(
76-
/** @type {import('../iterateJsdoc.js').Node} */ (node),
77-
esquery.parse(cntxt.context),
78-
undefined,
79-
{
80-
visitorKeys: sourceCode.visitorKeys,
81-
},
82-
)) && comment === cntxt.comment;
83-
});
84-
85-
const contextStr = typeof foundContext === 'object' ?
86-
foundContext.context ?? 'any' :
87-
String(foundContext);
62+
const {
63+
contextStr,
64+
} = utils.findContext(contexts, comment);
8865

8966
setDefaults(state);
9067

src/rules/noRestrictedSyntax.js

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import iterateJsdoc from '../iterateJsdoc';
2-
import esquery from 'esquery';
32

43
export default iterateJsdoc(({
5-
node,
64
context,
75
info: {
86
comment,
97
},
10-
sourceCode,
118
report,
9+
utils,
1210
}) => {
1311
if (!context.options.length) {
1412
report('Rule `no-restricted-syntax` is missing a `contexts` option.');
@@ -20,44 +18,20 @@ export default iterateJsdoc(({
2018
contexts,
2119
} = context.options[0];
2220

23-
const foundContext = contexts.find(
24-
/**
25-
* @param {string|{context: string, comment: string}} cntxt
26-
* @returns {boolean}
27-
*/
28-
(cntxt) => {
29-
return typeof cntxt === 'string' ?
30-
esquery.matches(
31-
/** @type {import('../iterateJsdoc.js').Node} */ (node),
32-
esquery.parse(cntxt),
33-
undefined,
34-
{
35-
visitorKeys: sourceCode.visitorKeys,
36-
},
37-
) :
38-
(!cntxt.context || cntxt.context === 'any' ||
39-
esquery.matches(
40-
/** @type {import('../iterateJsdoc.js').Node} */ (node),
41-
esquery.parse(cntxt.context),
42-
undefined,
43-
{
44-
visitorKeys: sourceCode.visitorKeys,
45-
},
46-
)) &&
47-
comment === cntxt.comment;
48-
},
49-
);
21+
const {
22+
foundContext,
23+
contextStr,
24+
} = utils.findContext(contexts, comment);
5025

5126
// We are not on the *particular* matching context/comment, so don't assume
5227
// we need reporting
5328
if (!foundContext) {
5429
return;
5530
}
5631

57-
const contextStr = typeof foundContext === 'object' ?
58-
foundContext.context ?? 'any' :
59-
foundContext;
60-
const message = foundContext?.message ??
32+
const message = /** @type {import('../iterateJsdoc.js').ContextObject} */ (
33+
foundContext
34+
)?.message ??
6135
'Syntax is restricted: {{context}}' +
6236
(comment ? ' with {{comment}}' : '');
6337

src/rules/requireReturns.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,15 @@ const canSkip = (utils) => {
3535
};
3636

3737
export default iterateJsdoc(({
38+
info: {
39+
comment,
40+
},
3841
report,
3942
utils,
4043
context,
4144
}) => {
4245
const {
46+
contexts,
4347
forceRequireReturn = false,
4448
forceReturnsWithAsync = false,
4549
} = context.options[0] || {};
@@ -50,6 +54,17 @@ export default iterateJsdoc(({
5054
return;
5155
}
5256

57+
/** @type {boolean|undefined} */
58+
let forceRequireReturnContext;
59+
if (contexts) {
60+
const {
61+
foundContext,
62+
} = utils.findContext(contexts, comment);
63+
if (typeof foundContext === 'object') {
64+
forceRequireReturnContext = foundContext.forceRequireReturn;
65+
}
66+
}
67+
5368
const tagName = /** @type {string} */ (utils.getPreferredTagName({
5469
tagName: 'returns',
5570
}));
@@ -76,7 +91,7 @@ export default iterateJsdoc(({
7691
return false;
7792
}
7893

79-
if (forceRequireReturn && (
94+
if ((forceRequireReturn || forceRequireReturnContext) && (
8095
iteratingFunction || utils.isVirtualFunction()
8196
)) {
8297
return true;
@@ -131,6 +146,9 @@ export default iterateJsdoc(({
131146
context: {
132147
type: 'string',
133148
},
149+
forceRequireReturn: {
150+
type: 'boolean',
151+
},
134152
},
135153
type: 'object',
136154
},

0 commit comments

Comments
 (0)