From 6e5e76d05253d4ce2b097fd9c4d8fb27840c585e Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Mon, 17 May 2021 07:34:50 +0800 Subject: [PATCH] feat(`tag-lines`): add `tags` option to override behavior on a tag-specific basis (including only applying to tag(s)); fixes #734 --- .README/rules/tag-lines.md | 17 +- README.md | 115 ++++++++++++- src/rules/tagLines.js | 101 ++++++++--- test/rules/assertions/tagLines.js | 277 ++++++++++++++++++++++++++++++ 4 files changed, 480 insertions(+), 30 deletions(-) diff --git a/.README/rules/tag-lines.md b/.README/rules/tag-lines.md index 6d165f42d..53c80e3f5 100644 --- a/.README/rules/tag-lines.md +++ b/.README/rules/tag-lines.md @@ -4,8 +4,11 @@ Enforces lines (or no lines) between tags. #### Options -The first option is a single string set to "always" or "never" (defaults to -"never"). +The first option is a single string set to "always", "never", or "any" +(defaults to "never"). + +"any" is only useful with `tags` (allowing non-enforcement of lines except +for particular tags). The second option is an object with the following optional properties. @@ -18,6 +21,16 @@ Use with "always" to indicate the number of lines to require be present. Use with "always" to indicate the normal lines to be added after tags should not be added after the final tag. +##### `tags` (default to empty object) + +Overrides the default behavior depending on specific tags. + +An object whose keys are tag names and whose values are objects with the +following keys: + +1. `lines` - Set to `always` or `never` to override. +2. `count` - Overrides main `count` (for "always") + ||| |---|---| |Context|everywhere| diff --git a/README.md b/README.md index 504c7bb1f..6a5407eca 100644 --- a/README.md +++ b/README.md @@ -18998,8 +18998,11 @@ Enforces lines (or no lines) between tags. #### Options -The first option is a single string set to "always" or "never" (defaults to -"never"). +The first option is a single string set to "always", "never", or "any" +(defaults to "never"). + +"any" is only useful with `tags` (allowing non-enforcement of lines except +for particular tags). The second option is an object with the following optional properties. @@ -19014,6 +19017,17 @@ Use with "always" to indicate the number of lines to require be present. Use with "always" to indicate the normal lines to be added after tags should not be added after the final tag. + +##### tags (default to empty object) + +Overrides the default behavior depending on specific tags. + +An object whose keys are tag names and whose values are objects with the +following keys: + +1. `lines` - Set to `always` or `never` to override. +2. `count` - Overrides main `count` (for "always") + ||| |---|---| |Context|everywhere| @@ -19086,6 +19100,57 @@ The following patterns are considered problems: */ // "jsdoc/tag-lines": ["error"|"warn", "always"] // Message: Expected 1 line between tags but found 0 + +/** + * Some description + * @param {string} a + * @param {number} b + */ +// "jsdoc/tag-lines": ["error"|"warn", "never",{"tags":{"param":{"lines":"always"}}}] +// Message: Expected 1 line between tags but found 0 + +/** + * Some description + * @param {string} a + * @param {number} b + */ +// "jsdoc/tag-lines": ["error"|"warn", "never",{"tags":{"param":{"lines":"always"}}}] +// Message: Expected 1 line between tags but found 0 + +/** + * Some description + * @param {string} a + * @param {number} b + * + */ +// "jsdoc/tag-lines": ["error"|"warn", "always",{"tags":{"param":{"lines":"never"}}}] +// Message: Expected no lines between tags + +/** + * Some description + * @param {string} a + * + * @param {number} b + */ +// "jsdoc/tag-lines": ["error"|"warn", "never",{"count":2,"tags":{"param":{"lines":"always"}}}] +// Message: Expected 2 lines between tags but found 1 + +/** + * Some description + * @param {string} a + * + * @param {number} b + */ +// "jsdoc/tag-lines": ["error"|"warn", "never",{"count":5,"tags":{"param":{"count":2,"lines":"always"}}}] +// Message: Expected 2 lines between tags but found 1 + +/** + * Some description + * @param {string} a + * @param {number} b + */ +// "jsdoc/tag-lines": ["error"|"warn", "always",{"tags":{"anotherTag":{"lines":"never"}}}] +// Message: Expected 1 line between tags but found 0 ```` The following patterns are not considered problems: @@ -19138,6 +19203,52 @@ The following patterns are not considered problems: * */ // "jsdoc/tag-lines": ["error"|"warn", "always",{"count":2}] + +/** + * Some description + * @param {string} a + * @param {number} b + */ +// "jsdoc/tag-lines": ["error"|"warn", "never",{"tags":{"param":{"lines":"any"}}}] + +/** + * Some description + * @param {string} a + * @param {number} b + */ +// "jsdoc/tag-lines": ["error"|"warn", "always",{"tags":{"param":{"lines":"never"}}}] + +/** + * Some description + * @param {number} a + * @param {number} b + */ +// "jsdoc/tag-lines": ["error"|"warn", "never",{"tags":{"param":{"lines":"any"}}}] + +/** + * Some description + * @param {number} a + * @param {number} b + */ +// "jsdoc/tag-lines": ["error"|"warn", "never",{"tags":{"param":{"lines":"never"}}}] + +/** + * Some description + * @param {string} a + * + * + * @param {number} b + * + * + */ +// "jsdoc/tag-lines": ["error"|"warn", "never",{"count":5,"tags":{"param":{"count":2,"lines":"always"}}}] + +/** + * Some description + * @param {string} a + * @param {number} b + */ +// "jsdoc/tag-lines": ["error"|"warn", "never",{"tags":{"anotherTag":{"lines":"always"}}}] ```` diff --git a/src/rules/tagLines.js b/src/rules/tagLines.js index 197bb398f..e55e423e2 100644 --- a/src/rules/tagLines.js +++ b/src/rules/tagLines.js @@ -10,50 +10,80 @@ export default iterateJsdoc(({ { count = 1, noEndLines = false, + tags = {}, } = {}, ] = context.options; - if (alwaysNever === 'never') { - jsdoc.tags.some((tg, tagIdx) => { - return tg.source.some(({tokens: {tag, name, type, description, end}}, idx) => { - const fixer = () => { - utils.removeTagItem(tagIdx, idx); - }; - if (!tag && !name && !type && !description && !end) { - utils.reportJSDoc( - 'Expected no lines between tags', - {line: tg.source[0].number + 1}, - fixer, - true, - ); - - return true; - } + jsdoc.tags.some((tg, tagIdx) => { + let lastTag; + return tg.source.some(({tokens: {tag, name, type, description, end}}, idx) => { + const fixer = () => { + utils.removeTagItem(tagIdx, idx); + }; + if (lastTag && tags[lastTag.slice(1)]?.lines === 'always') { return false; - }); - }); + } - return; - } + if ( + !tag && !name && !type && !description && !end && + (alwaysNever === 'never' || + lastTag && tags[lastTag.slice(1)]?.lines === 'never' + ) + ) { + utils.reportJSDoc( + 'Expected no lines between tags', + {line: tg.source[0].number + 1}, + fixer, + ); + + return true; + } + + lastTag = tag; + + return false; + }); + }); (noEndLines ? jsdoc.tags.slice(0, -1) : jsdoc.tags).some((tg, tagIdx) => { const lines = []; + let currentTag; tg.source.forEach(({number, tokens: {tag, name, type, description, end}}, idx) => { + if (tag) { + currentTag = tag; + } if (!tag && !name && !type && !description && !end) { lines.push({idx, number}); } }); - if (lines.length < count) { + + const currentTg = currentTag && tags[currentTag.slice(1)]; + const tagCount = currentTg?.count; + + const defaultAlways = alwaysNever === 'always' && currentTg?.lines !== 'never' && + currentTg?.lines !== 'any' && lines.length < count; + + let overrideAlways; + let fixCount = count; + if (!defaultAlways) { + fixCount = typeof tagCount === 'number' ? tagCount : count; + overrideAlways = currentTg?.lines === 'always' && + lines.length < fixCount; + } + + if (defaultAlways || overrideAlways) { const fixer = () => { - utils.addLines(tagIdx, lines[lines.length - 1]?.idx || 1, count - lines.length); + utils.addLines(tagIdx, lines[lines.length - 1]?.idx || 1, fixCount - lines.length); }; + const line = lines[lines.length - 1]?.number || tg.source[0].number; utils.reportJSDoc( - `Expected ${count} line${count === 1 ? '' : 's'} between tags but found ${lines.length}`, - {line: lines[lines.length - 1]?.number || tg.source[0].number}, + `Expected ${fixCount} line${fixCount === 1 ? '' : 's'} between tags but found ${lines.length}`, + { + line, + }, fixer, - true, ); return true; @@ -71,7 +101,7 @@ export default iterateJsdoc(({ fixable: 'code', schema: [ { - enum: ['always', 'never'], + enum: ['always', 'any', 'never'], type: 'string', }, { @@ -83,6 +113,25 @@ export default iterateJsdoc(({ noEndLines: { type: 'boolean', }, + tags: { + properties: { + patternProperties: { + '.*': { + additionalProperties: false, + properties: { + count: { + type: 'integer', + }, + lines: { + enum: ['always', 'never'], + type: 'string', + }, + }, + }, + }, + }, + type: 'object', + }, }, type: 'object', }, diff --git a/test/rules/assertions/tagLines.js b/test/rules/assertions/tagLines.js index b7cdeadb2..fb5f95d38 100644 --- a/test/rules/assertions/tagLines.js +++ b/test/rules/assertions/tagLines.js @@ -173,6 +173,181 @@ export default { */ `, }, + { + code: ` + /** + * Some description + * @param {string} a + * @param {number} b + */ + `, + errors: [{ + line: 4, + message: 'Expected 1 line between tags but found 0', + }], + options: ['never', { + tags: { + param: { + lines: 'always', + }, + }, + }], + output: ` + /** + * Some description + * @param {string} a + * + * @param {number} b + */ + `, + }, + { + code: ` + /** + * Some description + * @param {string} a + * @param {number} b + */ + `, + errors: [{ + line: 4, + message: 'Expected 1 line between tags but found 0', + }], + options: ['never', { + tags: { + param: { + lines: 'always', + }, + }, + }], + output: ` + /** + * Some description + * @param {string} a + * + * @param {number} b + */ + `, + }, + { + code: ` + /** + * Some description + * @param {string} a + * @param {number} b + * + */ + `, + errors: [{ + line: 6, + message: 'Expected no lines between tags', + }], + options: ['always', { + tags: { + param: { + lines: 'never', + }, + }, + }], + output: ` + /** + * Some description + * @param {string} a + * @param {number} b + */ + `, + }, + { + code: ` + /** + * Some description + * @param {string} a + * + * @param {number} b + */ + `, + errors: [{ + line: 5, + message: 'Expected 2 lines between tags but found 1', + }], + options: ['never', { + count: 2, + tags: { + param: { + lines: 'always', + }, + }, + }], + output: ` + /** + * Some description + * @param {string} a + * + * + * @param {number} b + */ + `, + }, + { + code: ` + /** + * Some description + * @param {string} a + * + * @param {number} b + */ + `, + errors: [{ + line: 5, + message: 'Expected 2 lines between tags but found 1', + }], + options: ['never', { + count: 5, + tags: { + param: { + count: 2, + lines: 'always', + }, + }, + }], + output: ` + /** + * Some description + * @param {string} a + * + * + * @param {number} b + */ + `, + }, + { + code: ` + /** + * Some description + * @param {string} a + * @param {number} b + */ + `, + errors: [{ + line: 4, + message: 'Expected 1 line between tags but found 0', + }], + options: ['always', { + tags: { + anotherTag: { + lines: 'never', + }, + }, + }], + output: ` + /** + * Some description + * @param {string} a + * + * @param {number} b + */ + `, + }, ], valid: [ { @@ -252,5 +427,107 @@ export default { count: 2, }], }, + { + code: ` + /** + * Some description + * @param {string} a + * @param {number} b + */ + `, + options: ['never', { + tags: { + param: { + lines: 'any', + }, + }, + }], + }, + { + code: ` + /** + * Some description + * @param {string} a + * @param {number} b + */ + `, + options: ['always', { + tags: { + param: { + lines: 'never', + }, + }, + }], + }, + { + code: ` + /** + * Some description + * @param {number} a + * @param {number} b + */ + `, + options: ['never', { + tags: { + param: { + lines: 'any', + }, + }, + }], + }, + { + code: ` + /** + * Some description + * @param {number} a + * @param {number} b + */ + `, + options: ['never', { + tags: { + param: { + lines: 'never', + }, + }, + }], + }, + { + code: ` + /** + * Some description + * @param {string} a + * + * + * @param {number} b + * + * + */ + `, + options: ['never', { + count: 5, + tags: { + param: { + count: 2, + lines: 'always', + }, + }, + }], + }, + { + code: ` + /** + * Some description + * @param {string} a + * @param {number} b + */ + `, + options: ['never', { + tags: { + anotherTag: { + lines: 'always', + }, + }, + }], + }, ], };