eslint-plugin-jsdoc 61.2.1 → 61.4.0

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.
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _iterateJsdoc = _interopRequireDefault(require("../iterateJsdoc.cjs"));
8
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
+ /**
10
+ * Checks if a node or its children contain Promise rejection patterns
11
+ * @param {import('eslint').Rule.Node} node
12
+ * @param {boolean} [innerFunction]
13
+ * @returns {boolean}
14
+ */
15
+ // eslint-disable-next-line complexity -- Temporary
16
+ const hasRejectValue = (node, innerFunction) => {
17
+ if (!node) {
18
+ return false;
19
+ }
20
+ switch (node.type) {
21
+ case 'ArrowFunctionExpression':
22
+ case 'FunctionDeclaration':
23
+ case 'FunctionExpression':
24
+ {
25
+ // For inner functions in async contexts, check if they throw
26
+ // (they could be called and cause rejection)
27
+ if (innerFunction) {
28
+ // Check the inner function's body for throw statements
29
+ return hasRejectValue(/** @type {import('eslint').Rule.Node} */node.body, false);
30
+ }
31
+ return hasRejectValue(/** @type {import('eslint').Rule.Node} */node.body, true);
32
+ }
33
+ case 'BlockStatement':
34
+ {
35
+ return node.body.some(bodyNode => {
36
+ return hasRejectValue(/** @type {import('eslint').Rule.Node} */bodyNode, innerFunction);
37
+ });
38
+ }
39
+ case 'CallExpression':
40
+ {
41
+ // Check for Promise.reject()
42
+ if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name === 'Promise' && node.callee.property.type === 'Identifier' && node.callee.property.name === 'reject') {
43
+ return true;
44
+ }
45
+
46
+ // Check for reject() call (in Promise executor context)
47
+ if (node.callee.type === 'Identifier' && node.callee.name === 'reject') {
48
+ return true;
49
+ }
50
+
51
+ // Check if this is calling an inner function that might reject
52
+ if (innerFunction && node.callee.type === 'Identifier') {
53
+ // We found a function call inside - check if it could be calling a function that rejects
54
+ // We'll handle this in function body traversal
55
+ return false;
56
+ }
57
+ return false;
58
+ }
59
+ case 'DoWhileStatement':
60
+ case 'ForInStatement':
61
+ case 'ForOfStatement':
62
+ case 'ForStatement':
63
+ case 'LabeledStatement':
64
+ case 'WhileStatement':
65
+ case 'WithStatement':
66
+ {
67
+ return hasRejectValue(/** @type {import('eslint').Rule.Node} */node.body, innerFunction);
68
+ }
69
+ case 'ExpressionStatement':
70
+ {
71
+ return hasRejectValue(/** @type {import('eslint').Rule.Node} */node.expression, innerFunction);
72
+ }
73
+ case 'IfStatement':
74
+ {
75
+ return hasRejectValue(/** @type {import('eslint').Rule.Node} */node.consequent, innerFunction) || hasRejectValue(/** @type {import('eslint').Rule.Node} */node.alternate, innerFunction);
76
+ }
77
+ case 'NewExpression':
78
+ {
79
+ // Check for new Promise((resolve, reject) => { reject(...) })
80
+ if (node.callee.type === 'Identifier' && node.callee.name === 'Promise' && node.arguments.length > 0) {
81
+ const executor = node.arguments[0];
82
+ if (executor.type === 'ArrowFunctionExpression' || executor.type === 'FunctionExpression') {
83
+ // Check if the executor has reject() calls
84
+ return hasRejectValue(/** @type {import('eslint').Rule.Node} */executor.body, false);
85
+ }
86
+ }
87
+ return false;
88
+ }
89
+ case 'ReturnStatement':
90
+ {
91
+ if (node.argument) {
92
+ return hasRejectValue(/** @type {import('eslint').Rule.Node} */node.argument, innerFunction);
93
+ }
94
+ return false;
95
+ }
96
+ case 'SwitchStatement':
97
+ {
98
+ return node.cases.some(someCase => {
99
+ return someCase.consequent.some(nde => {
100
+ return hasRejectValue(/** @type {import('eslint').Rule.Node} */nde, innerFunction);
101
+ });
102
+ });
103
+ }
104
+
105
+ // Throw statements in async functions become rejections
106
+ case 'ThrowStatement':
107
+ {
108
+ return true;
109
+ }
110
+ case 'TryStatement':
111
+ {
112
+ return hasRejectValue(/** @type {import('eslint').Rule.Node} */node.handler && node.handler.body, innerFunction) || hasRejectValue(/** @type {import('eslint').Rule.Node} */node.finalizer, innerFunction);
113
+ }
114
+ default:
115
+ {
116
+ return false;
117
+ }
118
+ }
119
+ };
120
+
121
+ /**
122
+ * We can skip checking for a rejects value, in case the documentation is inherited
123
+ * or the method is abstract.
124
+ * @param {import('../iterateJsdoc.js').Utils} utils
125
+ * @returns {boolean}
126
+ */
127
+ const canSkip = utils => {
128
+ return utils.hasATag(['abstract', 'virtual', 'type']) || utils.avoidDocs();
129
+ };
130
+ var _default = exports.default = (0, _iterateJsdoc.default)(({
131
+ node,
132
+ report,
133
+ utils
134
+ }) => {
135
+ if (canSkip(utils)) {
136
+ return;
137
+ }
138
+ const tagName = /** @type {string} */utils.getPreferredTagName({
139
+ tagName: 'rejects'
140
+ });
141
+ if (!tagName) {
142
+ return;
143
+ }
144
+ const tags = utils.getTags(tagName);
145
+ const iteratingFunction = utils.isIteratingFunction();
146
+ const [tag] = tags;
147
+ const missingRejectsTag = typeof tag === 'undefined' || tag === null;
148
+ const shouldReport = () => {
149
+ if (!missingRejectsTag) {
150
+ return false;
151
+ }
152
+
153
+ // Check if this is an async function or returns a Promise
154
+ const isAsync = utils.isAsync();
155
+ if (!isAsync && !iteratingFunction) {
156
+ return false;
157
+ }
158
+
159
+ // For async functions, check for throw statements
160
+ // For regular functions, check for Promise.reject or reject calls
161
+ return hasRejectValue(/** @type {import('eslint').Rule.Node} */node);
162
+ };
163
+ if (shouldReport()) {
164
+ report('Promise-rejecting function requires `@reject` tag');
165
+ }
166
+ }, {
167
+ contextDefaults: true,
168
+ meta: {
169
+ docs: {
170
+ description: 'Requires that Promise rejections are documented with `@rejects` tags.',
171
+ url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-rejects.md#repos-sticky-header'
172
+ },
173
+ schema: [{
174
+ additionalProperties: false,
175
+ properties: {
176
+ contexts: {
177
+ description: `Set this to an array of strings representing the AST context
178
+ (or objects with optional \`context\` and \`comment\` properties) where you wish
179
+ the rule to be applied.
180
+
181
+ \`context\` defaults to \`any\` and \`comment\` defaults to no specific comment context.
182
+
183
+ Overrides the default contexts (\`ArrowFunctionExpression\`, \`FunctionDeclaration\`,
184
+ \`FunctionExpression\`).`,
185
+ items: {
186
+ anyOf: [{
187
+ type: 'string'
188
+ }, {
189
+ additionalProperties: false,
190
+ properties: {
191
+ comment: {
192
+ type: 'string'
193
+ },
194
+ context: {
195
+ type: 'string'
196
+ }
197
+ },
198
+ type: 'object'
199
+ }]
200
+ },
201
+ type: 'array'
202
+ },
203
+ exemptedBy: {
204
+ description: `Array of tags (e.g., \`['type']\`) whose presence on the
205
+ document block avoids the need for a \`@rejects\`. Defaults to an array
206
+ with \`abstract\`, \`virtual\`, and \`type\`. If you set this array, it will overwrite the default,
207
+ so be sure to add back those tags if you wish their presence to cause
208
+ exemption of the rule.`,
209
+ items: {
210
+ type: 'string'
211
+ },
212
+ type: 'array'
213
+ }
214
+ },
215
+ type: 'object'
216
+ }],
217
+ type: 'suggestion'
218
+ }
219
+ });
220
+ module.exports = exports.default;
221
+ //# sourceMappingURL=requireRejects.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requireRejects.cjs","names":["_iterateJsdoc","_interopRequireDefault","require","e","__esModule","default","hasRejectValue","node","innerFunction","type","body","some","bodyNode","callee","object","name","property","expression","consequent","alternate","arguments","length","executor","argument","cases","someCase","nde","handler","finalizer","canSkip","utils","hasATag","avoidDocs","_default","exports","iterateJsdoc","report","tagName","getPreferredTagName","tags","getTags","iteratingFunction","isIteratingFunction","tag","missingRejectsTag","shouldReport","isAsync","contextDefaults","meta","docs","description","url","schema","additionalProperties","properties","contexts","items","anyOf","comment","context","exemptedBy","module"],"sources":["../../src/rules/requireRejects.js"],"sourcesContent":["import iterateJsdoc from '../iterateJsdoc.js';\n\n/**\n * Checks if a node or its children contain Promise rejection patterns\n * @param {import('eslint').Rule.Node} node\n * @param {boolean} [innerFunction]\n * @returns {boolean}\n */\n// eslint-disable-next-line complexity -- Temporary\nconst hasRejectValue = (node, innerFunction) => {\n if (!node) {\n return false;\n }\n\n switch (node.type) {\n case 'ArrowFunctionExpression':\n case 'FunctionDeclaration':\n case 'FunctionExpression': {\n // For inner functions in async contexts, check if they throw\n // (they could be called and cause rejection)\n if (innerFunction) {\n // Check the inner function's body for throw statements\n return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), false);\n }\n\n return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), true);\n }\n\n case 'BlockStatement': {\n return node.body.some((bodyNode) => {\n return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (bodyNode), innerFunction);\n });\n }\n\n case 'CallExpression': {\n // Check for Promise.reject()\n if (node.callee.type === 'MemberExpression' &&\n node.callee.object.type === 'Identifier' &&\n node.callee.object.name === 'Promise' &&\n node.callee.property.type === 'Identifier' &&\n node.callee.property.name === 'reject') {\n return true;\n }\n\n // Check for reject() call (in Promise executor context)\n if (node.callee.type === 'Identifier' && node.callee.name === 'reject') {\n return true;\n }\n\n // Check if this is calling an inner function that might reject\n if (innerFunction && node.callee.type === 'Identifier') {\n // We found a function call inside - check if it could be calling a function that rejects\n // We'll handle this in function body traversal\n return false;\n }\n\n return false;\n }\n\n case 'DoWhileStatement':\n case 'ForInStatement':\n case 'ForOfStatement':\n case 'ForStatement':\n case 'LabeledStatement':\n case 'WhileStatement':\n\n case 'WithStatement': {\n return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), innerFunction);\n }\n\n case 'ExpressionStatement': {\n return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.expression), innerFunction);\n }\n\n case 'IfStatement': {\n return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.consequent), innerFunction) || hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.alternate), innerFunction);\n }\n\n case 'NewExpression': {\n // Check for new Promise((resolve, reject) => { reject(...) })\n if (node.callee.type === 'Identifier' && node.callee.name === 'Promise' && node.arguments.length > 0) {\n const executor = node.arguments[0];\n if (executor.type === 'ArrowFunctionExpression' || executor.type === 'FunctionExpression') {\n // Check if the executor has reject() calls\n return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (executor.body), false);\n }\n }\n\n return false;\n }\n\n case 'ReturnStatement': {\n if (node.argument) {\n return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.argument), innerFunction);\n }\n\n return false;\n }\n\n case 'SwitchStatement': {\n return node.cases.some(\n (someCase) => {\n return someCase.consequent.some((nde) => {\n return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (nde), innerFunction);\n });\n },\n );\n }\n\n // Throw statements in async functions become rejections\n case 'ThrowStatement': {\n return true;\n }\n\n case 'TryStatement': {\n return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.handler && node.handler.body), innerFunction) ||\n hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.finalizer), innerFunction);\n }\n\n default: {\n return false;\n }\n }\n};\n\n/**\n * We can skip checking for a rejects value, in case the documentation is inherited\n * or the method is abstract.\n * @param {import('../iterateJsdoc.js').Utils} utils\n * @returns {boolean}\n */\nconst canSkip = (utils) => {\n return utils.hasATag([\n 'abstract',\n 'virtual',\n 'type',\n ]) ||\n utils.avoidDocs();\n};\n\nexport default iterateJsdoc(({\n node,\n report,\n utils,\n}) => {\n if (canSkip(utils)) {\n return;\n }\n\n const tagName = /** @type {string} */ (utils.getPreferredTagName({\n tagName: 'rejects',\n }));\n if (!tagName) {\n return;\n }\n\n const tags = utils.getTags(tagName);\n const iteratingFunction = utils.isIteratingFunction();\n\n const [\n tag,\n ] = tags;\n const missingRejectsTag = typeof tag === 'undefined' || tag === null;\n\n const shouldReport = () => {\n if (!missingRejectsTag) {\n return false;\n }\n\n // Check if this is an async function or returns a Promise\n const isAsync = utils.isAsync();\n if (!isAsync && !iteratingFunction) {\n return false;\n }\n\n // For async functions, check for throw statements\n // For regular functions, check for Promise.reject or reject calls\n return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node));\n };\n\n if (shouldReport()) {\n report('Promise-rejecting function requires `@reject` tag');\n }\n}, {\n contextDefaults: true,\n meta: {\n docs: {\n description: 'Requires that Promise rejections are documented with `@rejects` tags.',\n url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-rejects.md#repos-sticky-header',\n },\n schema: [\n {\n additionalProperties: false,\n properties: {\n contexts: {\n description: `Set this to an array of strings representing the AST context\n(or objects with optional \\`context\\` and \\`comment\\` properties) where you wish\nthe rule to be applied.\n\n\\`context\\` defaults to \\`any\\` and \\`comment\\` defaults to no specific comment context.\n\nOverrides the default contexts (\\`ArrowFunctionExpression\\`, \\`FunctionDeclaration\\`,\n\\`FunctionExpression\\`).`,\n items: {\n anyOf: [\n {\n type: 'string',\n },\n {\n additionalProperties: false,\n properties: {\n comment: {\n type: 'string',\n },\n context: {\n type: 'string',\n },\n },\n type: 'object',\n },\n ],\n },\n type: 'array',\n },\n exemptedBy: {\n description: `Array of tags (e.g., \\`['type']\\`) whose presence on the\ndocument block avoids the need for a \\`@rejects\\`. Defaults to an array\nwith \\`abstract\\`, \\`virtual\\`, and \\`type\\`. If you set this array, it will overwrite the default,\nso be sure to add back those tags if you wish their presence to cause\nexemption of the rule.`,\n items: {\n type: 'string',\n },\n type: 'array',\n },\n },\n type: 'object',\n },\n ],\n type: 'suggestion',\n },\n});\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,3 @@
1
+ declare const _default: import("eslint").Rule.RuleModule;
2
+ export default _default;
3
+ //# sourceMappingURL=requireRejects.d.ts.map
package/dist/rules.d.ts CHANGED
@@ -47,6 +47,10 @@ export interface Rules {
47
47
  | []
48
48
  | [
49
49
  {
50
+ /**
51
+ * Allows indentation of nested sections on subsequent lines (like bullet lists)
52
+ */
53
+ allowIndentedSections?: boolean;
50
54
  /**
51
55
  * Array of tags (e.g., `['example', 'description']`) whose content will be
52
56
  * "hidden" from the `check-indentation` rule. Defaults to `['example']`.
@@ -2192,6 +2196,39 @@ export interface Rules {
2192
2196
  /** Requires that each `@property` tag has a type value (in curly brackets). */
2193
2197
  "jsdoc/require-property-type": [];
2194
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
+
2195
2232
  /** Requires that returns are documented with `@returns`. */
2196
2233
  "jsdoc/require-returns":
2197
2234
  | []
package/package.json CHANGED
@@ -192,5 +192,5 @@
192
192
  "test-cov": "TIMING=1 c8 --reporter text pnpm run test-no-cov",
193
193
  "test-index": "pnpm run test-no-cov test/rules/index.js"
194
194
  },
195
- "version": "61.2.1"
195
+ "version": "61.4.0"
196
196
  }
package/src/index-cjs.js CHANGED
@@ -54,6 +54,7 @@ import requireProperty from './rules/requireProperty.js';
54
54
  import requirePropertyDescription from './rules/requirePropertyDescription.js';
55
55
  import requirePropertyName from './rules/requirePropertyName.js';
56
56
  import requirePropertyType from './rules/requirePropertyType.js';
57
+ import requireRejects from './rules/requireRejects.js';
57
58
  import requireReturns from './rules/requireReturns.js';
58
59
  import requireReturnsCheck from './rules/requireReturnsCheck.js';
59
60
  import requireReturnsDescription from './rules/requireReturnsDescription.js';
@@ -188,6 +189,7 @@ index.rules = {
188
189
  'require-property-description': requirePropertyDescription,
189
190
  'require-property-name': requirePropertyName,
190
191
  'require-property-type': requirePropertyType,
192
+ 'require-rejects': requireRejects,
191
193
  'require-returns': requireReturns,
192
194
  'require-returns-check': requireReturnsCheck,
193
195
  'require-returns-description': requireReturnsDescription,
@@ -332,6 +334,7 @@ const createRecommendedRuleset = (warnOrError, flatName) => {
332
334
  'jsdoc/require-property-description': warnOrError,
333
335
  'jsdoc/require-property-name': warnOrError,
334
336
  'jsdoc/require-property-type': warnOrError,
337
+ 'jsdoc/require-rejects': 'off',
335
338
  'jsdoc/require-returns': warnOrError,
336
339
  'jsdoc/require-returns-check': warnOrError,
337
340
  'jsdoc/require-returns-description': warnOrError,
package/src/index-esm.js CHANGED
@@ -177,6 +177,11 @@ export const jsdoc = function (cfg) {
177
177
  'type',
178
178
  ],
179
179
  },
180
+ rejects: {
181
+ required: [
182
+ 'type',
183
+ ],
184
+ },
180
185
  },
181
186
  } :
