From 9962b222f5a6479f28d427940297bdef574dfb4b Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Sun, 2 Jan 2022 09:15:41 +0800 Subject: [PATCH] fix(`no-undefined-types`): detection of AST descendants of template tag; fixes #559, fixes #827 --- README.md | 52 +++++++++++++++ src/rules/noUndefinedTypes.js | 47 ++++++------- test/rules/assertions/noUndefinedTypes.js | 81 +++++++++++++++++++++++ 3 files changed, 153 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 4c0351dd2..9886cfdd1 100644 --- a/README.md +++ b/README.md @@ -9896,12 +9896,64 @@ function quux(foo) { } +/** + * @template T + * @param {T} arg + * @returns {[T]} + */ +function genericFunctionExample(arg) { + const result = /** @type {[T]} */ (new Array()); + result[0] = arg; + return result; +} +// Settings: {"jsdoc":{"mode":"closure"}} + +/** @typedef QDigestNode */ +class A { + /** + * @template {object} T + * @param {(node: QDigestNode) => T} callback + * @returns {T[]} + */ + map(callback) { + /** @type {T[]} */ + let vals; + return vals; + } +} +// Settings: {"jsdoc":{"mode":"typescript"}} + +/** + * @template T + * @param {T} arg + */ +function example(arg) { + + /** @param {T} */ + function inner(x) { + } +} +// Settings: {"jsdoc":{"mode":"typescript"}} + /** * @suppress {visibility} */ function foo () { } // Settings: {"jsdoc":{"mode":"closure"}} + +/** + * @template T + */ +export class Foo { + // cast to T + getType() { + const x = "hello"; + const y = /** @type {T} */ (x); + return y; + } +} +// Settings: {"jsdoc":{"mode":"typescript"}} ```` diff --git a/src/rules/noUndefinedTypes.js b/src/rules/noUndefinedTypes.js index c37e7aede..4392f0547 100644 --- a/src/rules/noUndefinedTypes.js +++ b/src/rules/noUndefinedTypes.js @@ -92,42 +92,35 @@ export default iterateJsdoc(({ }); const ancestorNodes = []; - let currentScope = scopeManager.acquire(node); - while (currentScope && currentScope.block.type !== 'Program') { - ancestorNodes.push(currentScope.block); - currentScope = currentScope.upper; + let currentNode = node; + // No need for Program node? + while (currentNode?.parent) { + ancestorNodes.push(currentNode); + currentNode = currentNode.parent; } + const getTemplateTags = function (ancestorNode) { + const commentNode = getJSDocComment(sourceCode, ancestorNode, settings); + if (!commentNode) { + return []; + } + + const jsdoc = parseComment(commentNode, ''); + + return jsdocUtils.filterTags(jsdoc.tags, (tag) => { + return tag.tag === 'template'; + }); + }; + // `currentScope` may be `null` or `Program`, so in such a case, // we look to present tags instead - let templateTags = ancestorNodes.length ? + const templateTags = ancestorNodes.length ? ancestorNodes.flatMap((ancestorNode) => { - const commentNode = getJSDocComment(sourceCode, ancestorNode, settings); - if (!commentNode) { - return []; - } - - const jsdoc = parseComment(commentNode, ''); - - return jsdocUtils.filterTags(jsdoc.tags, (tag) => { - return tag.tag === 'template'; - }); + return getTemplateTags(ancestorNode); }) : utils.getPresentTags('template'); - const classJsdoc = utils.getClassJsdoc(); - if (classJsdoc?.tags) { - templateTags = templateTags.concat( - classJsdoc.tags - .filter(({ - tag, - }) => { - return tag === 'template'; - }), - ); - } - const closureGenericTypes = templateTags.flatMap((tag) => { return utils.parseClosureTemplateTag(tag); }); diff --git a/test/rules/assertions/noUndefinedTypes.js b/test/rules/assertions/noUndefinedTypes.js index a8675a875..e5d0a16a3 100644 --- a/test/rules/assertions/noUndefinedTypes.js +++ b/test/rules/assertions/noUndefinedTypes.js @@ -1109,6 +1109,66 @@ export default { `, ignoreReadme: true, }, + { + code: ` + /** + * @template T + * @param {T} arg + * @returns {[T]} + */ + function genericFunctionExample(arg) { + const result = /** @type {[T]} */ (new Array()); + result[0] = arg; + return result; + } + `, + settings: { + jsdoc: { + mode: 'closure', + }, + }, + }, + { + code: ` + /** @typedef QDigestNode */ + class A { + /** + * @template {object} T + * @param {(node: QDigestNode) => T} callback + * @returns {T[]} + */ + map(callback) { + /** @type {T[]} */ + let vals; + return vals; + } + } + `, + settings: { + jsdoc: { + mode: 'typescript', + }, + }, + }, + { + code: ` + /** + * @template T + * @param {T} arg + */ + function example(arg) { + + /** @param {T} */ + function inner(x) { + } + } + `, + settings: { + jsdoc: { + mode: 'typescript', + }, + }, + }, { // https://github.com/gajus/eslint-plugin-jsdoc/issues/748 code: ` @@ -1134,5 +1194,26 @@ export default { }, }, }, + { + code: ` + /** + * @template T + */ + export class Foo { + // cast to T + getType() { + const x = "hello"; + const y = /** @type {T} */ (x); + return y; + } + } + `, + parser: require.resolve('@babel/eslint-parser'), + settings: { + jsdoc: { + mode: 'typescript', + }, + }, + }, ], };