Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .README/rules/check-indentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ the following description is not reported:
|Context|everywhere|
|Tags|N/A|
|Recommended|false|
|Options|`excludeTags`|
|Options|`allowIndentedSections`, `excludeTags`|

## Failing examples

Expand Down
87 changes: 86 additions & 1 deletion docs/rules/check-indentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# <code>check-indentation</code>

* [Options](#user-content-check-indentation-options)
* [`allowIndentedSections`](#user-content-check-indentation-options-allowindentedsections)
* [`excludeTags`](#user-content-check-indentation-options-excludetags)
* [Context and settings](#user-content-check-indentation-context-and-settings)
* [Failing examples](#user-content-check-indentation-failing-examples)
Expand Down Expand Up @@ -31,6 +32,12 @@ the following description is not reported:

A single options object has the following properties.

<a name="user-content-check-indentation-options-allowindentedsections"></a>
<a name="check-indentation-options-allowindentedsections"></a>
### <code>allowIndentedSections</code>

Allows indentation of nested sections on subsequent lines (like bullet lists)

<a name="user-content-check-indentation-options-excludetags"></a>
<a name="check-indentation-options-excludetags"></a>
### <code>excludeTags</code>
Expand Down Expand Up @@ -65,7 +72,7 @@ report a padding issue:
|Context|everywhere|
|Tags|N/A|
|Recommended|false|
|Options|`excludeTags`|
|Options|`allowIndentedSections`, `excludeTags`|

<a name="user-content-check-indentation-failing-examples"></a>
<a name="check-indentation-failing-examples"></a>
Expand Down Expand Up @@ -179,6 +186,32 @@ function quux () {
*/
// "jsdoc/check-indentation": ["error"|"warn", {"excludeTags":[]}]
// Message: There must be no indentation.

/**
* @param {number} val Still disallowed
*/
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
// Message: There must be no indentation.

/**
* Disallowed
* Indentation
*/
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
// Message: There must be no indentation.

/**
* Some text
* that is indented
* but is inconsistent
*/
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
// Message: There must be no indentation.

/** Indented on first line
*/
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
// Message: There must be no indentation.
````


Expand Down Expand Up @@ -293,5 +326,57 @@ function MyDecorator(options: { myOptions: number }) {
function MyDecorator(options: { myOptions: number }) {
return (Base: Function) => {};
}

/**
* Foobar
*
* This method does the following things:
* - foo...
* this is the first step
* - bar
* this is the second step
*/
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]

/**
* Allowed
* Indentation
*/
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]

/**
* @param {number} val Multi-
* line
*/
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]

/**
* - foo:
* - bar
*/
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]

/**
* Some text
* that is indented
* and continues at same level
* and increases further
*/
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]

/**
* Description
* @param {string} foo Param
* with continuation
* at same indentation
*/
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]

/**
* Description
*
* More content
*/
// "jsdoc/check-indentation": ["error"|"warn", {"allowIndentedSections":true}]
````

4 changes: 4 additions & 0 deletions src/rules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export interface Rules {
| []
| [
{
/**
* Allows indentation of nested sections on subsequent lines (like bullet lists)
*/
allowIndentedSections?: boolean;
/**
* Array of tags (e.g., `['example', 'description']`) whose content will be
* "hidden" from the `check-indentation` rule. Defaults to `['example']`.
Expand Down
96 changes: 89 additions & 7 deletions src/rules/checkIndentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,106 @@ const maskCodeBlocks = (str) => {
});
};

/**
* @param {string[]} lines
* @param {number} lineIndex
* @returns {number}
*/
const getLineNumber = (lines, lineIndex) => {
const precedingText = lines.slice(0, lineIndex).join('\n');
const lineBreaks = precedingText.match(/\n/gv) || [];
return lineBreaks.length + 1;
};

export default iterateJsdoc(({
context,
jsdocNode,
report,
sourceCode,
}) => {
const options = context.options[0] || {};
const /** @type {{excludeTags: string[]}} */ {
const /** @type {{excludeTags: string[], allowIndentedSections: boolean}} */ {
allowIndentedSections = false,
excludeTags = [
'example',
],
} = options;

const reg = /^(?:\/?\**|[ \t]*)\*[ \t]{2}/gmv;
const textWithoutCodeBlocks = maskCodeBlocks(sourceCode.getText(jsdocNode));
const text = excludeTags.length ? maskExcludedContent(textWithoutCodeBlocks, excludeTags) : textWithoutCodeBlocks;

if (reg.test(text)) {
const lineBreaks = text.slice(0, reg.lastIndex).match(/\n/gv) || [];
report('There must be no indentation.', null, {
line: lineBreaks.length,
});
if (allowIndentedSections) {
// When allowIndentedSections is enabled, only check for indentation on tag lines
// and the very first line of the main description
const lines = text.split('\n');
let hasSeenContent = false;
let currentSectionIndent = null;

for (const [
lineIndex,
line,
] of lines.entries()) {
// Check for indentation (two or more spaces after *)
const indentMatch = line.match(/^(?:\/?\**|[\t ]*)\*([\t ]{2,})/v);

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

// If this is a tag line with indentation, always report
if (/^@\w+/v.test(afterIndent)) {
report('There must be no indentation.', null, {
line: getLineNumber(lines, lineIndex),
});
return;
}

// If we haven't seen any content yet (main description first line) and there's content, report
if (!hasSeenContent && afterIndent.trim().length > 0) {
report('There must be no indentation.', null, {
line: getLineNumber(lines, lineIndex),
});
return;
}

// For continuation lines, check consistency
if (hasSeenContent && afterIndent.trim().length > 0) {
if (currentSectionIndent === null) {
// First indented line in this section, set the indent level
currentSectionIndent = indentAmount;
} else if (indentAmount < currentSectionIndent) {
// Indentation is less than the established level (inconsistent)
report('There must be no indentation.', null, {
line: getLineNumber(lines, lineIndex),
});
return;
}
}
} else if (/^\s*\*\s+\S/v.test(line)) {
// No indentation on this line, reset section indent tracking
// (unless it's just whitespace or a closing comment)
currentSectionIndent = null;
}

// Track if we've seen any content (non-whitespace after the *)
if (/^\s*\*\s+\S/v.test(line)) {
hasSeenContent = true;
}

// Reset section indent when we encounter a tag
if (/@\w+/v.test(line)) {
currentSectionIndent = null;
}
}
} else {
const reg = /^(?:\/?\**|[ \t]*)\*[ \t]{2}/gmv;
if (reg.test(text)) {
const lineBreaks = text.slice(0, reg.lastIndex).match(/\n/gv) || [];
report('There must be no indentation.', null, {
line: lineBreaks.length,
});
}
}
}, {
iterateAllJsdocs: true,
Expand All @@ -59,6 +137,10 @@ export default iterateJsdoc(({
{
additionalProperties: false,
properties: {
allowIndentedSections: {
description: 'Allows indentation of nested sections on subsequent lines (like bullet lists)',
type: 'boolean',
},
excludeTags: {
description: `Array of tags (e.g., \`['example', 'description']\`) whose content will be
"hidden" from the \`check-indentation\` rule. Defaults to \`['example']\`.
Expand Down
Loading
Loading