182
187
  {},
package/src/index.js CHANGED
@@ -60,6 +60,7 @@ import requireProperty from './rules/requireProperty.js';
60
60
  import requirePropertyDescription from './rules/requirePropertyDescription.js';
61
61
  import requirePropertyName from './rules/requirePropertyName.js';
62
62
  import requirePropertyType from './rules/requirePropertyType.js';
63
+ import requireRejects from './rules/requireRejects.js';
63
64
  import requireReturns from './rules/requireReturns.js';
64
65
  import requireReturnsCheck from './rules/requireReturnsCheck.js';
65
66
  import requireReturnsDescription from './rules/requireReturnsDescription.js';
@@ -194,6 +195,7 @@ index.rules = {
194
195
  'require-property-description': requirePropertyDescription,
195
196
  'require-property-name': requirePropertyName,
196
197
  'require-property-type': requirePropertyType,
198
+ 'require-rejects': requireRejects,
197
199
  'require-returns': requireReturns,
198
200
  'require-returns-check': requireReturnsCheck,
199
201
  'require-returns-description': requireReturnsDescription,
@@ -338,6 +340,7 @@ const createRecommendedRuleset = (warnOrError, flatName) => {
338
340
  'jsdoc/require-property-description': warnOrError,
339
341
  'jsdoc/require-property-name': warnOrError,
340
342
  'jsdoc/require-property-type': warnOrError,
343
+ 'jsdoc/require-rejects': 'off',
341
344
  'jsdoc/require-returns': warnOrError,
342
345
  'jsdoc/require-returns-check': warnOrError,
343
346
  'jsdoc/require-returns-description': warnOrError,
@@ -883,6 +886,11 @@ export const jsdoc = function (cfg) {
883
886
  'type',
884
887
  ],
885
888
  },
889
+ rejects: {
890
+ required: [
891
+ 'type',
892
+ ],
893
+ },
886
894
  },
