Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/rules/no-undocumented-throws.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ module.exports = createRule({
}
break;
}
// If getter/setter both exist and is used in assignment,
// only setter throw types are collected.
case AST_NODE_TYPES.MemberExpression:
if (node.parent?.type === AST_NODE_TYPES.AssignmentExpression) {
return;
}
default:
break;
}
Expand Down
56 changes: 38 additions & 18 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,44 @@ const getCallee = (node) => {
* @return {import('typescript').Declaration | null}
*/
const getCalleeDeclaration = (services, node) => {
/**
* Return type of setter when assigning
*
* @example
* ```
* foo.bar = 'baz';
* // ^ This can be a setter
* ```
*/
if (node.type === AST_NODE_TYPES.AssignmentExpression) {
/** @type {import('@typescript-eslint/utils').TSESTree.Node | null} */
const calleeNode = getCallee(node);
if (!calleeNode) return null;

const type = services.getTypeAtLocation(calleeNode);
for (
const declaration of
type.symbol?.declarations ??
services
.getSymbolAtLocation(calleeNode)
?.declarations ??
[]
) {
if (!services.tsNodeToESTreeNodeMap.has(declaration)) continue;

const declarationNode =
services.tsNodeToESTreeNodeMap.get(declaration);

const isSetter = isAccessorNode(declarationNode) &&
declarationNode.kind === 'set';

if (isSetter) {
return declaration;
}
}
return null;
}

/** @type {import('typescript').Declaration | null} */
let declaration = null;
if (
Expand Down Expand Up @@ -371,24 +409,6 @@ const getCalleeDeclaration = (services, node) => {
if (!declaration) return null;

switch (node.type) {
/**
* Return type of setter when assigning
*
* @example
* ```
* foo.bar = 'baz';
* // ^ This can be a setter
* ```
*/
case AST_NODE_TYPES.AssignmentExpression: {
const declarationNode =
services.tsNodeToESTreeNodeMap.get(declaration);

const isSetter = isAccessorNode(declarationNode) &&
declarationNode.kind === 'set';

return isSetter ? declaration : null;
}
/**
* Return type of getter when accessing
*
Expand Down
66 changes: 66 additions & 0 deletions tests/rules/check-throws-tag-type.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1676,6 +1676,72 @@ ruleTester.run(
{ messageId: 'throwTypeMismatch' },
],
},
{
code: `
const foo = {
/**
* @throws {RangeError}
*/
get bar() {
throw new RangeError('baz');
},
/**
* @throws {TypeError}
*/
set bar(value) {
throw new TypeError('baz');
},
};

/**
* @throws {TypeError}
*/
function baz() {
foo.bar;
}

/**
* @throws {RangeError}
*/
function qux() {
foo.bar = 'quux';
}
`,
output: `
const foo = {
/**
* @throws {RangeError}
*/
get bar() {
throw new RangeError('baz');
},
/**
* @throws {TypeError}
*/
set bar(value) {
throw new TypeError('baz');
},
};

/**
* @throws {RangeError}
*/
function baz() {
foo.bar;
}

/**
* @throws {TypeError}
*/
function qux() {
foo.bar = 'quux';
}
`,
errors: [
{ messageId: 'throwTypeMismatch' },
{ messageId: 'throwTypeMismatch' },
],
},
],
},
);
Expand Down
60 changes: 60 additions & 0 deletions tests/rules/no-undocumented-throws.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4023,6 +4023,66 @@ ruleTester.run(
{ messageId: 'missingThrowsTag' },
],
},
{
code: `
const foo = {
/**
* @throws {RangeError}
*/
get bar() {
throw new RangeError('baz');
},
/**
* @throws {TypeError}
*/
set bar(value) {
throw new TypeError('baz');
},
};

function baz() {
foo.bar;
}

function qux() {
foo.bar = 'quux';
}
`,
output: `
const foo = {
/**
* @throws {RangeError}
*/
get bar() {
throw new RangeError('baz');
},
/**
* @throws {TypeError}
*/
set bar(value) {
throw new TypeError('baz');
},
};

/**
* @throws {RangeError}
*/
function baz() {
foo.bar;
}

/**
* @throws {TypeError}
*/
function qux() {
foo.bar = 'quux';
}
`,
errors: [
{ messageId: 'missingThrowsTag' },
{ messageId: 'missingThrowsTag' },
],
},
],
},
);