Skip to content

Commit 894f1d4

Browse files
committed
Refactor
1 parent ef81384 commit 894f1d4

File tree

3 files changed

+51
-36
lines changed

3 files changed

+51
-36
lines changed

src/rules/no-implicit-propagation.js

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const {
1111
getJSDocThrowsTagTypes,
1212
getDeclarationTSNodeOfESTreeNode,
1313
toFlattenedTypeArray,
14-
isTypesAssignableTo,
14+
groupTypesByCompatibility,
1515
findClosestFunctionNode,
1616
findNodeToComment,
1717
createInsertJSDocBeforeFixer,
@@ -178,29 +178,14 @@ module.exports = createRule({
178178

179179
const callerThrowsTypes = toFlattenedTypeArray(getJSDocThrowsTagTypes(checker, callerDeclarationTSNode));
180180

181-
if (
182-
isTypesAssignableTo(services.program, calleeThrowsTypes, callerThrowsTypes)
183-
) {
181+
const typeGroups = groupTypesByCompatibility(services.program, calleeThrowsTypes, callerThrowsTypes);
182+
if (!typeGroups.incompatible) {
184183
return;
185184
}
186185

187186
const lastThrowsTypeNode = getLast(callerThrowsTypeNodes);
188187
if (!lastThrowsTypeNode) return;
189188

190-
const notAssignableThrows = calleeThrowsTypes
191-
.filter((calleeType) => !callerThrowsTypes
192-
.some((callerType) => {
193-
if (
194-
utils.isErrorLike(services.program, callerType) &&
195-
utils.isErrorLike(services.program, calleeType)
196-
) {
197-
return utils.typeIsOrHasBaseType(calleeType, callerType);
198-
}
199-
return checker.isTypeAssignableTo(calleeType, callerType);
200-
}));
201-
202-
if (!notAssignableThrows.length) return;
203-
204189
context.report({
205190
node,
206191
messageId: 'throwTypeMismatch',
@@ -224,11 +209,14 @@ module.exports = createRule({
224209
jsdocString
225210
);
226211

212+
if (!typeGroups.incompatible) {
213+
return null;
214+
}
227215
return fixer.replaceTextRange(
228216
[callerJSDocTSNode.getStart(), callerJSDocTSNode.getEnd()],
229217
appendThrowsTags(
230218
callerJSDocTSNode.getFullText(),
231-
notAssignableThrows
219+
typeGroups.incompatible,
232220
)
233221
);
234222
}

src/rules/no-undocumented-throws.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const {
1313
getOptionsFromContext,
1414
getJSDocThrowsTags,
1515
getJSDocThrowsTagTypes,
16-
isTypesAssignableTo,
16+
groupTypesByCompatibility,
1717
findClosestFunctionNode,
1818
findNodeToComment,
1919
findIdentifierDeclaration,
@@ -113,7 +113,12 @@ module.exports = createRule({
113113
.map(t => node.async ? checker.getAwaitedType(t) : t)
114114
.filter(t => !!t);
115115

116-
if (isTypesAssignableTo(services.program, throwTypes, throwsTagTypes)) return;
116+
const typeGroups = groupTypesByCompatibility(
117+
services.program,
118+
throwTypes,
119+
throwsTagTypes,
120+
);
121+
if (!typeGroups.incompatible) return;
117122

118123
const lastTagtypeNode = getLast(throwsTagTypeNodes);
119124
if (!lastTagtypeNode) return;

src/utils.js

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@ const { ESLintUtils, AST_NODE_TYPES } = require('@typescript-eslint/utils');
33
const utils = require('@typescript-eslint/type-utils');
44
const ts = require('typescript');
55

6+
// Object.groupBy clone
7+
/**
8+
* Groups an array of objects by a specified key or function.
9+
* @template T
10+
* @template {string} K
11+
* @param {T[]} arr - The array to group.
12+
* @param {((item: T) => K)} key
13+
* @return {Record<K, T[] | undefined>}
14+
*/
15+
const groupBy = (arr, key) => {
16+
return arr.reduce((acc, item) => {
17+
const groupKey = key(item);
18+
if (!acc[groupKey]) {
19+
acc[groupKey] = [];
20+
}
21+
acc[groupKey].push(item);
22+
return acc;
23+
}, /** @type {Record<string, T[]>} */(Object.create(null)));
24+
};
25+
626
/**
727
* @template {unknown} T
828
* @param {Readonly<T[]>} arr
@@ -173,23 +193,25 @@ const toFlattenedTypeArray = (types) =>
173193
* @param {import('@typescript-eslint/utils').ParserServicesWithTypeInformation['program']} program
174194
* @param {import('typescript').Type[]} source
175195
* @param {import('typescript').Type[]} target
176-
* @returns {boolean}
196+
* @returns {{ compatible?: import('typescript').Type[]; incompatible?: import('typescript').Type[] }}
177197
*/
178-
const isTypesAssignableTo = (program, source, target) => {
198+
const groupTypesByCompatibility = (program, source, target) => {
179199
const checker = program.getTypeChecker();
180-
return source
181-
.every(sourceType =>
182-
target
183-
.some(targetType => {
184-
if (
185-
utils.isErrorLike(program, sourceType) &&
186-
utils.isErrorLike(program, targetType)
187-
) {
188-
return utils.typeIsOrHasBaseType(sourceType, targetType);
189-
}
190-
return checker.isTypeAssignableTo(sourceType, targetType);
191-
})
200+
201+
return groupBy(source, sourceType => {
202+
const isCompatible = target.some(targetType => {
203+
if (
204+
utils.isErrorLike(program, sourceType) &&
205+
utils.isErrorLike(program, targetType)
206+
) {
207+
return utils.typeIsOrHasBaseType(sourceType, targetType);
208+
}
209+
return checker.isTypeAssignableTo(sourceType, targetType);
210+
});
211+
return /** @type {'compatible'|'incompatible'} */(
212+
isCompatible ? 'compatible' : 'incompatible'
192213
);
214+
})
193215
}
194216

195217
/**
@@ -399,7 +421,7 @@ module.exports = {
399421
getJSDocThrowsTags,
400422
getJSDocThrowsTagTypes,
401423
toFlattenedTypeArray,
402-
isTypesAssignableTo,
424+
groupTypesByCompatibility,
403425
findClosestFunctionNode,
404426
findNodeToComment,
405427
findIdentifierDeclaration,

0 commit comments

Comments
 (0)