eslint-plugin-jsdoc 61.2.1 → 61.3.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.
|
@@ -28,6 +28,17 @@ const maskCodeBlocks = str => {
|
|
|
28
28
|
return (margin + '\n').repeat(code.match(/\n/gv).length);
|
|
29
29
|
});
|
|
30
30
|
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string[]} lines
|
|
34
|
+
* @param {number} lineIndex
|
|
35
|
+
* @returns {number}
|
|
36
|
+
*/
|
|
37
|
+
const getLineNumber = (lines, lineIndex) => {
|
|
38
|
+
const precedingText = lines.slice(0, lineIndex).join('\n');
|
|
39
|
+
const lineBreaks = precedingText.match(/\n/gv) || [];
|
|
40
|
+
return lineBreaks.length + 1;
|
|
41
|
+
};
|
|
31
42
|
var _default = exports.default = (0, _iterateJsdoc.default)(({
|
|
32
43
|
context,
|
|
33
44
|
jsdocNode,
|
|
@@ -35,17 +46,79 @@ var _default = exports.default = (0, _iterateJsdoc.default)(({
|
|
|
35
46
|
sourceCode
|
|
36
47
|
}) => {
|
|
37
48
|
const options = context.options[0] || {};
|
|
38
|
-
const /** @type {{excludeTags: string[]}} */{
|
|
49
|
+
const /** @type {{excludeTags: string[], allowIndentedSections: boolean}} */{
|
|
50
|
+
allowIndentedSections = false,
|
|
39
51
|
excludeTags = ['example']
|
|
40
52
|
} = options;
|
|
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
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
if (allowIndentedSections) {
|
|
56
|
+
// When allowIndentedSections is enabled, only check for indentation on tag lines
|
|
57
|
+
// and the very first line of the main description
|
|
58
|
+
const lines = text.split('\n');
|
|
59
|
+
let hasSeenContent = false;
|
|
60
|
+
let currentSectionIndent = null;
|
|
61
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
62
|
+
// Check for indentation (two or more spaces after *)
|
|
63
|
+
const indentMatch = line.match(/^(?:\/?\**|[\t ]*)\*([\t ]{2,})/v);
|
|
64
|
+
if (indentMatch) {
|
|
65
|
+
// Check what comes after the indentation
|
|
66
|
+
const afterIndent = line.slice(indentMatch[0].length);
|
|
67
|
+
const indentAmount = indentMatch[1].length;
|
|
68
|
+
|
|
69
|
+
// If this is a tag line with indentation, always report
|
|
70
|
+
if (/^@\w+/v.test(afterIndent)) {
|
|
71
|
+
report('There must be no indentation.', null, {
|
|
72
|
+
line: getLineNumber(lines, lineIndex)
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If we haven't seen any content yet (main description first line) and there's content, report
|
|
78
|
+
if (!hasSeenContent && afterIndent.trim().length > 0) {
|
|
79
|
+
report('There must be no indentation.', null, {
|
|
80
|
+
line: getLineNumber(lines, lineIndex)
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// For continuation lines, check consistency
|
|
86
|
+
if (hasSeenContent && afterIndent.trim().length > 0) {
|
|
87
|
+
if (currentSectionIndent === null) {
|
|
88
|
+
// First indented line in this section, set the indent level
|
|
89
|
+
currentSectionIndent = indentAmount;
|
|
90
|
+
} else if (indentAmount < currentSectionIndent) {
|
|
91
|
+
// Indentation is less than the established level (inconsistent)
|
|
92
|
+
report('There must be no indentation.', null, {
|
|
93
|
+
line: getLineNumber(lines, lineIndex)
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} else if (/^\s*\*\s+\S/v.test(line)) {
|
|
99
|
+
// No indentation on this line, reset section indent tracking
|
|
100
|
+
// (unless it's just whitespace or a closing comment)
|
|
101
|
+
currentSectionIndent = null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Track if we've seen any content (non-whitespace after the *)
|
|
105
|
+
if (/^\s*\*\s+\S/v.test(line)) {
|
|
106
|
+
hasSeenContent = true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Reset section indent when we encounter a tag
|
|
110
|
+
if (/@\w+/v.test(line)) {
|
|
111
|
+
currentSectionIndent = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
const reg = /^(?:\/?\**|[ \t]*)\*[ \t]{2}/gmv;
|
|
116
|
+
if (reg.test(text)) {
|
|
117
|
+
const lineBreaks = text.slice(0, reg.lastIndex).match(/\n/gv) || [];
|
|
118
|
+
report('There must be no indentation.', null, {
|
|
119
|
+
line: lineBreaks.length
|
|
120
|
+
});
|
|
121
|
+
}
|
|
49
122
|
}
|
|
50
123
|
}, {
|
|
51
124
|
iterateAllJsdocs: true,
|
|
@@ -57,6 +130,10 @@ var _default = exports.default = (0, _iterateJsdoc.default)(({
|
|
|
57
130
|
schema: [{
|
|
58
131
|
additionalProperties: false,
|
|
59
132
|
properties: {
|
|
133
|
+
allowIndentedSections: {
|
|
134
|
+
description: 'Allows indentation of nested sections on subsequent lines (like bullet lists)',
|
|
135
|
+
type: 'boolean'
|
|
136
|
+
},
|
|
60
137
|
excludeTags: {
|
|
61
138
|
description: `Array of tags (e.g., \`['example', 'description']\`) whose content will be
|
|
62
139
|
"hidden" from the \`check-indentation\` rule. Defaults to \`['example']\`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkIndentation.cjs","names":["_iterateJsdoc","_interopRequireDefault","require","e","__esModule","default","maskExcludedContent","str","excludeTags","regContent","RegExp","join","replace","_match","margin","code","repeat","match","length","maskCodeBlocks","replaceAll","_default","exports","iterateJsdoc","context","jsdocNode","report","sourceCode","options","reg","textWithoutCodeBlocks","getText","text","test","lineBreaks","slice","lastIndex","line","iterateAllJsdocs","meta","docs","description","url","schema","additionalProperties","properties","items","pattern","type","module"],"sources":["../../src/rules/checkIndentation.js"],"sourcesContent":["import iterateJsdoc from '../iterateJsdoc.js';\n\n/**\n * @param {string} str\n * @param {string[]} excludeTags\n * @returns {string}\n */\nconst maskExcludedContent = (str, excludeTags) => {\n const regContent = new RegExp(`([ \\\\t]+\\\\*)[ \\\\t]@(?:${excludeTags.join('|')})(?=[ \\\\n])([\\\\w\\\\|\\\\W]*?\\\\n)(?=[ \\\\t]*\\\\*(?:[ \\\\t]*@\\\\w+\\\\s|\\\\/))`, 'gv');\n\n return str.replace(regContent, (_match, margin, code) => {\n return (margin + '\\n').repeat(code.match(/\\n/gv).length);\n });\n};\n\n/**\n * @param {string} str\n * @returns {string}\n */\nconst maskCodeBlocks = (str) => {\n const regContent = /([ \\t]+\\*)[ \\t]```[^\\n]*?([\\w\\|\\W]*?\\n)(?=[ \\t]*\\*(?:[ \\t]*(?:```|@\\w+\\s)|\\/))/gv;\n\n return str.replaceAll(regContent, (_match, margin, code) => {\n return (margin + '\\n').repeat(code.match(/\\n/gv).length);\n });\n};\n\nexport default iterateJsdoc(({\n context,\n jsdocNode,\n report,\n sourceCode,\n}) => {\n const options = context.options[0] || {};\n const /** @type {{excludeTags: string[]}} */ {\n excludeTags = [\n 'example',\n ],\n } = options;\n\n const reg = /^(?:\\/?\\**|[ \\t]*)\\*[ \\t]{2}/gmv;\n const textWithoutCodeBlocks = maskCodeBlocks(sourceCode.getText(jsdocNode));\n const text = excludeTags.length ? maskExcludedContent(textWithoutCodeBlocks, excludeTags) : textWithoutCodeBlocks;\n\n if (reg.test(text)) {\n const lineBreaks = text.slice(0, reg.lastIndex).match(/\\n/gv) || [];\n report('There must be no indentation.', null, {\n line: lineBreaks.length,\n });\n }\n}, {\n iterateAllJsdocs: true,\n meta: {\n docs: {\n description: 'Reports invalid padding inside JSDoc blocks.',\n url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-indentation.md#repos-sticky-header',\n },\n schema: [\n {\n additionalProperties: false,\n properties: {\n excludeTags: {\n description: `Array of tags (e.g., \\`['example', 'description']\\`) whose content will be\n\"hidden\" from the \\`check-indentation\\` rule. Defaults to \\`['example']\\`.\n\nBy default, the whole JSDoc block will be checked for invalid padding.\nThat would include \\`@example\\` blocks too, which can get in the way\nof adding full, readable examples of code without ending up with multiple\nlinting issues.\n\nWhen disabled (by passing \\`excludeTags: []\\` option), the following code *will*\nreport a padding issue:\n\n\\`\\`\\`js\n/**\n * @example\n * anArray.filter((a) => {\n * return a.b;\n * });\n */\n\\`\\`\\``,\n items: {\n pattern: '^\\\\S+$',\n type: 'string',\n },\n type: 'array',\n },\n },\n type: 'object',\n },\n ],\n type: 'layout',\n },\n});\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"checkIndentation.cjs","names":["_iterateJsdoc","_interopRequireDefault","require","e","__esModule","default","maskExcludedContent","str","excludeTags","regContent","RegExp","join","replace","_match","margin","code","repeat","match","length","maskCodeBlocks","replaceAll","getLineNumber","lines","lineIndex","precedingText","slice","lineBreaks","_default","exports","iterateJsdoc","context","jsdocNode","report","sourceCode","options","allowIndentedSections","textWithoutCodeBlocks","getText","text","split","hasSeenContent","currentSectionIndent","line","entries","indentMatch","afterIndent","indentAmount","test","trim","reg","lastIndex","iterateAllJsdocs","meta","docs","description","url","schema","additionalProperties","properties","type","items","pattern","module"],"sources":["../../src/rules/checkIndentation.js"],"sourcesContent":["import iterateJsdoc from '../iterateJsdoc.js';\n\n/**\n * @param {string} str\n * @param {string[]} excludeTags\n * @returns {string}\n */\nconst maskExcludedContent = (str, excludeTags) => {\n const regContent = new RegExp(`([ \\\\t]+\\\\*)[ \\\\t]@(?:${excludeTags.join('|')})(?=[ \\\\n])([\\\\w\\\\|\\\\W]*?\\\\n)(?=[ \\\\t]*\\\\*(?:[ \\\\t]*@\\\\w+\\\\s|\\\\/))`, 'gv');\n\n return str.replace(regContent, (_match, margin, code) => {\n return (margin + '\\n').repeat(code.match(/\\n/gv).length);\n });\n};\n\n/**\n * @param {string} str\n * @returns {string}\n */\nconst maskCodeBlocks = (str) => {\n const regContent = /([ \\t]+\\*)[ \\t]```[^\\n]*?([\\w\\|\\W]*?\\n)(?=[ \\t]*\\*(?:[ \\t]*(?:```|@\\w+\\s)|\\/))/gv;\n\n return str.replaceAll(regContent, (_match, margin, code) => {\n return (margin + '\\n').repeat(code.match(/\\n/gv).length);\n });\n};\n\n/**\n * @param {string[]} lines\n * @param {number} lineIndex\n * @returns {number}\n */\nconst getLineNumber = (lines, lineIndex) => {\n const precedingText = lines.slice(0, lineIndex).join('\\n');\n const lineBreaks = precedingText.match(/\\n/gv) || [];\n return lineBreaks.length + 1;\n};\n\nexport default iterateJsdoc(({\n context,\n jsdocNode,\n report,\n sourceCode,\n}) => {\n const options = context.options[0] || {};\n const /** @type {{excludeTags: string[], allowIndentedSections: boolean}} */ {\n allowIndentedSections = false,\n excludeTags = [\n 'example',\n ],\n } = options;\n\n const textWithoutCodeBlocks = maskCodeBlocks(sourceCode.getText(jsdocNode));\n const text = excludeTags.length ? maskExcludedContent(textWithoutCodeBlocks, excludeTags) : textWithoutCodeBlocks;\n\n if (allowIndentedSections) {\n // When allowIndentedSections is enabled, only check for indentation on tag lines\n // and the very first line of the main description\n const lines = text.split('\\n');\n let hasSeenContent = false;\n let currentSectionIndent = null;\n\n for (const [\n lineIndex,\n line,\n ] of lines.entries()) {\n // Check for indentation (two or more spaces after *)\n const indentMatch = line.match(/^(?:\\/?\\**|[\\t ]*)\\*([\\t ]{2,})/v);\n\n if (indentMatch) {\n // Check what comes after the indentation\n const afterIndent = line.slice(indentMatch[0].length);\n const indentAmount = indentMatch[1].length;\n\n // If this is a tag line with indentation, always report\n if (/^@\\w+/v.test(afterIndent)) {\n report('There must be no indentation.', null, {\n line: getLineNumber(lines, lineIndex),\n });\n return;\n }\n\n // If we haven't seen any content yet (main description first line) and there's content, report\n if (!hasSeenContent && afterIndent.trim().length > 0) {\n report('There must be no indentation.', null, {\n line: getLineNumber(lines, lineIndex),\n });\n return;\n }\n\n // For continuation lines, check consistency\n if (hasSeenContent && afterIndent.trim().length > 0) {\n if (currentSectionIndent === null) {\n // First indented line in this section, set the indent level\n currentSectionIndent = indentAmount;\n } else if (indentAmount < currentSectionIndent) {\n // Indentation is less than the established level (inconsistent)\n report('There must be no indentation.', null, {\n line: getLineNumber(lines, lineIndex),\n });\n return;\n }\n }\n } else if (/^\\s*\\*\\s+\\S/v.test(line)) {\n // No indentation on this line, reset section indent tracking\n // (unless it's just whitespace or a closing comment)\n currentSectionIndent = null;\n }\n\n // Track if we've seen any content (non-whitespace after the *)\n if (/^\\s*\\*\\s+\\S/v.test(line)) {\n hasSeenContent = true;\n }\n\n // Reset section indent when we encounter a tag\n if (/@\\w+/v.test(line)) {\n currentSectionIndent = null;\n }\n }\n } else {\n const reg = /^(?:\\/?\\**|[ \\t]*)\\*[ \\t]{2}/gmv;\n if (reg.test(text)) {\n const lineBreaks = text.slice(0, reg.lastIndex).match(/\\n/gv) || [];\n report('There must be no indentation.', null, {\n line: lineBreaks.length,\n });\n }\n }\n}, {\n iterateAllJsdocs: true,\n meta: {\n docs: {\n description: 'Reports invalid padding inside JSDoc blocks.',\n url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-indentation.md#repos-sticky-header',\n },\n schema: [\n {\n additionalProperties: false,\n properties: {\n allowIndentedSections: {\n description: 'Allows indentation of nested sections on subsequent lines (like bullet lists)',\n type: 'boolean',\n },\n excludeTags: {\n description: `Array of tags (e.g., \\`['example', 'description']\\`) whose content will be\n\"hidden\" from the \\`check-indentation\\` rule. Defaults to \\`['example']\\`.\n\nBy default, the whole JSDoc block will be checked for invalid padding.\nThat would include \\`@example\\` blocks too, which can get in the way\nof adding full, readable examples of code without ending up with multiple\nlinting issues.\n\nWhen disabled (by passing \\`excludeTags: []\\` option), the following code *will*\nreport a padding issue:\n\n\\`\\`\\`js\n/**\n * @example\n * anArray.filter((a) => {\n * return a.b;\n * });\n */\n\\`\\`\\``,\n items: {\n pattern: '^\\\\S+$',\n type: 'string',\n },\n type: 'array',\n },\n },\n type: 'object',\n },\n ],\n type: 'layout',\n },\n});\n"],"mappings":";;;;;;AAAA,IAAAA,aAAA,GAAAC,sBAAA,CAAAC,OAAA;AAA8C,SAAAD,uBAAAE,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAE9C;AACA;AACA;AACA;AACA;AACA,MAAMG,mBAAmB,GAAGA,CAACC,GAAG,EAAEC,WAAW,KAAK;EAChD,MAAMC,UAAU,GAAG,IAAIC,MAAM,CAAC,yBAAyBF,WAAW,CAACG,IAAI,CAAC,GAAG,CAAC,oEAAoE,EAAE,IAAI,CAAC;EAEvJ,OAAOJ,GAAG,CAACK,OAAO,CAACH,UAAU,EAAE,CAACI,MAAM,EAAEC,MAAM,EAAEC,IAAI,KAAK;IACvD,OAAO,CAACD,MAAM,GAAG,IAAI,EAAEE,MAAM,CAACD,IAAI,CAACE,KAAK,CAAC,MAAM,CAAC,CAACC,MAAM,CAAC;EAC1D,CAAC,CAAC;AACJ,CAAC;;AAED;AACA;AACA;AACA;AACA,MAAMC,cAAc,GAAIZ,GAAG,IAAK;EAC9B,MAAME,UAAU,GAAG,kFAAkF;EAErG,OAAOF,GAAG,CAACa,UAAU,CAACX,UAAU,EAAE,CAACI,MAAM,EAAEC,MAAM,EAAEC,IAAI,KAAK;IAC1D,OAAO,CAACD,MAAM,GAAG,IAAI,EAAEE,MAAM,CAACD,IAAI,CAACE,KAAK,CAAC,MAAM,CAAC,CAACC,MAAM,CAAC;EAC1D,CAAC,CAAC;AACJ,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,MAAMG,aAAa,GAAGA,CAACC,KAAK,EAAEC,SAAS,KAAK;EAC1C,MAAMC,aAAa,GAAGF,KAAK,CAACG,KAAK,CAAC,CAAC,EAAEF,SAAS,CAAC,CAACZ,IAAI,CAAC,IAAI,CAAC;EAC1D,MAAMe,UAAU,GAAGF,aAAa,CAACP,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE;EACpD,OAAOS,UAAU,CAACR,MAAM,GAAG,CAAC;AAC9B,CAAC;AAAC,IAAAS,QAAA,GAAAC,OAAA,CAAAvB,OAAA,GAEa,IAAAwB,qBAAY,EAAC,CAAC;EAC3BC,OAAO;EACPC,SAAS;EACTC,MAAM;EACNC;AACF,CAAC,KAAK;EACJ,MAAMC,OAAO,GAAGJ,OAAO,CAACI,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;EACxC,MAAM,sEAAuE;IAC3EC,qBAAqB,GAAG,KAAK;IAC7B3B,WAAW,GAAG,CACZ,SAAS;EAEb,CAAC,GAAG0B,OAAO;EAEX,MAAME,qBAAqB,GAAGjB,cAAc,CAACc,UAAU,CAACI,OAAO,CAACN,SAAS,CAAC,CAAC;EAC3E,MAAMO,IAAI,GAAG9B,WAAW,CAACU,MAAM,GAAGZ,mBAAmB,CAAC8B,qBAAqB,EAAE5B,WAAW,CAAC,GAAG4B,qBAAqB;EAEjH,IAAID,qBAAqB,EAAE;IACzB;IACA;IACA,MAAMb,KAAK,GAAGgB,IAAI,CAACC,KAAK,CAAC,IAAI,CAAC;IAC9B,IAAIC,cAAc,GAAG,KAAK;IAC1B,IAAIC,oBAAoB,GAAG,IAAI;IAE/B,KAAK,MAAM,CACTlB,SAAS,EACTmB,IAAI,CACL,IAAIpB,KAAK,CAACqB,OAAO,CAAC,CAAC,EAAE;MACpB;MACA,MAAMC,WAAW,GAAGF,IAAI,CAACzB,KAAK,CAAC,kCAAkC,CAAC;MAElE,IAAI2B,WAAW,EAAE;QACf;QACA,MAAMC,WAAW,GAAGH,IAAI,CAACjB,KAAK,CAACmB,WAAW,CAAC,CAAC,CAAC,CAAC1B,MAAM,CAAC;QACrD,MAAM4B,YAAY,GAAGF,WAAW,CAAC,CAAC,CAAC,CAAC1B,MAAM;;QAE1C;QACA,IAAI,QAAQ,CAAC6B,IAAI,CAACF,WAAW,CAAC,EAAE;UAC9Bb,MAAM,CAAC,+BAA+B,EAAE,IAAI,EAAE;YAC5CU,IAAI,EAAErB,aAAa,CAACC,KAAK,EAAEC,SAAS;UACtC,CAAC,CAAC;UACF;QACF;;QAEA;QACA,IAAI,CAACiB,cAAc,IAAIK,WAAW,CAACG,IAAI,CAAC,CAAC,CAAC9B,MAAM,GAAG,CAAC,EAAE;UACpDc,MAAM,CAAC,+BAA+B,EAAE,IAAI,EAAE;YAC5CU,IAAI,EAAErB,aAAa,CAACC,KAAK,EAAEC,SAAS;UACtC,CAAC,CAAC;UACF;QACF;;QAEA;QACA,IAAIiB,cAAc,IAAIK,WAAW,CAACG,IAAI,CAAC,CAAC,CAAC9B,MAAM,GAAG,CAAC,EAAE;UACnD,IAAIuB,oBAAoB,KAAK,IAAI,EAAE;YACjC;YACAA,oBAAoB,GAAGK,YAAY;UACrC,CAAC,MAAM,IAAIA,YAAY,GAAGL,oBAAoB,EAAE;YAC9C;YACAT,MAAM,CAAC,+BAA+B,EAAE,IAAI,EAAE;cAC5CU,IAAI,EAAErB,aAAa,CAACC,KAAK,EAAEC,SAAS;YACtC,CAAC,CAAC;YACF;UACF;QACF;MACF,CAAC,MAAM,IAAI,cAAc,CAACwB,IAAI,CAACL,IAAI,CAAC,EAAE;QACpC;QACA;QACAD,oBAAoB,GAAG,IAAI;MAC7B;;MAEA;MACA,IAAI,cAAc,CAACM,IAAI,CAACL,IAAI,CAAC,EAAE;QAC7BF,cAAc,GAAG,IAAI;MACvB;;MAEA;MACA,IAAI,OAAO,CAACO,IAAI,CAACL,IAAI,CAAC,EAAE;QACtBD,oBAAoB,GAAG,IAAI;MAC7B;IACF;EACF,CAAC,MAAM;IACL,MAAMQ,GAAG,GAAG,iCAAiC;IAC7C,IAAIA,GAAG,CAACF,IAAI,CAACT,IAAI,CAAC,EAAE;MAClB,MAAMZ,UAAU,GAAGY,IAAI,CAACb,KAAK,CAAC,CAAC,EAAEwB,GAAG,CAACC,SAAS,CAAC,CAACjC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE;MACnEe,MAAM,CAAC,+BAA+B,EAAE,IAAI,EAAE;QAC5CU,IAAI,EAAEhB,UAAU,CAACR;MACnB,CAAC,CAAC;IACJ;EACF;AACF,CAAC,EAAE;EACDiC,gBAAgB,EAAE,IAAI;EACtBC,IAAI,EAAE;IACJC,IAAI,EAAE;MACJC,WAAW,EAAE,8CAA8C;MAC3DC,GAAG,EAAE;IACP,CAAC;IACDC,MAAM,EAAE,CACN;MACEC,oBAAoB,EAAE,KAAK;MAC3BC,UAAU,EAAE;QACVvB,qBAAqB,EAAE;UACrBmB,WAAW,EAAE,+EAA+E;UAC5FK,IAAI,EAAE;QACR,CAAC;QACDnD,WAAW,EAAE;UACX8C,WAAW,EAAE;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;UACKM,KAAK,EAAE;YACLC,OAAO,EAAE,QAAQ;YACjBF,IAAI,EAAE;UACR,CAAC;UACDA,IAAI,EAAE;QACR;MACF,CAAC;MACDA,IAAI,EAAE;IACR,CAAC,CACF;IACDA,IAAI,EAAE;EACR;AACF,CAAC,CAAC;AAAAG,MAAA,CAAAlC,OAAA,GAAAA,OAAA,CAAAvB,OAAA","ignoreList":[]}
|
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']`.
|
package/package.json
CHANGED
|
@@ -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 (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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']\`.
|
package/src/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']`.
|