Skip to content

Commit 7c34b93

Browse files
authored
Merge pull request #138 from Xvezda/feature/fix-setter-type-infer
Fix setter type inference
2 parents 2b9d88c + 9bbe355 commit 7c34b93

File tree

4 files changed

+170
-18
lines changed

4 files changed

+170
-18
lines changed

src/rules/no-undocumented-throws.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ module.exports = createRule({
157157
}
158158
break;
159159
}
160+
// If getter/setter both exist and is used in assignment,
161+
// only setter throw types are collected.
162+
case AST_NODE_TYPES.MemberExpression:
163+
if (node.parent?.type === AST_NODE_TYPES.AssignmentExpression) {
164+
return;
165+
}
160166
default:
161167
break;
162168
}

src/utils.js

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,44 @@ const getCallee = (node) => {
336336
* @return {import('typescript').Declaration | null}
337337
*/
338338
const getCalleeDeclaration = (services, node) => {
339+
/**
340+
* Return type of setter when assigning
341+
*
342+
* @example
343+
* ```
344+
* foo.bar = 'baz';
345+
* // ^ This can be a setter
346+
* ```
347+
*/
348+
if (node.type === AST_NODE_TYPES.AssignmentExpression) {
349+
/** @type {import('@typescript-eslint/utils').TSESTree.Node | null} */
350+
const calleeNode = getCallee(node);
351+
if (!calleeNode) return null;
352+
353+
const type = services.getTypeAtLocation(calleeNode);
354+
for (
355+
const declaration of
356+
type.symbol?.declarations ??
357+
services
358+
.getSymbolAtLocation(calleeNode)
359+
?.declarations ??
360+
[]
361+
) {
362+
if (!services.tsNodeToESTreeNodeMap.has(declaration)) continue;
363+
364+
const declarationNode =
365+
services.tsNodeToESTreeNodeMap.get(declaration);
366+
367+
const isSetter = isAccessorNode(declarationNode) &&
368+
declarationNode.kind === 'set';
369+
370+
if (isSetter) {
371+
return declaration;
372+
}
373+
}
374+
return null;
375+
}
376+
339377
/** @type {import('typescript').Declaration | null} */
340378
let declaration = null;
341379
if (
@@ -371,24 +409,6 @@ const getCalleeDeclaration = (services, node) => {
371409
if (!declaration) return null;
372410

373411
switch (node.type) {
374-
/**
375-
* Return type of setter when assigning
376-
*
377-
* @example
378-
* ```
379-
* foo.bar = 'baz';
380-
* // ^ This can be a setter
381-
* ```
382-
*/
383-
case AST_NODE_TYPES.AssignmentExpression: {
384-
const declarationNode =
385-
services.tsNodeToESTreeNodeMap.get(declaration);
386-
387-
const isSetter = isAccessorNode(declarationNode) &&
388-
declarationNode.kind === 'set';
389-
390-
return isSetter ? declaration : null;
391-
}
392412
/**
393413
* Return type of getter when accessing
394414
*

tests/rules/check-throws-tag-type.test.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,72 @@ ruleTester.run(
16761676
{ messageId: 'throwTypeMismatch' },
16771677
],
16781678
},
1679+
{
1680+
code: `
1681+
const foo = {
1682+
/**
1683+
* @throws {RangeError}
1684+
*/
1685+
get bar() {
1686+
throw new RangeError('baz');
1687+
},
1688+
/**
1689+
* @throws {TypeError}
1690+
*/
1691+
set bar(value) {
1692+
throw new TypeError('baz');
1693+
},
1694+
};
1695+
1696+
/**
1697+
* @throws {TypeError}
1698+
*/
1699+
function baz() {
1700+
foo.bar;
1701+
}
1702+
1703+
/**
1704+
* @throws {RangeError}
1705+
*/
1706+
function qux() {
1707+
foo.bar = 'quux';
1708+
}
1709+
`,
1710+
output: `
1711+
const foo = {
1712+
/**
1713+
* @throws {RangeError}
1714+
*/
1715+
get bar() {
1716+
throw new RangeError('baz');
1717+
},
1718+
/**
1719+
* @throws {TypeError}
1720+
*/
1721+
set bar(value) {
1722+
throw new TypeError('baz');
1723+
},
1724+
};
1725+
1726+
/**
1727+
* @throws {RangeError}
1728+
*/
1729+
function baz() {
1730+
foo.bar;
1731+
}
1732+
1733+
/**
1734+
* @throws {TypeError}
1735+
*/
1736+
function qux() {
1737+
foo.bar = 'quux';
1738+
}
1739+
`,
1740+
errors: [
1741+
{ messageId: 'throwTypeMismatch' },
1742+
{ messageId: 'throwTypeMismatch' },
1743+
],
1744+
},
16791745
],
16801746
},
16811747
);

tests/rules/no-undocumented-throws.test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4023,6 +4023,66 @@ ruleTester.run(
40234023
{ messageId: 'missingThrowsTag' },
40244024
],
40254025
},
4026+
{
4027+
code: `
4028+
const foo = {
4029+
/**
4030+
* @throws {RangeError}
4031+
*/
4032+
get bar() {
4033+
throw new RangeError('baz');
4034+
},
4035+
/**
4036+
* @throws {TypeError}
4037+
*/
4038+
set bar(value) {
4039+
throw new TypeError('baz');
4040+
},
4041+
};
4042+
4043+
function baz() {
4044+
foo.bar;
4045+
}
4046+
4047+
function qux() {
4048+
foo.bar = 'quux';
4049+
}
4050+
`,
4051+
output: `
4052+
const foo = {
4053+
/**
4054+
* @throws {RangeError}
4055+
*/
4056+
get bar() {
4057+
throw new RangeError('baz');
4058+
},
4059+
/**
4060+
* @throws {TypeError}
4061+
*/
4062+
set bar(value) {
4063+
throw new TypeError('baz');
4064+
},
4065+
};
4066+
4067+
/**
4068+
* @throws {RangeError}
4069+
*/
4070+
function baz() {
4071+
foo.bar;
4072+
}
4073+
4074+
/**
4075+
* @throws {TypeError}
4076+
*/
4077+
function qux() {
4078+
foo.bar = 'quux';
4079+
}
4080+
`,
4081+
errors: [
4082+
{ messageId: 'missingThrowsTag' },
4083+
{ messageId: 'missingThrowsTag' },
4084+
],
4085+
},
40264086
],
40274087
},
40284088
);

0 commit comments

Comments
 (0)