eslint-plugin-jsdoc 61.3.0 → 61.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/alignTransform.cjs +80 -1
- package/dist/alignTransform.cjs.map +1 -1
- package/dist/cjs/rules/requireRejects.d.ts +2 -0
- package/dist/index-cjs.cjs +3 -0
- package/dist/index-cjs.cjs.map +1 -1
- package/dist/index-esm.cjs +3 -0
- package/dist/index-esm.cjs.map +1 -1
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -1
- package/dist/rules/checkLineAlignment.cjs +55 -1
- package/dist/rules/checkLineAlignment.cjs.map +1 -1
- package/dist/rules/checkTagNames.cjs +5 -2
- package/dist/rules/checkTagNames.cjs.map +1 -1
- package/dist/rules/requireRejects.cjs +226 -0
- package/dist/rules/requireRejects.cjs.map +1 -0
- package/dist/rules/requireRejects.d.ts +3 -0
- package/dist/rules.d.ts +33 -0
- package/package.json +1 -1
- package/src/alignTransform.js +87 -1
- package/src/index-cjs.js +3 -0
- package/src/index-esm.js +5 -0
- package/src/index.js +8 -0
- package/src/rules/checkLineAlignment.js +59 -1
- package/src/rules/checkTagNames.js +4 -2
- package/src/rules/requireRejects.js +246 -0
- package/src/rules.d.ts +33 -0
|
@@ -8,6 +8,54 @@ const {
|
|
|
8
8
|
flow: commentFlow,
|
|
9
9
|
} = transforms;
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Detects if a line starts with a markdown list marker
|
|
13
|
+
* Supports: -, *, numbered lists (1., 2., etc.)
|
|
14
|
+
* This explicitly excludes hyphens that are part of JSDoc tag syntax
|
|
15
|
+
* @param {string} text - The text to check
|
|
16
|
+
* @param {boolean} isFirstLineOfTag - True if this is the first line (tag line)
|
|
17
|
+
* @returns {boolean} - True if the text starts with a list marker
|
|
18
|
+
*/
|
|
19
|
+
const startsWithListMarker = (text, isFirstLineOfTag = false) => {
|
|
20
|
+
// On the first line of a tag, the hyphen is typically the JSDoc separator,
|
|
21
|
+
// not a list marker
|
|
22
|
+
if (isFirstLineOfTag) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Match lines that start with optional whitespace, then a list marker
|
|
27
|
+
// - or * followed by a space
|
|
28
|
+
// or a number followed by . or ) and a space
|
|
29
|
+
return /^\s*(?:[\-*]|\d+(?:\.|\)))\s+/v.test(text);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Checks if we should allow extra indentation beyond wrapIndent.
|
|
34
|
+
* This is true for list continuation lines (lines with more indent than wrapIndent
|
|
35
|
+
* that follow a list item).
|
|
36
|
+
* @param {import('comment-parser').Spec} tag - The tag being checked
|
|
37
|
+
* @param {import('../iterateJsdoc.js').Integer} idx - Current line index (0-based in tag.source.slice(1))
|
|
38
|
+
* @returns {boolean} - True if extra indentation should be allowed
|
|
39
|
+
*/
|
|
40
|
+
const shouldAllowExtraIndent = (tag, idx) => {
|
|
41
|
+
// Check if any previous line in this tag had a list marker
|
|
42
|
+
// idx is 0-based in the continuation lines (tag.source.slice(1))
|
|
43
|
+
// So tag.source[0] is the tag line, tag.source[idx+1] is current line
|
|
44
|
+
let hasSeenListMarker = false;
|
|
45
|
+
|
|
46
|
+
// Check all lines from the tag line onwards
|
|
47
|
+
for (let lineIdx = 0; lineIdx <= idx + 1; lineIdx++) {
|
|
48
|
+
const line = tag.source[lineIdx];
|
|
49
|
+
const isFirstLine = lineIdx === 0;
|
|
50
|
+
if (line?.tokens?.description && startsWithListMarker(line.tokens.description, isFirstLine)) {
|
|
51
|
+
hasSeenListMarker = true;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return hasSeenListMarker;
|
|
57
|
+
};
|
|
58
|
+
|
|
11
59
|
/**
|
|
12
60
|
* @typedef {{
|
|
13
61
|
* postDelimiter: import('../iterateJsdoc.js').Integer,
|
|
@@ -298,7 +346,17 @@ export default iterateJsdoc(({
|
|
|
298
346
|
}
|
|
299
347
|
|
|
300
348
|
// Don't include a single separating space/tab
|
|
301
|
-
|
|
349
|
+
const actualIndent = tokens.postDelimiter.slice(1);
|
|
350
|
+
const hasCorrectWrapIndent = actualIndent === wrapIndent;
|
|
351
|
+
|
|
352
|
+
// Allow extra indentation if this line or previous lines contain list markers
|
|
353
|
+
// This preserves nested list structure
|
|
354
|
+
const hasExtraIndent = actualIndent.length > wrapIndent.length &&
|
|
355
|
+
actualIndent.startsWith(wrapIndent);
|
|
356
|
+
const isInListContext = shouldAllowExtraIndent(tag, idx - 1);
|
|
357
|
+
|
|
358
|
+
if (!disableWrapIndent && !hasCorrectWrapIndent &&
|
|
359
|
+
!(hasExtraIndent && isInListContext)) {
|
|
302
360
|
utils.reportJSDoc('Expected wrap indent', {
|
|
303
361
|
line: tag.source[0].number + idx,
|
|
304
362
|
}, () => {
|
|
@@ -124,7 +124,8 @@ export default iterateJsdoc(({
|
|
|
124
124
|
*/
|
|
125
125
|
const isInAmbientContext = (subNode) => {
|
|
126
126
|
return subNode.type === 'Program' ?
|
|
127
|
-
|
|
127
|
+
/* c8 ignore next -- Support old ESLint */
|
|
128
|
+
(context.filename ?? context.getFilename()).endsWith('.d.ts') :
|
|
128
129
|
Boolean(
|
|
129
130
|
/** @type {import('@typescript-eslint/types').TSESTree.VariableDeclaration} */ (
|
|
130
131
|
subNode
|
|
@@ -149,7 +150,8 @@ export default iterateJsdoc(({
|
|
|
149
150
|
return false;
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
|
|
153
|
+
/* c8 ignore next -- Support old ESLint */
|
|
154
|
+
if ((context.filename ?? context.getFilename()).endsWith('.d.ts') && [
|
|
153
155
|
null, 'Program', undefined,
|
|
154
156
|
].includes(node?.parent?.type)) {
|
|
155
157
|
return false;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import iterateJsdoc from '../iterateJsdoc.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a node or its children contain Promise rejection patterns
|
|
5
|
+
* @param {import('eslint').Rule.Node} node
|
|
6
|
+
* @param {boolean} [innerFunction]
|
|
7
|
+
* @param {boolean} [isAsync]
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
// eslint-disable-next-line complexity -- Temporary
|
|
11
|
+
const hasRejectValue = (node, innerFunction, isAsync) => {
|
|
12
|
+
if (!node) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
switch (node.type) {
|
|
17
|
+
case 'ArrowFunctionExpression':
|
|
18
|
+
case 'FunctionDeclaration':
|
|
19
|
+
case 'FunctionExpression': {
|
|
20
|
+
// For inner functions in async contexts, check if they throw
|
|
21
|
+
// (they could be called and cause rejection)
|
|
22
|
+
if (innerFunction) {
|
|
23
|
+
// Check inner functions for throws - if called from async context, throws become rejections
|
|
24
|
+
const innerIsAsync = node.async;
|
|
25
|
+
// Pass isAsync=true if the inner function is async OR if we're already in an async context
|
|
26
|
+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), false, innerIsAsync || isAsync);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// This is the top-level function we're checking
|
|
30
|
+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), true, node.async);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
case 'BlockStatement': {
|
|
34
|
+
return node.body.some((bodyNode) => {
|
|
35
|
+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (bodyNode), innerFunction, isAsync);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case 'CallExpression': {
|
|
40
|
+
// Check for Promise.reject()
|
|
41
|
+
if (node.callee.type === 'MemberExpression' &&
|
|
42
|
+
node.callee.object.type === 'Identifier' &&
|
|
43
|
+
node.callee.object.name === 'Promise' &&
|
|
44
|
+
node.callee.property.type === 'Identifier' &&
|
|
45
|
+
node.callee.property.name === 'reject') {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check for reject() call (in Promise executor context)
|
|
50
|
+
if (node.callee.type === 'Identifier' && node.callee.name === 'reject') {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if this is calling an inner function that might reject
|
|
55
|
+
if (innerFunction && node.callee.type === 'Identifier') {
|
|
56
|
+
// We found a function call inside - check if it could be calling a function that rejects
|
|
57
|
+
// We'll handle this in function body traversal
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
case 'DoWhileStatement':
|
|
65
|
+
case 'ForInStatement':
|
|
66
|
+
case 'ForOfStatement':
|
|
67
|
+
case 'ForStatement':
|
|
68
|
+
case 'LabeledStatement':
|
|
69
|
+
case 'WhileStatement':
|
|
70
|
+
|
|
71
|
+
case 'WithStatement': {
|
|
72
|
+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), innerFunction, isAsync);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
case 'ExpressionStatement': {
|
|
76
|
+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.expression), innerFunction, isAsync);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case 'IfStatement': {
|
|
80
|
+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.consequent), innerFunction, isAsync) || hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.alternate), innerFunction, isAsync);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
case 'NewExpression': {
|
|
84
|
+
// Check for new Promise((resolve, reject) => { reject(...) })
|
|
85
|
+
if (node.callee.type === 'Identifier' && node.callee.name === 'Promise' && node.arguments.length > 0) {
|
|
86
|
+
const executor = node.arguments[0];
|
|
87
|
+
if (executor.type === 'ArrowFunctionExpression' || executor.type === 'FunctionExpression') {
|
|
88
|
+
// Check if the executor has reject() calls
|
|
89
|
+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (executor.body), false, false);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
case 'ReturnStatement': {
|
|
97
|
+
if (node.argument) {
|
|
98
|
+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.argument), innerFunction, isAsync);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
case 'SwitchStatement': {
|
|
105
|
+
return node.cases.some(
|
|
106
|
+
(someCase) => {
|
|
107
|
+
return someCase.consequent.some((nde) => {
|
|
108
|
+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (nde), innerFunction, isAsync);
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Throw statements in async functions become rejections
|
|
115
|
+
case 'ThrowStatement': {
|
|
116
|
+
return isAsync === true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case 'TryStatement': {
|
|
120
|
+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.handler && node.handler.body), innerFunction, isAsync) ||
|
|
121
|
+
hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.finalizer), innerFunction, isAsync);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
default: {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* We can skip checking for a rejects value, in case the documentation is inherited
|
|
132
|
+
* or the method is abstract.
|
|
133
|
+
* @param {import('../iterateJsdoc.js').Utils} utils
|
|
134
|
+
* @returns {boolean}
|
|
135
|
+
*/
|
|
136
|
+
const canSkip = (utils) => {
|
|
137
|
+
return utils.hasATag([
|
|
138
|
+
'abstract',
|
|
139
|
+
'virtual',
|
|
140
|
+
'type',
|
|
141
|
+
]) ||
|
|
142
|
+
utils.avoidDocs();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default iterateJsdoc(({
|
|
146
|
+
node,
|
|
147
|
+
report,
|
|
148
|
+
utils,
|
|
149
|
+
}) => {
|
|
150
|
+
if (canSkip(utils)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const tagName = /** @type {string} */ (utils.getPreferredTagName({
|
|
155
|
+
tagName: 'rejects',
|
|
156
|
+
}));
|
|
157
|
+
if (!tagName) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const tags = utils.getTags(tagName);
|
|
162
|
+
const iteratingFunction = utils.isIteratingFunction();
|
|
163
|
+
|
|
164
|
+
const [
|
|
165
|
+
tag,
|
|
166
|
+
] = tags;
|
|
167
|
+
const missingRejectsTag = typeof tag === 'undefined' || tag === null;
|
|
168
|
+
|
|
169
|
+
const shouldReport = () => {
|
|
170
|
+
if (!missingRejectsTag) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check if this is an async function or returns a Promise
|
|
175
|
+
const isAsync = utils.isAsync();
|
|
176
|
+
if (!isAsync && !iteratingFunction) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// For async functions, check for throw statements
|
|
181
|
+
// For regular functions, check for Promise.reject or reject calls
|
|
182
|
+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node));
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
if (shouldReport()) {
|
|
186
|
+
report('Promise-rejecting function requires `@reject` tag');
|
|
187
|
+
}
|
|
188
|
+
}, {
|
|
189
|
+
contextDefaults: true,
|
|
190
|
+
meta: {
|
|
191
|
+
docs: {
|
|
192
|
+
description: 'Requires that Promise rejections are documented with `@rejects` tags.',
|
|
193
|
+
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-rejects.md#repos-sticky-header',
|
|
194
|
+
},
|
|
195
|
+
schema: [
|
|
196
|
+
{
|
|
197
|
+
additionalProperties: false,
|
|
198
|
+
properties: {
|
|
199
|
+
contexts: {
|
|
200
|
+
description: `Set this to an array of strings representing the AST context
|
|
201
|
+
(or objects with optional \`context\` and \`comment\` properties) where you wish
|
|
202
|
+
the rule to be applied.
|
|
203
|
+
|
|
204
|
+
\`context\` defaults to \`any\` and \`comment\` defaults to no specific comment context.
|
|
205
|
+
|
|
206
|
+
Overrides the default contexts (\`ArrowFunctionExpression\`, \`FunctionDeclaration\`,
|
|
207
|
+
\`FunctionExpression\`).`,
|
|
208
|
+
items: {
|
|
209
|
+
anyOf: [
|
|
210
|
+
{
|
|
211
|
+
type: 'string',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
additionalProperties: false,
|
|
215
|
+
properties: {
|
|
216
|
+
comment: {
|
|
217
|
+
type: 'string',
|
|
218
|
+
},
|
|
219
|
+
context: {
|
|
220
|
+
type: 'string',
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
type: 'object',
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
type: 'array',
|
|
228
|
+
},
|
|
229
|
+
exemptedBy: {
|
|
230
|
+
description: `Array of tags (e.g., \`['type']\`) whose presence on the
|
|
231
|
+
document block avoids the need for a \`@rejects\`. Defaults to an array
|
|
232
|
+
with \`abstract\`, \`virtual\`, and \`type\`. If you set this array, it will overwrite the default,
|
|
233
|
+
so be sure to add back those tags if you wish their presence to cause
|
|
234
|
+
exemption of the rule.`,
|
|
235
|
+
items: {
|
|
236
|
+
type: 'string',
|
|
237
|
+
},
|
|
238
|
+
type: 'array',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
type: 'object',
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
type: 'suggestion',
|
|
245
|
+
},
|
|
246
|
+
});
|
package/src/rules.d.ts
CHANGED
|
@@ -2196,6 +2196,39 @@ export interface Rules {
|
|
|
2196
2196
|
/** Requires that each `@property` tag has a type value (in curly brackets). */
|
|
2197
2197
|
"jsdoc/require-property-type": [];
|
|
2198
2198
|
|
|
2199
|
+
/** Requires that Promise rejections are documented with `@rejects` tags. */
|
|
2200
|
+
"jsdoc/require-rejects":
|
|
2201
|
+
| []
|
|
2202
|
+
| [
|
|
2203
|
+
{
|
|
2204
|
+
/**
|
|
2205
|
+
* Set this to an array of strings representing the AST context
|
|
2206
|
+
* (or objects with optional `context` and `comment` properties) where you wish
|
|
2207
|
+
* the rule to be applied.
|
|
2208
|
+
*
|
|
2209
|
+
* `context` defaults to `any` and `comment` defaults to no specific comment context.
|
|
2210
|
+
*
|
|
2211
|
+
* Overrides the default contexts (`ArrowFunctionExpression`, `FunctionDeclaration`,
|
|
2212
|
+
* `FunctionExpression`).
|
|
2213
|
+
*/
|
|
2214
|
+
contexts?: (
|
|
2215
|
+
| string
|
|
2216
|
+
| {
|
|
2217
|
+
comment?: string;
|
|
2218
|
+
context?: string;
|
|
2219
|
+
}
|
|
2220
|
+
)[];
|
|
2221
|
+
/**
|
|
2222
|
+
* Array of tags (e.g., `['type']`) whose presence on the
|
|
2223
|
+
* document block avoids the need for a `@rejects`. Defaults to an array
|
|
2224
|
+
* with `abstract`, `virtual`, and `type`. If you set this array, it will overwrite the default,
|
|
2225
|
+
* so be sure to add back those tags if you wish their presence to cause
|
|
2226
|
+
* exemption of the rule.
|
|
2227
|
+
*/
|
|
2228
|
+
exemptedBy?: string[];
|
|
2229
|
+
}
|
|
2230
|
+
];
|
|
2231
|
+
|
|
2199
2232
|
/** Requires that returns are documented with `@returns`. */
|
|
2200
2233
|
"jsdoc/require-returns":
|
|
2201
2234
|
| []
|