diff --git a/.README/rules/multiline-blocks.md b/.README/rules/multiline-blocks.md
index 6cbdb182a..9d71e323d 100644
--- a/.README/rules/multiline-blocks.md
+++ b/.README/rules/multiline-blocks.md
@@ -38,6 +38,13 @@ line will be reported. (Text preceding a newline is not reported.)
If this is `true`, any single line blocks will be reported, except those which
are whitelisted in `singleLineTags`.
+### `requireSingleLineUnderCount` (defaults to `null`)
+
+If this number is set, it indicates a minimum line width for a single line of
+JSDoc content spread over a multi-line comment block. If a line is under the
+minimum length, it will be reported so as to enforce single line JSDoc blocks
+for such cases.
+
### `singleLineTags` (defaults to `['lends', 'type']`)
An array of tags which can nevertheless be allowed as single line blocks when
@@ -96,7 +103,7 @@ cannot be reliably added after the tag either).
|Tags|Any (though `singleLineTags` and `multilineTags` control the application)|
|Recommended|true|
|Settings||
-|Options|`allowMultipleTags`, `minimumLengthForMultiline`, `multilineTags`, `noFinalLineText`, `noMultilineBlocks`, `noSingleLineBlocks`, `noZeroLineText`, `singleLineTags`|
+|Options|`allowMultipleTags`, `minimumLengthForMultiline`, `multilineTags`, `noFinalLineText`, `noMultilineBlocks`, `noSingleLineBlocks`, `noZeroLineText`, `requireSingleLineUnderCount`, `singleLineTags`|
## Failing examples
diff --git a/docs/rules/multiline-blocks.md b/docs/rules/multiline-blocks.md
index 7913017ef..9c3a23072 100644
--- a/docs/rules/multiline-blocks.md
+++ b/docs/rules/multiline-blocks.md
@@ -50,6 +50,15 @@ line will be reported. (Text preceding a newline is not reported.)
If this is `true`, any single line blocks will be reported, except those which
are whitelisted in `singleLineTags`.
+
+
+### requireSingleLineUnderCount (defaults to null)
+
+If this number is set, it indicates a minimum line width for a single line of
+JSDoc content spread over a multi-line comment block. If a line is under the
+minimum length, it will be reported so as to enforce single line JSDoc blocks
+for such cases.
+
### singleLineTags (defaults to ['lends', 'type'])
@@ -120,7 +129,7 @@ cannot be reliably added after the tag either).
|Tags|Any (though `singleLineTags` and `multilineTags` control the application)|
|Recommended|true|
|Settings||
-|Options|`allowMultipleTags`, `minimumLengthForMultiline`, `multilineTags`, `noFinalLineText`, `noMultilineBlocks`, `noSingleLineBlocks`, `noZeroLineText`, `singleLineTags`|
+|Options|`allowMultipleTags`, `minimumLengthForMultiline`, `multilineTags`, `noFinalLineText`, `noMultilineBlocks`, `noSingleLineBlocks`, `noZeroLineText`, `requireSingleLineUnderCount`, `singleLineTags`|
@@ -283,6 +292,38 @@ The following patterns are considered problems:
* Description */
// "jsdoc/multiline-blocks": ["error"|"warn", {"noFinalLineText":true}]
// Message: Should have no text on the final line (before the `*/`).
+
+/**
+ * Description too short
+ */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+// Message: Description is too short to be multi-line.
+
+/** Description too short
+ */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+// Message: Description is too short to be multi-line.
+
+/**
+ * Description too short */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+// Message: Description is too short to be multi-line.
+
+/**
+ * @someTag {someType} Description too short
+ */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+// Message: Description is too short to be multi-line.
+
+/** @someTag {someType} Description too short
+ */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+// Message: Description is too short to be multi-line.
+
+/**
+ * @someTag {someType} Description too short */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+// Message: Description is too short to be multi-line.
````
@@ -394,5 +435,39 @@ The following patterns are not considered problems:
/** @someTag with Description */
// "jsdoc/multiline-blocks": ["error"|"warn", {"noFinalLineText":true}]
+
+/**
+ * This description here is very much long enough, I'd say, wouldn't you?
+ */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+
+/**
+ * This description here is
+ * on multiple lines.
+ */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+
+/** This description here is on a single line, so it doesn't matter if it goes over. */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+
+/**
+ * @someTag {someType} This description here is very much long enough, I'd say, wouldn't you?
+ */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+
+/**
+ * @someTag {someType} This description here is
+ * on multiple lines.
+ */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+
+/** @someTag {someTag} This description here is on a single line, so it doesn't matter if it goes over. */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
+
+/**
+ * Description short but has...
+ * @someTag
+ */
+// "jsdoc/multiline-blocks": ["error"|"warn", {"requireSingleLineUnderCount":80}]
````
diff --git a/src/rules/multilineBlocks.js b/src/rules/multilineBlocks.js
index 29079110d..223dece31 100644
--- a/src/rules/multilineBlocks.js
+++ b/src/rules/multilineBlocks.js
@@ -1,5 +1,151 @@
import iterateJsdoc from '../iterateJsdoc.js';
+/**
+ * @param {import('@es-joy/jsdoccomment').JsdocBlockWithInline} jsdoc
+ * @param {import('../iterateJsdoc.js').Utils} utils
+ * @param {number} requireSingleLineUnderCount
+ */
+const checkForShortTags = (jsdoc, utils, requireSingleLineUnderCount) => {
+ if (!requireSingleLineUnderCount || !jsdoc.tags.length) {
+ return false;
+ }
+
+ let lastLineWithTag = 0;
+ let exceedsLength = false;
+ let hasDesc = false;
+ const tagLines = jsdoc.source.reduce((acc, {
+ tokens: {
+ delimiter,
+ description: desc,
+ name,
+ postDelimiter,
+ postName,
+ postTag,
+ postType,
+ start,
+ tag,
+ type,
+ },
+ }, idx) => {
+ if (tag.length) {
+ lastLineWithTag = idx;
+ if (
+ start.length + delimiter.length + postDelimiter.length +
+ type.length + postType.length + name.length + postName.length +
+ tag.length + postTag.length + desc.length <
+ requireSingleLineUnderCount
+ ) {
+ exceedsLength = true;
+ }
+
+ return acc + 1;
+ } else if (desc.length) {
+ hasDesc = true;
+ return acc;
+ }
+
+ return acc;
+ }, 0);
+ // Could be tagLines > 1
+ if (!hasDesc && exceedsLength && tagLines === 1) {
+ const fixer = () => {
+ const tokens = jsdoc.source[lastLineWithTag].tokens;
+ jsdoc.source = [
+ {
+ number: 0,
+ source: '',
+ tokens: utils.seedTokens({
+ delimiter: '/**',
+ description: tokens.description.trimEnd() + ' ',
+ end: '*/',
+ name: tokens.name,
+ postDelimiter: ' ',
+ postName: tokens.postName,
+ postTag: tokens.postTag,
+ postType: tokens.postType,
+ start: jsdoc.source[0].tokens.start,
+ tag: tokens.tag,
+ type: tokens.type,
+ }),
+ },
+ ];
+ };
+
+ utils.reportJSDoc(
+ 'Description is too short to be multi-line.',
+ null,
+ fixer,
+ );
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * @param {import('@es-joy/jsdoccomment').JsdocBlockWithInline} jsdoc
+ * @param {import('../iterateJsdoc.js').Utils} utils
+ * @param {number} requireSingleLineUnderCount
+ */
+const checkForShortDescriptions = (jsdoc, utils, requireSingleLineUnderCount) => {
+ if (!requireSingleLineUnderCount || jsdoc.tags.length) {
+ return false;
+ }
+
+ let lastLineWithDesc = 0;
+ let exceedsLength = false;
+ const descLines = jsdoc.source.reduce((acc, {
+ tokens: {
+ delimiter,
+ description: desc,
+ postDelimiter,
+ start,
+ },
+ }, idx) => {
+ if (desc.length) {
+ lastLineWithDesc = idx;
+ if (
+ start.length + delimiter.length + postDelimiter.length + desc.length <
+ requireSingleLineUnderCount
+ ) {
+ exceedsLength = true;
+ }
+
+ return acc + 1;
+ }
+
+ return acc;
+ }, 0);
+ // Could be descLines > 1
+ if (exceedsLength && descLines === 1) {
+ const fixer = () => {
+ const desc = jsdoc.source[lastLineWithDesc].tokens.description;
+ jsdoc.source = [
+ {
+ number: 0,
+ source: '',
+ tokens: utils.seedTokens({
+ delimiter: '/**',
+ description: desc.trimEnd() + ' ',
+ end: '*/',
+ postDelimiter: ' ',
+ start: jsdoc.source[0].tokens.start,
+ }),
+ },
+ ];
+ };
+
+ utils.reportJSDoc(
+ 'Description is too short to be multi-line.',
+ null,
+ fixer,
+ );
+ return true;
+ }
+
+ return false;
+};
+
export default iterateJsdoc(({
context,
jsdoc,
@@ -15,6 +161,7 @@ export default iterateJsdoc(({
noMultilineBlocks = false,
noSingleLineBlocks = false,
noZeroLineText = true,
+ requireSingleLineUnderCount = null,
singleLineTags = [
'lends', 'type',
],
@@ -62,6 +209,16 @@ export default iterateJsdoc(({
return;
}
+ if (checkForShortDescriptions(jsdoc, utils, requireSingleLineUnderCount)
+ ) {
+ return;
+ }
+
+ if (checkForShortTags(jsdoc, utils, requireSingleLineUnderCount)
+ ) {
+ return;
+ }
+
const lineChecks = () => {
if (
noZeroLineText &&
@@ -318,6 +475,9 @@ export default iterateJsdoc(({
noZeroLineText: {
type: 'boolean',
},
+ requireSingleLineUnderCount: {
+ type: 'number',
+ },
singleLineTags: {
items: {
type: 'string',
diff --git a/test/rules/assertions/multilineBlocks.js b/test/rules/assertions/multilineBlocks.js
index eae189420..cd7288c4e 100644
--- a/test/rules/assertions/multilineBlocks.js
+++ b/test/rules/assertions/multilineBlocks.js
@@ -619,6 +619,128 @@ export default /** @type {import('../index.js').TestCases} */ ({
*/
`,
},
+ {
+ code: `
+ /**
+ * Description too short
+ */
+ `,
+ errors: [
+ {
+ line: 2,
+ message: 'Description is too short to be multi-line.',
+ },
+ ],
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ output: `
+ /** Description too short */
+ `,
+ },
+ {
+ code: `
+ /** Description too short
+ */
+ `,
+ errors: [
+ {
+ line: 2,
+ message: 'Description is too short to be multi-line.',
+ },
+ ],
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ output: `
+ /** Description too short */
+ `,
+ },
+ {
+ code: `
+ /**
+ * Description too short */
+ `,
+ errors: [
+ {
+ line: 2,
+ message: 'Description is too short to be multi-line.',
+ },
+ ],
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ output: `
+ /** Description too short */
+ `,
+ },
+ {
+ code: `
+ /**
+ * @someTag {someType} Description too short
+ */
+ `,
+ errors: [
+ {
+ line: 2,
+ message: 'Description is too short to be multi-line.',
+ },
+ ],
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ output: `
+ /** @someTag {someType} Description too short */
+ `,
+ },
+ {
+ code: `
+ /** @someTag {someType} Description too short
+ */
+ `,
+ errors: [
+ {
+ line: 2,
+ message: 'Description is too short to be multi-line.',
+ },
+ ],
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ output: `
+ /** @someTag {someType} Description too short */
+ `,
+ },
+ {
+ code: `
+ /**
+ * @someTag {someType} Description too short */
+ `,
+ errors: [
+ {
+ line: 2,
+ message: 'Description is too short to be multi-line.',
+ },
+ ],
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ output: `
+ /** @someTag {someType} Description too short */
+ `,
+ },
],
valid: [
{
@@ -895,5 +1017,88 @@ export default /** @type {import('../index.js').TestCases} */ ({
},
],
},
+ {
+ code: `
+ /**
+ * This description here is very much long enough, I'd say, wouldn't you?
+ */
+`,
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ },
+ {
+ code: `
+ /**
+ * This description here is
+ * on multiple lines.
+ */
+`,
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ },
+ {
+ code: `
+ /** This description here is on a single line, so it doesn't matter if it goes over. */
+`,
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ },
+ {
+ code: `
+ /**
+ * @someTag {someType} This description here is very much long enough, I'd say, wouldn't you?
+ */
+`,
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ },
+ {
+ code: `
+ /**
+ * @someTag {someType} This description here is
+ * on multiple lines.
+ */
+`,
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ },
+ {
+ code: `
+ /** @someTag {someTag} This description here is on a single line, so it doesn't matter if it goes over. */
+`,
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ },
+ {
+ code: `
+ /**
+ * Description short but has...
+ * @someTag
+ */
+ `,
+ options: [
+ {
+ requireSingleLineUnderCount: 80,
+ },
+ ],
+ },
],
});