887
895
  } :
888
896
  {},
@@ -25,6 +25,17 @@ const maskCodeBlocks = (str) => {
25
25
  });
26
26
  };
27
27
 
28
+ /**
29
+ * @param {string[]} lines
30
+ * @param {number} lineIndex
31
+ * @returns {number}
32
+ */
33
+ const getLineNumber = (lines, lineIndex) => {
34
+ const precedingText = lines.slice(0, lineIndex).join('\n');
35
+ const lineBreaks = precedingText.match(/\n/gv) || [];
36
+ return lineBreaks.length + 1;
37
+ };
38
+
28
39
  export default iterateJsdoc(({
29
40
  context,
30
41
  jsdocNode,
@@ -32,21 +43,88 @@ export default iterateJsdoc(({
32
43
  sourceCode,
33
44
  }) => {
34
45
  const options = context.options[0] || {};
35
- const /** @type {{excludeTags: string[]}} */ {
46
+ const /** @type {{excludeTags: string[], allowIndentedSections: boolean}} */ {
47
+ allowIndentedSections = false,
36
48
  excludeTags = [
37
49
  'example',
38
50
  ],
39
51
  } = options;
40
52
 
41
- const reg = /^(?:\/?\**|[ \t]*)\*[ \t]{2}/gmv;
42
53
  const textWithoutCodeBlocks = maskCodeBlocks(sourceCode.getText(jsdocNode));
43
54
  const text = excludeTags.length ? maskExcludedContent(textWithoutCodeBlocks, excludeTags) : textWithoutCodeBlocks;
44
55
 
45
- if (reg.test(text)) {
46
- const lineBreaks = text.slice(0, reg.lastIndex).match(/\n/gv) || [];
47
- report('There must be no indentation.', null, {
48
- line: lineBreaks.length,
49
- });
56
+ if (allowIndentedSections) {
57
+ // When allowIndentedSections is enabled, only check for indentation on tag lines
58
+ // and the very first line of the main description
59
+ const lines = text.split('\n');
60
+ let hasSeenContent = false;
61
+ let currentSectionIndent = null;
62
+
63
+ for (const [
64
+ lineIndex,
65
+ line,
66
+ ] of lines.entries()) {
67
+ // Check for indentation (two or more spaces after *)
68
+ const indentMatch = line.match(/^(?:\/?\**|[\t ]*)\*([\t ]{2,})/v);
69
+
70
+ if (indentMatch) {
71
+ // Check what comes after the indentation
72
+ const afterIndent = line.slice(indentMatch[0].length);
73
+ const indentAmount = indentMatch[1].length;
74
+
75
+ // If this is a tag line with indentation, always report
76
+ if (/^@\w+/v.test(afterIndent)) {
77
+ report('There must be no indentation.', null, {
78
+ line: getLineNumber(lines, lineIndex),
79
+ });
80
+ return;
81
+ }
82
+
83
+ // If we haven't seen any content yet (main description first line) and there's content, report
84
+ if (!hasSeenContent && afterIndent.trim().length > 0) {
85
+ report('There must be no indentation.', null, {
86
+ line: getLineNumber(lines, lineIndex),
87
+ });
88
+ return;
89
+ }
90
+
91
+ // For continuation lines, check consistency
92
+ if (hasSeenContent && afterIndent.trim().length > 0) {
93
+ if (currentSectionIndent === null) {
94
+ // First indented line in this section, set the indent level
95
+ currentSectionIndent = indentAmount;
96
+ } else if (indentAmount < currentSectionIndent) {
97
+ // Indentation is less than the established level (inconsistent)
98
+ report('There must be no indentation.', null, {
99
+ line: getLineNumber(lines, lineIndex),
100
+ });
101
+ return;
102
+ }
103
+ }
104
+ } else if (/^\s*\*\s+\S/v.test(line)) {
105
+ // No indentation on this line, reset section indent tracking
106
+ // (unless it's just whitespace or a closing comment)
107
+ currentSectionIndent = null;
108
+ }
109
+
110
+ // Track if we've seen any content (non-whitespace after the *)
111
+ if (/^\s*\*\s+\S/v.test(line)) {
112
+ hasSeenContent = true;
113
+ }
114
+
115
+ // Reset section indent when we encounter a tag
116
+ if (/@\w+/v.test(line)) {
117
+ currentSectionIndent = null;
118
+ }
119
+ }
120
+ } else {
121
+ const reg = /^(?:\/?\**|[ \t]*)\*[ \t]{2}/gmv;
122
+ if (reg.test(text)) {
123
+ const lineBreaks = text.slice(0, reg.lastIndex).match(/\n/gv) || [];
124
+ report('There must be no indentation.', null, {
125
+ line: lineBreaks.length,
126
+ });
127
+ }
50
128
  }
51
129
  }, {
52
130
  iterateAllJsdocs: true,
@@ -59,6 +137,10 @@ export default iterateJsdoc(({
59
137
  {
60
138
  additionalProperties: false,
61
139
  properties: {
140
+ allowIndentedSections: {
141
+ description: 'Allows indentation of nested sections on subsequent lines (like bullet lists)',
142
+ type: 'boolean',
143
+ },
62
144
  excludeTags: {
63
145
  description: `Array of tags (e.g., \`['example', 'description']\`) whose content will be
64
146
  "hidden" from the \`check-indentation\` rule. Defaults to \`['example']\`.
@@ -124,7 +124,8 @@ export default iterateJsdoc(({
124
124
  */
125
125
  const isInAmbientContext = (subNode) => {
126
126
  return subNode.type === 'Program' ?
127
- context.getFilename().endsWith('.d.ts') :
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
- if (context.getFilename().endsWith('.d.ts') && [
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;