diff --git a/src/rules/no-undocumented-throws.js b/src/rules/no-undocumented-throws.js index d55dfa4..bf07066 100644 --- a/src/rules/no-undocumented-throws.js +++ b/src/rules/no-undocumented-throws.js @@ -143,13 +143,23 @@ module.exports = createRule({ const calleeDeclaration = getCalleeDeclaration(services, node); if (!calleeDeclaration) return; - const signature = getCallSignature( - services, - services.tsNodeToESTreeNodeMap.get(calleeDeclaration) - ); + switch (node.type) { + case AST_NODE_TYPES.CallExpression: + case AST_NODE_TYPES.NewExpression: { + if (services.tsNodeToESTreeNodeMap.has(calleeDeclaration)) { + const signature = getCallSignature( + services, + services.tsNodeToESTreeNodeMap.get(calleeDeclaration) + ); - const returnType = signature?.getReturnType(); - if (returnType && isGeneratorLike(returnType)) return; + const returnType = signature?.getReturnType(); + if (returnType && isGeneratorLike(returnType)) return; + } + break; + } + default: + break; + } /** @type {import('typescript').JSDocThrowsTag[]} */ const comments = []; diff --git a/src/utils.js b/src/utils.js index 7090d52..46c92e8 100644 --- a/src/utils.js +++ b/src/utils.js @@ -350,11 +350,14 @@ const getCalleeDeclaration = (services, node) => { const calleeNode = getCallee(node); if (!calleeNode) return null; - const symbol = services - .getTypeAtLocation(calleeNode) - .symbol; + const type = services.getTypeAtLocation(calleeNode); - if (!symbol || !symbol.valueDeclaration) { + if (type.symbol?.valueDeclaration) { + declaration = type.symbol.valueDeclaration; + } else if (type.symbol?.declarations?.length) { + // If there are multiple declarations, we take the first one. + declaration = type.symbol.declarations[0]; + } else { const declarations = services .getSymbolAtLocation(calleeNode) ?.declarations; @@ -363,8 +366,6 @@ const getCalleeDeclaration = (services, node) => { // If there are multiple declarations, we take the first one. declaration = declarations[0]; - } else { - declaration = symbol.valueDeclaration; } } if (!declaration) return null; diff --git a/tests/rules/no-undocumented-throws.test.js b/tests/rules/no-undocumented-throws.test.js index 8191f00..3989fde 100644 --- a/tests/rules/no-undocumented-throws.test.js +++ b/tests/rules/no-undocumented-throws.test.js @@ -462,6 +462,17 @@ ruleTester.run( } `, }, + { + code: ` + function foo() { + const promise = new Promise((resolve, reject) => { + reject(new Error()); + }); + + return promise.then(console.log, console.error); + } + `, + }, { code: ` function foo() { @@ -606,9 +617,6 @@ ruleTester.run( throw new TypeError(); }; - /** - * @throws {Promise} - */ function foo() { const promise = Promise.resolve(); @@ -1791,6 +1799,38 @@ ruleTester.run( { messageId: 'missingThrowsTag' }, ], }, + { + code: ` + function foo() { + return Promise.resolve() + .then(() => { + throw new Error(); + }) + .then(console.log, console.error) + .then(() => { + throw new TypeError(); + }); + } + `, + output: ` + /** + * @throws {Promise} + */ + function foo() { + return Promise.resolve() + .then(() => { + throw new Error(); + }) + .then(console.log, console.error) + .then(() => { + throw new TypeError(); + }); + } + `, + errors: [ + { messageId: 'missingThrowsTag' }, + ], + }, { code: ` function foo() { @@ -3685,6 +3725,304 @@ ruleTester.run( { messageId: 'missingThrowsTag' }, ], }, + { + code: ` + type Result = { + flag: true; + /** + * @throws {TypeError} + */ + method(): void; + } | { + flag: false; + /** + * @throws {RangeError} + */ + method(): void; + }; + + function foo(): Result { + if (Math.random() > 0.5) { + return { + flag: true, + /** + * @throws {TypeError} + */ + method() { + throw new TypeError(); + }, + }; + } else { + return { + flag: false, + /** + * @throws {RangeError} + */ + method() { + throw new RangeError(); + }, + }; + } + } + + function bar() { + const result = foo(); + if (!result.flag) { + result.method(); + } + } + + function baz() { + const result = foo(); + if (result.flag) { + result.method(); + } + } + `, + output: ` + type Result = { + flag: true; + /** + * @throws {TypeError} + */ + method(): void; + } | { + flag: false; + /** + * @throws {RangeError} + */ + method(): void; + }; + + function foo(): Result { + if (Math.random() > 0.5) { + return { + flag: true, + /** + * @throws {TypeError} + */ + method() { + throw new TypeError(); + }, + }; + } else { + return { + flag: false, + /** + * @throws {RangeError} + */ + method() { + throw new RangeError(); + }, + }; + } + } + + /** + * @throws {RangeError} + */ + function bar() { + const result = foo(); + if (!result.flag) { + result.method(); + } + } + + /** + * @throws {TypeError} + */ + function baz() { + const result = foo(); + if (result.flag) { + result.method(); + } + } + `, + errors: [ + { messageId: 'missingThrowsTag' }, + { messageId: 'missingThrowsTag' }, + ], + }, + { + code: ` + function foo() { + if (Math.random() > 0.5) { + return { + flag: true, + /** + * @throws {TypeError} + */ + set value(v) { + throw new TypeError(); + }, + } as const; + } else { + return { + flag: false, + /** + * @throws {RangeError} + */ + set value(v) { + throw new RangeError(); + }, + } as const; + } + } + + function bar() { + const result = foo(); + if (!result.flag) { + result.value = 42; + } + } + + function baz() { + const result = foo(); + if (result.flag) { + result.value = 42; + } + } + `, + output: ` + function foo() { + if (Math.random() > 0.5) { + return { + flag: true, + /** + * @throws {TypeError} + */ + set value(v) { + throw new TypeError(); + }, + } as const; + } else { + return { + flag: false, + /** + * @throws {RangeError} + */ + set value(v) { + throw new RangeError(); + }, + } as const; + } + } + + /** + * @throws {RangeError} + */ + function bar() { + const result = foo(); + if (!result.flag) { + result.value = 42; + } + } + + /** + * @throws {TypeError} + */ + function baz() { + const result = foo(); + if (result.flag) { + result.value = 42; + } + } + `, + errors: [ + { messageId: 'missingThrowsTag' }, + { messageId: 'missingThrowsTag' }, + ], + }, + { + code: ` + function foo() { + if (Math.random() > 0.5) { + return { + flag: true, + /** + * @throws {TypeError} + */ + get value() { + throw new TypeError(); + }, + } as const; + } else { + return { + flag: false, + /** + * @throws {RangeError} + */ + get value() { + throw new RangeError(); + }, + } as const; + } + } + + function bar() { + const result = foo(); + if (!result.flag) { + result.value; + } + } + + function baz() { + const result = foo(); + if (result.flag) { + result.value; + } + } + `, + output: ` + function foo() { + if (Math.random() > 0.5) { + return { + flag: true, + /** + * @throws {TypeError} + */ + get value() { + throw new TypeError(); + }, + } as const; + } else { + return { + flag: false, + /** + * @throws {RangeError} + */ + get value() { + throw new RangeError(); + }, + } as const; + } + } + + /** + * @throws {RangeError} + */ + function bar() { + const result = foo(); + if (!result.flag) { + result.value; + } + } + + /** + * @throws {TypeError} + */ + function baz() { + const result = foo(); + if (result.flag) { + result.value; + } + } + `, + errors: [ + { messageId: 'missingThrowsTag' }, + { messageId: 'missingThrowsTag' }, + ], + }, ], }, );