diff --git a/README.md b/README.md index 84e723164..f092b54c6 100644 --- a/README.md +++ b/README.md @@ -14126,6 +14126,32 @@ function quux() { } // Settings: {"jsdoc":{"structuredTags":{"see":{"required":["typeOrNameRequired"],"type":false}}}} // Message: Cannot add "typeOrNameRequired" to `require` with the tag's `type` set to `false` + +/** + * @template T<~, R + * @param {function(!T): !R} parser + * @return {function(!Array): !Array} + */ +parseArray = function(parser) { + return function(array) { + return array.map(parser); + }; +}; +// Settings: {"jsdoc":{"mode":"closure"}} +// Message: Syntax error in namepath: T<~ + +/** + * @template T, R<~ + * @param {function(!T): !R} parser + * @return {function(!Array): !Array} + */ +parseArray = function(parser) { + return function(array) { + return array.map(parser); + }; +}; +// Settings: {"jsdoc":{"mode":"closure"}} +// Message: Syntax error in namepath: R<~ ```` The following patterns are not considered problems: @@ -14369,6 +14395,30 @@ function quux() { } // Settings: {"jsdoc":{"structuredTags":{"see":{"name":"namepath-referencing"}}}} + +/** + * @template T, R + * @param {function(!T): !R} parser + * @return {function(!Array): !Array} + */ +parseArray = function(parser) { + return function(array) { + return array.map(parser); + }; +}; +// Settings: {"jsdoc":{"mode":"closure"}} + +/** + * @template T, R<~ + * @param {function(!T): !R} parser + * @return {function(!Array): !Array} + */ +parseArray = function(parser) { + return function(array) { + return array.map(parser); + }; +}; +// Settings: {"jsdoc":{"mode":"jsdoc"}} ```` diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index 9c91b8a78..234f77346 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -103,6 +103,10 @@ const getBasicUtils = (context, {tagNamePreference, mode}) => { }); }; + utils.parseClosureTemplateTag = (tag) => { + return jsdocUtils.parseClosureTemplateTag(tag); + }; + utils.getPreferredTagNameObject = ({tagName}) => { const ret = jsdocUtils.getPreferredTagName(context, mode, tagName, tagNamePreference); const isObject = ret && typeof ret === 'object'; diff --git a/src/rules/noUndefinedTypes.js b/src/rules/noUndefinedTypes.js index e6e815dfd..f17b83976 100644 --- a/src/rules/noUndefinedTypes.js +++ b/src/rules/noUndefinedTypes.js @@ -112,7 +112,7 @@ export default iterateJsdoc(({ } const closureGenericTypes = _.flatMap(templateTags, (tag) => { - return jsdocUtils.parseClosureTemplateTag(tag); + return utils.parseClosureTemplateTag(tag); }); const allDefinedTypes = new Set(globalScope.variables.map(({name}) => { diff --git a/src/rules/validTypes.js b/src/rules/validTypes.js index d5dfd56bb..8c09b3408 100644 --- a/src/rules/validTypes.js +++ b/src/rules/validTypes.js @@ -164,7 +164,13 @@ export default iterateJsdoc(({ ) && Boolean(tag.name); if (hasNameOrNamepathPosition) { - validNamepathParsing(tag.name, tag.tag); + if (mode !== 'jsdoc' && tag.tag === 'template') { + utils.parseClosureTemplateTag(tag).forEach((namepath) => { + validNamepathParsing(namepath); + }); + } else { + validNamepathParsing(tag.name, tag.tag); + } } }); }, { diff --git a/test/rules/assertions/validTypes.js b/test/rules/assertions/validTypes.js index 366ccd7fa..29b0cbaf9 100644 --- a/test/rules/assertions/validTypes.js +++ b/test/rules/assertions/validTypes.js @@ -632,6 +632,56 @@ export default { }, }, }, + { + code: ` + /** + * @template T<~, R + * @param {function(!T): !R} parser + * @return {function(!Array): !Array} + */ + parseArray = function(parser) { + return function(array) { + return array.map(parser); + }; + }; + `, + errors: [ + { + line: 3, + message: 'Syntax error in namepath: T<~', + }, + ], + settings: { + jsdoc: { + mode: 'closure', + }, + }, + }, + { + code: ` + /** + * @template T, R<~ + * @param {function(!T): !R} parser + * @return {function(!Array): !Array} + */ + parseArray = function(parser) { + return function(array) { + return array.map(parser); + }; + }; + `, + errors: [ + { + line: 3, + message: 'Syntax error in namepath: R<~', + }, + ], + settings: { + jsdoc: { + mode: 'closure', + }, + }, + }, ], valid: [ { @@ -1051,5 +1101,43 @@ export default { }, }, }, + { + code: ` + /** + * @template T, R + * @param {function(!T): !R} parser + * @return {function(!Array): !Array} + */ + parseArray = function(parser) { + return function(array) { + return array.map(parser); + }; + }; + `, + settings: { + jsdoc: { + mode: 'closure', + }, + }, + }, + { + code: ` + /** + * @template T, R<~ + * @param {function(!T): !R} parser + * @return {function(!Array): !Array} + */ + parseArray = function(parser) { + return function(array) { + return array.map(parser); + }; + }; + `, + settings: { + jsdoc: { + mode: 'jsdoc', + }, + }, + }, ], };