diff --git a/.README/README.md b/.README/README.md index 98e325076..f67732267 100644 --- a/.README/README.md +++ b/.README/README.md @@ -135,7 +135,7 @@ how many line breaks to add when a block is missing. containing both JavaScript and TypeScript, you can also use [`overrides`](https://eslint.org/docs/user-guide/configuring). You may also set to `"permissive"` to try to be as accommodating to any of the styles, but this is not recommended. Currently is used for the following: - - Determine valid tags for `check-tag-names` + - Determine valid tags and aliases for `check-tag-names` - Only check `@template` in `no-undefined-types` for types in "closure" and "typescript" modes - For type-checking rules, determine which tags will be checked for types @@ -144,6 +144,8 @@ how many line breaks to add when a block is missing. - For type-checking rules, impacts parsing of types (through [jsdoctypeparser](https://github.com/jsdoctypeparser/jsdoctypeparser) dependency) - Check preferred tag names + - Disallows namepath on `@interface` for "closure" mode in `valid-types` (and + avoids checking in other rules) ### Alias Preference diff --git a/.README/rules/check-tag-names.md b/.README/rules/check-tag-names.md index 733bbc6dd..97d187726 100644 --- a/.README/rules/check-tag-names.md +++ b/.README/rules/check-tag-names.md @@ -126,7 +126,8 @@ template And for [Closure](https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler), when `settings.jsdoc.mode` is set to `closure`, one may use the following (in -addition to the jsdoc and TypeScript tags): +addition to the jsdoc and TypeScript tags–though replacing `returns` with +`return`): ``` define (synonym of `const` per jsdoc source) diff --git a/.README/rules/valid-types.md b/.README/rules/valid-types.md index f20a4817c..e99ab74df 100644 --- a/.README/rules/valid-types.md +++ b/.README/rules/valid-types.md @@ -10,7 +10,8 @@ Also impacts behaviors on namepath (or event)-defining and pointing tags: `@class`, `@constructor`, `@constant`, `@const`, `@function`, `@func`, `@method`, `@interface`, `@member`, `@var`, `@mixin`, `@namespace` -1. Name(path)-pointing tags requiring namepath: `@alias`, `@augments`, `@extends`, `@lends`, `@memberof`, `@memberof!`, `@mixes`, `@this` +1. Name(path)-pointing tags requiring namepath: `@alias`, `@augments`, + `@extends`, `@lends`, `@memberof`, `@memberof!`, `@mixes`, `@this` 1. Name(path)-pointing tags (which may have value without namepath or their namepath can be expressed elsewhere on the block): `@listens`, `@fires`, `@emits`, and `@modifies` diff --git a/README.md b/README.md index 4a7902a8f..c59714f17 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ how many line breaks to add when a block is missing. containing both JavaScript and TypeScript, you can also use [`overrides`](https://eslint.org/docs/user-guide/configuring). You may also set to `"permissive"` to try to be as accommodating to any of the styles, but this is not recommended. Currently is used for the following: - - Determine valid tags for `check-tag-names` + - Determine valid tags and aliases for `check-tag-names` - Only check `@template` in `no-undefined-types` for types in "closure" and "typescript" modes - For type-checking rules, determine which tags will be checked for types @@ -202,6 +202,8 @@ how many line breaks to add when a block is missing. - For type-checking rules, impacts parsing of types (through [jsdoctypeparser](https://github.com/jsdoctypeparser/jsdoctypeparser) dependency) - Check preferred tag names + - Disallows namepath on `@interface` for "closure" mode in `valid-types` (and + avoids checking in other rules) ### Alias Preference @@ -2629,7 +2631,8 @@ template And for [Closure](https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler), when `settings.jsdoc.mode` is set to `closure`, one may use the following (in -addition to the jsdoc and TypeScript tags): +addition to the jsdoc and TypeScript tags–though replacing `returns` with +`return`): ``` define (synonym of `const` per jsdoc source) @@ -2861,6 +2864,13 @@ function quux () { // Settings: {"jsdoc":{"tagNamePreference":{"abc":"abcd"}}} // Message: Invalid JSDoc tag (preference). Replace "abc" JSDoc tag with "abcd". +/** + * @returns + */ +function quux (foo) {} +// Settings: {"jsdoc":{"mode":"closure"}} +// Message: Invalid JSDoc tag (preference). Replace "returns" JSDoc tag with "return". + /** * @modifies * @abstract @@ -3069,6 +3079,17 @@ function quux (foo) { } // Settings: {"jsdoc":{"tagNamePreference":{"param":"baz","returns":{"message":"Prefer `bar`","replacement":"bar"},"todo":false}}} +/** + * @returns + */ +function quux (foo) {} + +/** + * @return + */ +function quux (foo) {} +// Settings: {"jsdoc":{"mode":"closure"}} + /** * @modifies * @abstract @@ -13212,7 +13233,8 @@ Also impacts behaviors on namepath (or event)-defining and pointing tags: `@class`, `@constructor`, `@constant`, `@const`, `@function`, `@func`, `@method`, `@interface`, `@member`, `@var`, `@mixin`, `@namespace` -1. Name(path)-pointing tags requiring namepath: `@alias`, `@augments`, `@extends`, `@lends`, `@memberof`, `@memberof!`, `@mixes`, `@this` +1. Name(path)-pointing tags requiring namepath: `@alias`, `@augments`, + `@extends`, `@lends`, `@memberof`, `@memberof!`, `@mixes`, `@this` 1. Name(path)-pointing tags (which may have value without namepath or their namepath can be expressed elsewhere on the block): `@listens`, `@fires`, `@emits`, and `@modifies` @@ -13430,6 +13452,18 @@ function quux () {} function foo(bar) {} // Settings: {"jsdoc":{"mode":"jsdoc"}} // Message: Syntax error in type: [number, string] + +/** + * @interface name< + */ +// Settings: {"jsdoc":{"mode":"jsdoc"}} +// Message: Syntax error in namepath: name< + +/** + * @interface name + */ +// Settings: {"jsdoc":{"mode":"closure"}} +// Message: @interface should not have a name in "closure" mode. ```` The following patterns are not considered problems: diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index 1a6ddb7a0..f01430e2c 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -280,7 +280,7 @@ const getUtils = ( const exemptedBy = context.options[0]?.exemptedBy ?? [ 'inheritDoc', - ...settings.mode === 'closure' ? [] : ['inheritdoc'], + ...mode === 'closure' ? [] : ['inheritdoc'], ]; if (exemptedBy.length && utils.getPresentTags(exemptedBy).length) { return true; @@ -302,7 +302,7 @@ const getUtils = ( }; utils.tagMightHaveNamePosition = (tagName) => { - return jsdocUtils.tagMightHaveNamePosition(tagName); + return jsdocUtils.tagMightHaveNamePosition(mode, tagName); }; utils.tagMustHaveTypePosition = (tagName) => { @@ -314,7 +314,7 @@ const getUtils = ( }; utils.isNamepathDefiningTag = (tagName) => { - return jsdocUtils.isNamepathDefiningTag(tagName); + return jsdocUtils.isNamepathDefiningTag(mode, tagName); }; utils.hasDefinedTypeReturnTag = (tag) => { diff --git a/src/jsdocUtils.js b/src/jsdocUtils.js index 6428a3a0a..3e5a40100 100644 --- a/src/jsdocUtils.js +++ b/src/jsdocUtils.js @@ -231,7 +231,9 @@ const getPreferredTagName = ( // Allow keys to have a 'tag ' prefix to avoid upstream bug in ESLint // that disallows keys that conflict with Object.prototype, - // e.g. 'tag constructor' for 'constructor' (#537) + // e.g. 'tag constructor' for 'constructor': + // https://github.com/eslint/eslint/issues/13289 + // https://github.com/gajus/eslint-plugin-jsdoc/issues/537 const tagPreferenceFixed = _.mapKeys(tagPreference, (value, key) => { return key.replace('tag ', ''); }); @@ -375,7 +377,7 @@ const tagsWithOptionalTypePositionClosure = new Set([ ]); // None of these show as having curly brackets for their name/namepath -const namepathDefiningTags = new Set([ +const closureNamepathDefiningTags = new Set([ // These appear to require a "name" in their signature, albeit these // are somewhat different from other "name"'s (including as described // at https://jsdoc.app/about-namepaths.html ) @@ -386,7 +388,6 @@ const namepathDefiningTags = new Set([ 'class', 'constructor', 'constant', 'const', 'function', 'func', 'method', - 'interface', 'member', 'var', 'mixin', 'namespace', @@ -400,11 +401,14 @@ const namepathDefiningTags = new Set([ 'typedef', 'callback', ]); +const namepathDefiningTags = new Set([ + ...closureNamepathDefiningTags, -// The following do not seem to allow curly brackets in their doc -// signature or examples (besides `modifies` and `param`) -const tagsWithOptionalNamePosition = new Set([ - ...namepathDefiningTags, + // Allows for "name" in signature, but indicates as optional + 'interface', +]); + +const tagsWithOptionalNamePositionBase = new Set([ 'param', // `borrows` has a different format, however, so needs special parsing; @@ -432,6 +436,18 @@ const tagsWithOptionalNamePosition = new Set([ 'see', ]); +// The following do not seem to allow curly brackets in their doc +// signature or examples (besides `modifies` and `param`) +const tagsWithOptionalNamePosition = new Set([ + ...namepathDefiningTags, + ...tagsWithOptionalNamePositionBase, +]); + +const closureTagsWithOptionalNamePosition = new Set([ + ...closureNamepathDefiningTags, + ...tagsWithOptionalNamePositionBase, +]); + // Todo: `@link` seems to require a namepath OR URL and might be checked as such. // The doc signature of `event` seems to require a "name" @@ -463,8 +479,10 @@ const tagsWithMandatoryTypeOrNamePosition = new Set([ 'mixes', ]); -const isNamepathDefiningTag = (tagName) => { - return namepathDefiningTags.has(tagName); +const isNamepathDefiningTag = (mode, tagName) => { + return mode === 'closure' ? + closureNamepathDefiningTags.has(tagName) : + namepathDefiningTags.has(tagName); }; const tagMightHaveTypePosition = (mode, tag) => { @@ -485,8 +503,10 @@ const tagMustHaveTypePosition = (mode, tag) => { return tagsWithMandatoryTypePosition.has(tag); }; -const tagMightHaveNamePosition = (tag) => { - return tagsWithOptionalNamePosition.has(tag); +const tagMightHaveNamePosition = (mode, tag) => { + return mode === 'closure' ? + closureTagsWithOptionalNamePosition.has(tag) : + tagsWithOptionalNamePosition.has(tag); }; const tagMustHaveNamePosition = (tag) => { @@ -494,7 +514,7 @@ const tagMustHaveNamePosition = (tag) => { }; const tagMightHaveEitherTypeOrNamePosition = (mode, tag) => { - return tagMightHaveTypePosition(mode, tag) || tagMightHaveNamePosition(tag); + return tagMightHaveTypePosition(mode, tag) || tagMightHaveNamePosition(mode, tag); }; const tagMustHaveEitherTypeOrNamePosition = (tag) => { diff --git a/src/rules/validTypes.js b/src/rules/validTypes.js index 30320d1f4..8c894e0e3 100644 --- a/src/rules/validTypes.js +++ b/src/rules/validTypes.js @@ -18,6 +18,7 @@ export default iterateJsdoc(({ if (!jsdoc.tags) { return; } + // eslint-disable-next-line complexity jsdoc.tags.forEach((tag) => { const validNamepathParsing = function (namepath, tagName) { try { @@ -80,7 +81,8 @@ export default iterateJsdoc(({ const hasEither = utils.tagMightHaveEitherTypeOrNamePosition(tag.tag) && (hasTypePosition || hasNameOrNamepathPosition); const mustHaveEither = utils.tagMustHaveEitherTypeOrNamePosition(tag.tag); - if (tag.tag === 'borrows') { + switch (tag.tag) { + case 'borrows': { const thisNamepath = tag.description.replace(asExpression, ''); if (!asExpression.test(tag.description) || !thisNamepath) { @@ -94,7 +96,17 @@ export default iterateJsdoc(({ validNamepathParsing(thatNamepath); } - } else { + break; + } + case 'interface': { + if (mode === 'closure' && tag.name) { + report('@interface should not have a name in "closure" mode.', null, tag); + break; + } + } + + // Fallthrough + default: { if (mustHaveEither && !hasEither && !mustHaveTypePosition) { report(`Tag @${tag.tag} must have either a type or namepath`, null, tag); @@ -113,6 +125,7 @@ export default iterateJsdoc(({ report(`Tag @${tag.tag} must have a name/namepath`, null, tag); } } + } }); }, { iterateAllJsdocs: true, diff --git a/src/tagNames.js b/src/tagNames.js index fe055cddc..e3532c63d 100644 --- a/src/tagNames.js +++ b/src/tagNames.js @@ -144,13 +144,17 @@ const undocumentedClosureTags = { }; const { - // eslint-disable-next-line no-unused-vars + /* eslint-disable no-unused-vars */ inheritdoc, - ...typeScriptTagsNoInheritdoc + + // Will be inverted to prefer `return` + returns, + /* eslint-enable no-unused-vars */ + ...typeScriptTagsInClosure } = typeScriptTags; const closureTags = { - ...typeScriptTagsNoInheritdoc, + ...typeScriptTagsInClosure, ...undocumentedClosureTags, // From https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler @@ -180,6 +184,10 @@ const closureTags = { // Defined as a synonym of `interface` in jsdoc `definitions.js` record: [], + return: [ + 'returns', + ], + struct: [], suppress: [], diff --git a/test/rules/assertions/checkTagNames.js b/test/rules/assertions/checkTagNames.js index ca0f8d48c..2bef53075 100644 --- a/test/rules/assertions/checkTagNames.js +++ b/test/rules/assertions/checkTagNames.js @@ -503,6 +503,30 @@ export default { }, }, }, + { + code: ` + /** + * @returns + */ + function quux (foo) {} + `, + errors: [ + { + message: 'Invalid JSDoc tag (preference). Replace "returns" JSDoc tag with "return".', + }, + ], + output: ` + /** + * @return + */ + function quux (foo) {} + `, + settings: { + jsdoc: { + mode: 'closure', + }, + }, + }, { code: `${ALL_JSDOC_TAGS_COMMENT}\nfunction quux (foo) {}`, errors: [ @@ -619,6 +643,27 @@ export default { }, }, }, + { + code: ` + /** + * @returns + */ + function quux (foo) {} + `, + }, + { + code: ` + /** + * @return + */ + function quux (foo) {} + `, + settings: { + jsdoc: { + mode: 'closure', + }, + }, + }, { code: `${ALL_JSDOC_TAGS_COMMENT}\nfunction quux (foo) {}`, }, diff --git a/test/rules/assertions/validTypes.js b/test/rules/assertions/validTypes.js index 1c0a823b1..8a32751c9 100644 --- a/test/rules/assertions/validTypes.js +++ b/test/rules/assertions/validTypes.js @@ -359,6 +359,40 @@ export default { }, }, }, + { + code: ` + /** + * @interface name< + */ + `, + errors: [ + { + message: 'Syntax error in namepath: name<', + }, + ], + settings: { + jsdoc: { + mode: 'jsdoc', + }, + }, + }, + { + code: ` + /** + * @interface name + */ + `, + errors: [ + { + message: '@interface should not have a name in "closure" mode.', + }, + ], + settings: { + jsdoc: { + mode: 'closure', + }, + }, + }, ], valid: [ {