Skip to content

Commit 5bc8d1e

Browse files
committed
refactor(check-types): remove some complexity; document code; better var. name
1 parent 98ed500 commit 5bc8d1e

File tree

1 file changed

+159
-104
lines changed

1 file changed

+159
-104
lines changed

src/rules/checkTypes.js

Lines changed: 159 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,19 @@ const strictNativeTypes = [
2121
'RegExp',
2222
];
2323

24-
const adjustNames = (type, preferred, isGenericMatch, nodeName, node, parentNode) => {
24+
/**
25+
* Adjusts the parent type node `meta` for generic matches (or type node
26+
* `type` for `JsdocTypeAny`) and sets the type node `value`.
27+
*
28+
* @param {string} type The actual type
29+
* @param {string} preferred The preferred type
30+
* @param {boolean} isGenericMatch
31+
* @param {string} typeNodeName
32+
* @param {import('jsdoc-type-pratt-parser/dist/src/index.d.ts').NonTerminalResult} node
33+
* @param {import('jsdoc-type-pratt-parser/dist/src/index.d.ts').NonTerminalResult} parentNode
34+
* @returns {void}
35+
*/
36+
const adjustNames = (type, preferred, isGenericMatch, typeNodeName, node, parentNode) => {
2537
let ret = preferred;
2638
if (isGenericMatch) {
2739
if (preferred === '[]') {
@@ -42,7 +54,7 @@ const adjustNames = (type, preferred, isGenericMatch, nodeName, node, parentNode
4254
ret = preferred.slice(0, -2);
4355
} else if (
4456
parentNode.meta.brackets === 'square' &&
45-
(nodeName === '[]' || nodeName === 'Array')
57+
(typeNodeName === '[]' || typeNodeName === 'Array')
4658
) {
4759
parentNode.meta.brackets = 'angle';
4860
parentNode.meta.dot = false;
@@ -57,7 +69,7 @@ const adjustNames = (type, preferred, isGenericMatch, nodeName, node, parentNode
5769

5870
// For bare pseudo-types like `<>`
5971
if (!ret) {
60-
node.value = nodeName;
72+
node.value = typeNodeName;
6173
}
6274
};
6375

@@ -84,10 +96,20 @@ export default iterateJsdoc(({
8496
exemptTagContexts = [],
8597
} = context.options[0] || {};
8698

87-
const getPreferredTypeInfo = (_type, nodeName, parentNode, property) => {
88-
let hasMatchingPreferredType;
89-
let isGenericMatch;
90-
let typeName = nodeName;
99+
/**
100+
* Gets information about the preferred type: whether there is a matching
101+
* preferred type, what the type is, and whether it is a match to a generic.
102+
*
103+
* @param {string} _type Not currently in use
104+
* @param {string} typeNodeName
105+
* @param {import('jsdoc-type-pratt-parser/dist/src/index.d.ts').NonTerminalResult} parentNode
106+
* @param {string} property
107+
* @returns {[hasMatchingPreferredType: boolean, typeName: string, isGenericMatch: boolean]}
108+
*/
109+
const getPreferredTypeInfo = (_type, typeNodeName, parentNode, property) => {
110+
let hasMatchingPreferredType = false;
111+
let isGenericMatch = false;
112+
let typeName = typeNodeName;
91113
if (Object.keys(preferredTypes).length) {
92114
const isNameOfGeneric = parentNode !== undefined && parentNode.type === 'JsdocTypeGeneric' && property === 'left';
93115
if (unifyParentAndChildTypeChecks || isNameOfGeneric) {
@@ -101,7 +123,7 @@ export default iterateJsdoc(({
101123
'<>',
102124
];
103125
isGenericMatch = checkPostFixes.some((checkPostFix) => {
104-
if (preferredTypes?.[nodeName + checkPostFix] !== undefined) {
126+
if (preferredTypes?.[typeNodeName + checkPostFix] !== undefined) {
105127
typeName += checkPostFix;
106128

107129
return true;
@@ -130,8 +152,8 @@ export default iterateJsdoc(({
130152
}
131153
}
132154

133-
const directNameMatch = preferredTypes?.[nodeName] !== undefined &&
134-
!Object.values(preferredTypes).includes(nodeName);
155+
const directNameMatch = preferredTypes?.[typeNodeName] !== undefined &&
156+
!Object.values(preferredTypes).includes(typeNodeName);
135157
const unifiedSyntaxParentMatch = property && directNameMatch && unifyParentAndChildTypeChecks;
136158
isGenericMatch = isGenericMatch || unifiedSyntaxParentMatch;
137159

@@ -144,6 +166,128 @@ export default iterateJsdoc(({
144166
];
145167
};
146168

169+
/**
170+
* @param {string} typeNodeName
171+
* @param {string} preferred
172+
* @param {import('jsdoc-type-pratt-parser/dist/src/index.d.ts').NonTerminalResult} parentNode
173+
* @param {string[]} invalidTypes
174+
* @returns {string} The `preferred` string, optionally changed
175+
*/
176+
const check = (typeNodeName, preferred, parentNode, invalidTypes) => {
177+
let changedPreferred = preferred;
178+
for (const strictNativeType of strictNativeTypes) {
179+
if (
180+
// Todo: Avoid typescript condition if moving to default typescript
181+
strictNativeType === 'object' && mode === 'typescript' &&
182+
(
183+
// This is not set to remap with exact type match (e.g.,
184+
// `object: 'Object'`), so can ignore (including if circular)
185+
!preferredTypes?.[typeNodeName] ||
186+
// Although present on `preferredTypes` for remapping, this is a
187+
// parent object without a parent match (and not
188+
// `unifyParentAndChildTypeChecks`) and we don't want
189+
// `object<>` given TypeScript issue https://github.com/microsoft/TypeScript/issues/20555
190+
parentNode?.elements.length && (
191+
parentNode?.left.type === 'JsdocTypeName' &&
192+
parentNode?.left.value === 'Object'
193+
)
194+
)
195+
) {
196+
continue;
197+
}
198+
199+
if (strictNativeType !== typeNodeName &&
200+
strictNativeType.toLowerCase() === typeNodeName.toLowerCase() &&
201+
202+
// Don't report if user has own map for a strict native type
203+
(!preferredTypes || preferredTypes?.[strictNativeType] === undefined)
204+
) {
205+
changedPreferred = strictNativeType;
206+
invalidTypes.push([
207+
typeNodeName, changedPreferred,
208+
]);
209+
break;
210+
}
211+
}
212+
213+
return changedPreferred;
214+
};
215+
216+
/**
217+
* Collect invalid type info.
218+
*
219+
* @param {string} type
220+
* @param {string} value
221+
* @param {string} tagName
222+
* @param {string} property
223+
* @param {import('jsdoc-type-pratt-parser/dist/src/index.d.ts').NonTerminalResult} node
224+
* @param {import('jsdoc-type-pratt-parser/dist/src/index.d.ts').NonTerminalResult} parentNode
225+
* @param {string[]} invalidTypes
226+
* @returns {void}
227+
*/
228+
const getInvalidTypes = (type, value, tagName, property, node, parentNode, invalidTypes) => {
229+
let typeNodeName = type === 'JsdocTypeAny' ? '*' : value;
230+
231+
const [
232+
hasMatchingPreferredType,
233+
typeName,
234+
isGenericMatch,
235+
] = getPreferredTypeInfo(type, typeNodeName, parentNode, property);
236+
237+
let preferred;
238+
let types;
239+
if (hasMatchingPreferredType) {
240+
const preferredSetting = preferredTypes[typeName];
241+
typeNodeName = typeName === '[]' ? typeName : typeNodeName;
242+
243+
if (!preferredSetting) {
244+
invalidTypes.push([
245+
typeNodeName,
246+
]);
247+
} else if (typeof preferredSetting === 'string') {
248+
preferred = preferredSetting;
249+
invalidTypes.push([
250+
typeNodeName, preferred,
251+
]);
252+
} else if (typeof preferredSetting === 'object') {
253+
preferred = preferredSetting?.replacement;
254+
invalidTypes.push([
255+
typeNodeName,
256+
preferred,
257+
preferredSetting?.message,
258+
]);
259+
} else {
260+
utils.reportSettings(
261+
'Invalid `settings.jsdoc.preferredTypes`. Values must be falsy, a string, or an object.',
262+
);
263+
264+
return;
265+
}
266+
} else if (Object.entries(structuredTags).some(([
267+
tag,
268+
{
269+
type: typs,
270+
},
271+
]) => {
272+
types = typs;
273+
274+
return tag === tagName &&
275+
Array.isArray(types) &&
276+
!types.includes(typeNodeName);
277+
})) {
278+
invalidTypes.push([
279+
typeNodeName, types,
280+
]);
281+
} else if (!noDefaults && type === 'JsdocTypeName') {
282+
preferred = check(typeNodeName, preferred, parentNode, invalidTypes);
283+
}
284+
285+
// For fixer
286+
if (preferred) {
287+
adjustNames(type, preferred, isGenericMatch, typeNodeName, node, parentNode);
288+
}
289+
};
290+
147291
for (const jsdocTag of jsdocTagsWithPossibleType) {
148292
const invalidTypes = [];
149293
let typeAst;
@@ -156,7 +300,6 @@ export default iterateJsdoc(({
156300

157301
const tagName = jsdocTag.tag;
158302

159-
// eslint-disable-next-line complexity -- To refactor
160303
traverse(typeAst, (node, parentNode, property) => {
161304
const {
162305
type,
@@ -168,104 +311,16 @@ export default iterateJsdoc(({
168311
return;
169312
}
170313

171-
let nodeName = type === 'JsdocTypeAny' ? '*' : value;
172-
173-
const [
174-
hasMatchingPreferredType,
175-
typeName,
176-
isGenericMatch,
177-
] = getPreferredTypeInfo(type, nodeName, parentNode, property);
178-
179-
let preferred;
180-
let types;
181-
if (hasMatchingPreferredType) {
182-
const preferredSetting = preferredTypes[typeName];
183-
nodeName = typeName === '[]' ? typeName : nodeName;
184-
185-
if (!preferredSetting) {
186-
invalidTypes.push([
187-
nodeName,
188-
]);
189-
} else if (typeof preferredSetting === 'string') {
190-
preferred = preferredSetting;
191-
invalidTypes.push([
192-
nodeName, preferred,
193-
]);
194-
} else if (typeof preferredSetting === 'object') {
195-
preferred = preferredSetting?.replacement;
196-
invalidTypes.push([
197-
nodeName,
198-
preferred,
199-
preferredSetting?.message,
200-
]);
201-
} else {
202-
utils.reportSettings(
203-
'Invalid `settings.jsdoc.preferredTypes`. Values must be falsy, a string, or an object.',
204-
);
205-
206-
return;
207-
}
208-
} else if (Object.entries(structuredTags).some(([
209-
tag,
210-
{
211-
type: typs,
212-
},
213-
]) => {
214-
types = typs;
215-
216-
return tag === tagName &&
217-
Array.isArray(types) &&
218-
!types.includes(nodeName);
219-
})) {
220-
invalidTypes.push([
221-
nodeName, types,
222-
]);
223-
} else if (!noDefaults && type === 'JsdocTypeName') {
224-
for (const strictNativeType of strictNativeTypes) {
225-
if (
226-
// Todo: Avoid typescript condition if moving to default typescript
227-
strictNativeType === 'object' && mode === 'typescript' &&
228-
(
229-
// This is not set to remap with exact type match (e.g.,
230-
// `object: 'Object'`), so can ignore (including if circular)
231-
!preferredTypes?.[nodeName] ||
232-
// Although present on `preferredTypes` for remapping, this is a
233-
// parent object without a parent match (and not
234-
// `unifyParentAndChildTypeChecks`) and we don't want
235-
// `object<>` given TypeScript issue https://github.com/microsoft/TypeScript/issues/20555
236-
parentNode?.elements.length && (
237-
parentNode?.left.type === 'JsdocTypeName' &&
238-
parentNode?.left.value === 'Object'
239-
)
240-
)
241-
) {
242-
continue;
243-
}
244-
245-
if (strictNativeType !== nodeName &&
246-
strictNativeType.toLowerCase() === nodeName.toLowerCase() &&
247-
248-
// Don't report if user has own map for a strict native type
249-
(!preferredTypes || preferredTypes?.[strictNativeType] === undefined)
250-
) {
251-
preferred = strictNativeType;
252-
invalidTypes.push([
253-
nodeName, preferred,
254-
]);
255-
break;
256-
}
257-
}
258-
}
259-
260-
// For fixer
261-
if (preferred) {
262-
adjustNames(type, preferred, isGenericMatch, nodeName, node, parentNode);
263-
}
314+
getInvalidTypes(type, value, tagName, property, node, parentNode, invalidTypes);
264315
});
265316

266317
if (invalidTypes.length) {
267318
const fixedType = stringify(typeAst);
268319

320+
/**
321+
* @param {any} fixer The ESLint fixer
322+
* @returns {string}
323+
*/
269324
const fix = (fixer) => {
270325
return fixer.replaceText(
271326
jsdocNode,

0 commit comments

Comments
 (0)