Skip to content

Commit 3ccd79c

Browse files
authored
feat(eslint-plugin): [no-explicit-any] suggest to replace keyof any with PropertyKey (#11032)
* propertykey for keyof any implementation * simplify property key fixer * updated tests and refactored code * removed unused imports * inline suggestion objects and fix type annotation
1 parent 128d95b commit 3ccd79c

File tree

2 files changed

+1730
-854
lines changed

2 files changed

+1730
-854
lines changed

packages/eslint-plugin/src/rules/no-explicit-any.ts

+54-17
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ export type Options = [
1010
ignoreRestArgs?: boolean;
1111
},
1212
];
13-
export type MessageIds = 'suggestNever' | 'suggestUnknown' | 'unexpectedAny';
13+
export type MessageIds =
14+
| 'suggestNever'
15+
| 'suggestPropertyKey'
16+
| 'suggestUnknown'
17+
| 'unexpectedAny';
1418

1519
export default createRule<Options, MessageIds>({
1620
name: 'no-explicit-any',
@@ -25,6 +29,8 @@ export default createRule<Options, MessageIds>({
2529
messages: {
2630
suggestNever:
2731
"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of.",
32+
suggestPropertyKey:
33+
'Use `PropertyKey` instead, this is more explicit than `keyof any`.',
2834
suggestUnknown:
2935
'Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct.',
3036
unexpectedAny: 'Unexpected any. Specify a different type.',
@@ -170,8 +176,35 @@ export default createRule<Options, MessageIds>({
170176
);
171177
}
172178

179+
/**
180+
* Checks if the node is within a keyof any expression
181+
* @param node the node to be validated.
182+
* @returns true if the node is within a keyof any expression, false otherwise
183+
* @private
184+
*/
185+
function isNodeWithinKeyofAny(node: TSESTree.TSAnyKeyword): boolean {
186+
return (
187+
node.parent.type === AST_NODE_TYPES.TSTypeOperator &&
188+
node.parent.operator === 'keyof'
189+
);
190+
}
191+
192+
/**
193+
* Creates a fixer that replaces a keyof any with PropertyKey
194+
* @param node the node to be fixed.
195+
* @returns a function that will fix the node.
196+
* @private
197+
*/
198+
function createPropertyKeyFixer(node: TSESTree.TSAnyKeyword) {
199+
return (fixer: TSESLint.RuleFixer) => {
200+
return fixer.replaceText(node.parent, 'PropertyKey');
201+
};
202+
}
203+
173204
return {
174205
TSAnyKeyword(node): void {
206+
const isKeyofAny = isNodeWithinKeyofAny(node);
207+
175208
if (ignoreRestArgs && isNodeDescendantOfRestElementInFunction(node)) {
176209
return;
177210
}
@@ -181,25 +214,29 @@ export default createRule<Options, MessageIds>({
181214
suggest: TSESLint.ReportSuggestionArray<MessageIds> | null;
182215
} = {
183216
fix: null,
184-
suggest: [
185-
{
186-
messageId: 'suggestUnknown',
187-
fix(fixer): TSESLint.RuleFix {
188-
return fixer.replaceText(node, 'unknown');
189-
},
190-
},
191-
{
192-
messageId: 'suggestNever',
193-
fix(fixer): TSESLint.RuleFix {
194-
return fixer.replaceText(node, 'never');
195-
},
196-
},
197-
],
217+
suggest: isKeyofAny
218+
? [
219+
{
220+
messageId: 'suggestPropertyKey',
221+
fix: createPropertyKeyFixer(node),
222+
},
223+
]
224+
: [
225+
{
226+
messageId: 'suggestUnknown',
227+
fix: fixer => fixer.replaceText(node, 'unknown'),
228+
},
229+
{
230+
messageId: 'suggestNever',
231+
fix: fixer => fixer.replaceText(node, 'never'),
232+
},
233+
],
198234
};
199235

200236
if (fixToUnknown) {
201-
fixOrSuggest.fix = (fixer): TSESLint.RuleFix =>
202-
fixer.replaceText(node, 'unknown');
237+
fixOrSuggest.fix = isKeyofAny
238+
? createPropertyKeyFixer(node)
239+
: fixer => fixer.replaceText(node, 'unknown');
203240
}
204241

205242
context.report({

0 commit comments

Comments
 (0)