Skip to content

Commit 03adf63

Browse files
committed
refactor: forbid inconsistent indentation with new option
1 parent edaf615 commit 03adf63

File tree

3 files changed

+135
-10
lines changed

3 files changed

+135
-10
lines changed

docs/rules/check-indentation.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,19 @@ function quux () {
199199
*/
200200
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
201201
// Message: There must be no indentation.
202+
203+
/**
204+
* Some text
205+
* that is indented
206+
* but is inconsistent
207+
*/
208+
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
209+
// Message: There must be no indentation.
210+
211+
/** Indented on first line
212+
*/
213+
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
214+
// Message: There must be no indentation.
202215
````
203216

204217

@@ -342,5 +355,28 @@ function MyDecorator(options: { myOptions: number }) {
342355
* - bar
343356
*/
344357
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
358+
359+
/**
360+
* Some text
361+
* that is indented
362+
* and continues at same level
363+
* and increases further
364+
*/
365+
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
366+
367+
/**
368+
* Description
369+
* @param {string} foo Param
370+
* with continuation
371+
* at same indentation
372+
*/
373+
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
374+
375+
/**
376+
* Description
377+
*
378+
* More content
379+
*/
380+
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
345381
````
346382

src/rules/checkIndentation.js

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ const maskCodeBlocks = (str) => {
2525
});
2626
};
2727

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+
2839
export default iterateJsdoc(({
2940
context,
3041
jsdocNode,
@@ -47,47 +58,64 @@ export default iterateJsdoc(({
4758
// and the very first line of the main description
4859
const lines = text.split('\n');
4960
let hasSeenContent = false;
61+
let currentSectionIndent = null;
5062

5163
for (const [
5264
lineIndex,
5365
line,
5466
] of lines.entries()) {
5567
// Check for indentation (two or more spaces after *)
56-
const indentMatch = line.match(/^(?:\/?\**|[\t ]*)\*([\t ]{2,})/gv);
68+
const indentMatch = line.match(/^(?:\/?\**|[\t ]*)\*([\t ]{2,})/v);
5769

5870
if (indentMatch) {
5971
// Check what comes after the indentation
6072
const afterIndent = line.slice(indentMatch[0].length);
73+
const indentAmount = indentMatch[1].length;
6174

6275
// If this is a tag line with indentation, always report
6376
if (/^@\w+/v.test(afterIndent)) {
64-
// Count newlines before this line
65-
const precedingText = lines.slice(0, lineIndex).join('\n');
66-
const lineBreaks = precedingText.match(/\n/gv) || [];
6777
report('There must be no indentation.', null, {
68-
line: lineBreaks.length + 1,
78+
line: getLineNumber(lines, lineIndex),
6979
});
7080
return;
7181
}
7282

7383
// If we haven't seen any content yet (main description first line) and there's content, report
7484
if (!hasSeenContent && afterIndent.trim().length > 0) {
75-
// Count newlines before this line
76-
const precedingText = lines.slice(0, lineIndex).join('\n');
77-
const lineBreaks = precedingText.match(/\n/gv) || [];
7885
report('There must be no indentation.', null, {
79-
line: lineBreaks.length + 1,
86+
line: getLineNumber(lines, lineIndex),
8087
});
8188
return;
8289
}
8390

84-
// Otherwise, allow it (continuation lines)
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;
85108
}
86109

87110
// Track if we've seen any content (non-whitespace after the *)
88111
if (/^\s*\*\s+\S/v.test(line)) {
89112
hasSeenContent = true;
90113
}
114+
115+
// Reset section indent when we encounter a tag
116+
if (/@\w+/v.test(line)) {
117+
currentSectionIndent = null;
118+
}
91119
}
92120
} else {
93121
const reg = /^(?:\/?\**|[ \t]*)\*[ \t]{2}/gmv;

test/rules/assertions/checkIndentation.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,23 @@ export default /** @type {import('../index.js').TestCases} */ ({
251251
},
252252
],
253253
},
254+
{
255+
code: `
256+
/** Indented on first line
257+
*/
258+
`,
259+
errors: [
260+
{
261+
line: 3,
262+
message: 'There must be no indentation.',
263+
},
264+
],
265+
options: [
266+
{
267+
allowIndentedSections: true,
268+
},
269+
],
270+
},
254271
],
255272
valid: [
256273
{
@@ -463,5 +480,49 @@ export default /** @type {import('../index.js').TestCases} */ ({
463480
},
464481
],
465482
},
483+
{
484+
code: `
485+
/**
486+
* Some text
487+
* that is indented
488+
* and continues at same level
489+
* and increases further
490+
*/
491+
`,
492+
options: [
493+
{
494+
allowIndentedSections: true,
495+
},
496+
],
497+
},
498+
{
499+
code: `
500+
/**
501+
* Description
502+
* @param {string} foo Param
503+
* with continuation
504+
* at same indentation
505+
*/
506+
`,
507+
options: [
508+
{
509+
allowIndentedSections: true,
510+
},
511+
],
512+
},
513+
{
514+
code: `
515+
/**
516+
* Description
517+
*
518+
* More content
519+
*/
520+
`,
521+
options: [
522+
{
523+
allowIndentedSections: true,
524+
},
525+
],
526+
},
466527
],
467528
});

0 commit comments

Comments
 (0)