From f9a966e081eb15e2e859265a9b9f024de0f50edd Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Mon, 5 Aug 2024 00:06:22 +0800 Subject: [PATCH] feat(`lines-before-block`): add new rule; fixes #1209 --- .README/README.md | 1 + .README/rules/lines-before-block.md | 26 ++++ README.md | 1 + docs/rules/lines-before-block.md | 114 ++++++++++++++ src/index.js | 3 + src/rules/linesBeforeBlock.js | 53 +++++++ test/rules/assertions/linesBeforeBlock.js | 177 ++++++++++++++++++++++ test/rules/ruleNames.json | 1 + 8 files changed, 376 insertions(+) create mode 100644 .README/rules/lines-before-block.md create mode 100644 docs/rules/lines-before-block.md create mode 100644 src/rules/linesBeforeBlock.js create mode 100644 test/rules/assertions/linesBeforeBlock.js diff --git a/.README/README.md b/.README/README.md index 31487f62..bb122ae0 100644 --- a/.README/README.md +++ b/.README/README.md @@ -247,6 +247,7 @@ non-default-recommended fixer). |:heavy_check_mark:|:wrench:|[empty-tags](./docs/rules/empty-tags.md#readme)|Checks tags that are expected to be empty (e.g., `@abstract` or `@async`), reporting if they have content| |:heavy_check_mark:||[implements-on-classes](./docs/rules/implements-on-classes.md#readme)|Prohibits use of `@implements` on non-constructor functions (to enforce the tag only being used on classes/constructors)| |||[informative-docs](./docs/rules/informative-docs.md#readme)|Reports on JSDoc texts that serve only to restate their attached name.| +|:heavy_check_mark:||[lines-before-block](./docs/rules/lines-before-block.md#readme)|Enforces minimum number of newlines before JSDoc comment blocks| |||[match-description](./docs/rules/match-description.md#readme)|Defines customizable regular expression rules for your tag descriptions| ||:wrench:|[match-name](./docs/rules/match-name.md#readme)|Reports the name portion of a JSDoc tag if matching or not matching a given regular expression| |:heavy_check_mark:|:wrench:|[multiline-blocks](./docs/rules/multiline-blocks.md#readme)|Controls how and whether jsdoc blocks can be expressed as single or multiple line blocks| diff --git a/.README/rules/lines-before-block.md b/.README/rules/lines-before-block.md new file mode 100644 index 00000000..d1029c5a --- /dev/null +++ b/.README/rules/lines-before-block.md @@ -0,0 +1,26 @@ +# `lines-before-block` + +This rule enforces minimum number of newlines before JSDoc comment blocks +(except at the beginning of a file). + +## Options + +### `lines` + +The minimum number of lines to require. Defaults to 1. + +||| +|---|---| +|Context|everywhere| +|Tags|N/A| +|Recommended|true| +|Settings|| +|Options|`lines`| + +## Failing examples + + + +## Passing examples + + diff --git a/README.md b/README.md index a9dae4cb..09975474 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,7 @@ non-default-recommended fixer). |:heavy_check_mark:|:wrench:|[empty-tags](./docs/rules/empty-tags.md#readme)|Checks tags that are expected to be empty (e.g., `@abstract` or `@async`), reporting if they have content| |:heavy_check_mark:||[implements-on-classes](./docs/rules/implements-on-classes.md#readme)|Prohibits use of `@implements` on non-constructor functions (to enforce the tag only being used on classes/constructors)| |||[informative-docs](./docs/rules/informative-docs.md#readme)|Reports on JSDoc texts that serve only to restate their attached name.| +|:heavy_check_mark:||[lines-before-block](./docs/rules/lines-before-block.md#readme)|Enforces minimum number of newlines before JSDoc comment blocks| |||[match-description](./docs/rules/match-description.md#readme)|Defines customizable regular expression rules for your tag descriptions| ||:wrench:|[match-name](./docs/rules/match-name.md#readme)|Reports the name portion of a JSDoc tag if matching or not matching a given regular expression| |:heavy_check_mark:|:wrench:|[multiline-blocks](./docs/rules/multiline-blocks.md#readme)|Controls how and whether jsdoc blocks can be expressed as single or multiple line blocks| diff --git a/docs/rules/lines-before-block.md b/docs/rules/lines-before-block.md new file mode 100644 index 00000000..416f64c5 --- /dev/null +++ b/docs/rules/lines-before-block.md @@ -0,0 +1,114 @@ + + +# lines-before-block + +This rule enforces minimum number of newlines before JSDoc comment blocks +(except at the beginning of a file). + + + +## Options + + + +### lines + +The minimum number of lines to require. Defaults to 1. + +||| +|---|---| +|Context|everywhere| +|Tags|N/A| +|Recommended|true| +|Settings|| +|Options|`lines`| + + + +## Failing examples + +The following patterns are considered problems: + +````js +someCode; +/** + * + */ +// Message: Required 1 line(s) before JSDoc block + +someCode; +/** + * + */ +// "jsdoc/lines-before-block": ["error"|"warn", {"lines":2}] +// Message: Required 2 line(s) before JSDoc block + +// Some comment +/** + * + */ +// Message: Required 1 line(s) before JSDoc block + +/* Some comment */ +/** + * + */ +// Message: Required 1 line(s) before JSDoc block + +/** + * Some comment + */ +/** + * + */ +// Message: Required 1 line(s) before JSDoc block +```` + + + + + +## Passing examples + +The following patterns are not considered problems: + +````js +/** +* +*/ + +someCode; + +/** + * + */ + +someCode; + + +/** + * + */ +// "jsdoc/lines-before-block": ["error"|"warn", {"lines":2}] + +// Some comment + +/** + * + */ + +/* Some comment */ + +/** + * + */ + +/** + * Some comment + */ + +/** + * + */ +```` + diff --git a/src/index.js b/src/index.js index b01eacd3..61c6fe76 100644 --- a/src/index.js +++ b/src/index.js @@ -15,6 +15,7 @@ import emptyTags from './rules/emptyTags.js'; import implementsOnClasses from './rules/implementsOnClasses.js'; import importsAsDependencies from './rules/importsAsDependencies.js'; import informativeDocs from './rules/informativeDocs.js'; +import linesBeforeBlock from './rules/linesBeforeBlock.js'; import matchDescription from './rules/matchDescription.js'; import matchName from './rules/matchName.js'; import multilineBlocks from './rules/multilineBlocks.js'; @@ -92,6 +93,7 @@ const index = { 'implements-on-classes': implementsOnClasses, 'imports-as-dependencies': importsAsDependencies, 'informative-docs': informativeDocs, + 'lines-before-block': linesBeforeBlock, 'match-description': matchDescription, 'match-name': matchName, 'multiline-blocks': multilineBlocks, @@ -167,6 +169,7 @@ const createRecommendedRuleset = (warnOrError, flatName) => { 'jsdoc/implements-on-classes': warnOrError, 'jsdoc/imports-as-dependencies': 'off', 'jsdoc/informative-docs': 'off', + 'jsdoc/lines-before-block': warnOrError, 'jsdoc/match-description': 'off', 'jsdoc/match-name': 'off', 'jsdoc/multiline-blocks': warnOrError, diff --git a/src/rules/linesBeforeBlock.js b/src/rules/linesBeforeBlock.js new file mode 100644 index 00000000..c5e4cd7e --- /dev/null +++ b/src/rules/linesBeforeBlock.js @@ -0,0 +1,53 @@ +import iterateJsdoc from '../iterateJsdoc.js'; + +export default iterateJsdoc(({ + context, + jsdocNode, + sourceCode, + report, +}) => { + const { + lines = 1, + } = context.options[0] || {}; + + const tokenBefore = sourceCode.getTokenBefore(jsdocNode, {includeComments: true}); + if (!tokenBefore) { + return; + } + + if (tokenBefore.loc?.end?.line + lines >= + /** @type {number} */ + (jsdocNode.loc?.start?.line) + ) { + /** @type {import('eslint').Rule.ReportFixer} */ + const fix = (fixer) => { + return fixer.insertTextAfter( + /** @type {import('eslint').AST.Token} */ + (tokenBefore), + '\n'.repeat(lines) + ); + }; + report(`Required ${lines} line(s) before JSDoc block`, fix); + } +}, { + iterateAllJsdocs: true, + meta: { + fixable: 'code', + docs: { + description: 'Enforces minimum number of newlines before JSDoc comment blocks', + url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/lines-before-block.md#repos-sticky-header', + }, + schema: [ + { + additionalProperties: false, + properties: { + lines: { + type: 'integer' + } + }, + type: 'object', + }, + ], + type: 'suggestion', + }, +}); diff --git a/test/rules/assertions/linesBeforeBlock.js b/test/rules/assertions/linesBeforeBlock.js new file mode 100644 index 00000000..f84578db --- /dev/null +++ b/test/rules/assertions/linesBeforeBlock.js @@ -0,0 +1,177 @@ +export default { + invalid: [ + { + code: ` + someCode; + /** + * + */ + `, + errors: [ + { + line: 3, + message: 'Required 1 line(s) before JSDoc block', + }, + ], + output: ` + someCode; + + /** + * + */ + `, + }, + { + code: ` + someCode; + /** + * + */ + `, + errors: [ + { + line: 3, + message: 'Required 2 line(s) before JSDoc block', + }, + ], + options: [ + { + lines: 2, + }, + ], + output: ` + someCode; + + + /** + * + */ + `, + }, + { + code: ` + // Some comment + /** + * + */ + `, + errors: [ + { + line: 3, + message: 'Required 1 line(s) before JSDoc block', + }, + ], + output: ` + // Some comment + + /** + * + */ + `, + }, + { + code: ` + /* Some comment */ + /** + * + */ + `, + errors: [ + { + line: 3, + message: 'Required 1 line(s) before JSDoc block', + }, + ], + output: ` + /* Some comment */ + + /** + * + */ + `, + }, + { + code: ` + /** + * Some comment + */ + /** + * + */ + `, + errors: [ + { + line: 5, + message: 'Required 1 line(s) before JSDoc block', + }, + ], + output: ` + /** + * Some comment + */ + + /** + * + */ + `, + }, + ], + valid: [ + { + code: `/**\n *\n */`, + }, + { + code: ` + someCode; + + /** + * + */ + `, + }, + { + code: ` + someCode; + + + /** + * + */ + `, + options: [ + { + lines: 2, + }, + ], + }, + { + code: ` + // Some comment + + /** + * + */ + `, + }, + { + code: ` + /* Some comment */ + + /** + * + */ + `, + }, + { + code: ` + /** + * Some comment + */ + + /** + * + */ + `, + }, + ], +}; diff --git a/test/rules/ruleNames.json b/test/rules/ruleNames.json index 1540d80b..df037553 100644 --- a/test/rules/ruleNames.json +++ b/test/rules/ruleNames.json @@ -16,6 +16,7 @@ "implements-on-classes", "imports-as-dependencies", "informative-docs", + "lines-before-block", "match-description", "match-name", "multiline-blocks",