Skip to content

Commit 852fc31

Browse files
authored
fix(eslint-plugin): [no-magic-numbers] handle UnaryExpression for enums (#1415)
1 parent 206c94b commit 852fc31

File tree

2 files changed

+169
-150
lines changed

2 files changed

+169
-150
lines changed

packages/eslint-plugin/src/rules/no-magic-numbers.ts

+117-128
Original file line numberDiff line numberDiff line change
@@ -55,130 +55,6 @@ export default util.createRule<Options, MessageIds>({
5555
create(context, [options]) {
5656
const rules = baseRule.create(context);
5757

58-
/**
59-
* Returns whether the node is number literal
60-
* @param node the node literal being evaluated
61-
* @returns true if the node is a number literal
62-
*/
63-
function isNumber(node: TSESTree.Literal): boolean {
64-
return typeof node.value === 'number';
65-
}
66-
67-
/**
68-
* Checks if the node grandparent is a Typescript type alias declaration
69-
* @param node the node to be validated.
70-
* @returns true if the node grandparent is a Typescript type alias declaration
71-
* @private
72-
*/
73-
function isGrandparentTSTypeAliasDeclaration(node: TSESTree.Node): boolean {
74-
return node.parent && node.parent.parent
75-
? node.parent.parent.type === AST_NODE_TYPES.TSTypeAliasDeclaration
76-
: false;
77-
}
78-
79-
/**
80-
* Checks if the node grandparent is a Typescript union type and its parent is a type alias declaration
81-
* @param node the node to be validated.
82-
* @returns true if the node grandparent is a Typescript union type and its parent is a type alias declaration
83-
* @private
84-
*/
85-
function isGrandparentTSUnionType(node: TSESTree.Node): boolean {
86-
if (
87-
node.parent &&
88-
node.parent.parent &&
89-
node.parent.parent.type === AST_NODE_TYPES.TSUnionType
90-
) {
91-
return isGrandparentTSTypeAliasDeclaration(node.parent);
92-
}
93-
94-
return false;
95-
}
96-
97-
/**
98-
* Checks if the node parent is a Typescript enum member
99-
* @param node the node to be validated.
100-
* @returns true if the node parent is a Typescript enum member
101-
* @private
102-
*/
103-
function isParentTSEnumDeclaration(node: TSESTree.Node): boolean {
104-
return (
105-
typeof node.parent !== 'undefined' &&
106-
node.parent.type === AST_NODE_TYPES.TSEnumMember
107-
);
108-
}
109-
110-
/**
111-
* Checks if the node parent is a Typescript literal type
112-
* @param node the node to be validated.
113-
* @returns true if the node parent is a Typescript literal type
114-
* @private
115-
*/
116-
function isParentTSLiteralType(node: TSESTree.Node): boolean {
117-
return node.parent
118-
? node.parent.type === AST_NODE_TYPES.TSLiteralType
119-
: false;
120-
}
121-
122-
/**
123-
* Checks if the node is a valid TypeScript numeric literal type.
124-
* @param node the node to be validated.
125-
* @returns true if the node is a TypeScript numeric literal type.
126-
* @private
127-
*/
128-
function isTSNumericLiteralType(node: TSESTree.Node): boolean {
129-
// For negative numbers, update the parent node
130-
if (
131-
node.parent &&
132-
node.parent.type === AST_NODE_TYPES.UnaryExpression &&
133-
node.parent.operator === '-'
134-
) {
135-
node = node.parent;
136-
}
137-
138-
// If the parent node is not a TSLiteralType, early return
139-
if (!isParentTSLiteralType(node)) {
140-
return false;
141-
}
142-
143-
// If the grandparent is a TSTypeAliasDeclaration, ignore
144-
if (isGrandparentTSTypeAliasDeclaration(node)) {
145-
return true;
146-
}
147-
148-
// If the grandparent is a TSUnionType and it's parent is a TSTypeAliasDeclaration, ignore
149-
if (isGrandparentTSUnionType(node)) {
150-
return true;
151-
}
152-
153-
return false;
154-
}
155-
156-
/**
157-
* Checks if the node parent is a readonly class property
158-
* @param node the node to be validated.
159-
* @returns true if the node parent is a readonly class property
160-
* @private
161-
*/
162-
function isParentTSReadonlyClassProperty(node: TSESTree.Node): boolean {
163-
if (
164-
node.parent &&
165-
node.parent.type === AST_NODE_TYPES.UnaryExpression &&
166-
['-', '+'].includes(node.parent.operator)
167-
) {
168-
node = node.parent;
169-
}
170-
171-
if (
172-
node.parent &&
173-
node.parent.type === AST_NODE_TYPES.ClassProperty &&
174-
node.parent.readonly
175-
) {
176-
return true;
177-
}
178-
179-
return false;
180-
}
181-
18258
return {
18359
Literal(node): void {
18460
// Check if the node is a TypeScript enum declaration
@@ -189,14 +65,17 @@ export default util.createRule<Options, MessageIds>({
18965
// Check TypeScript specific nodes for Numeric Literal
19066
if (
19167
options.ignoreNumericLiteralTypes &&
192-
isNumber(node) &&
68+
typeof node.value === 'number' &&
19369
isTSNumericLiteralType(node)
19470
) {
19571
return;
19672
}
19773

19874
// Check if the node is a readonly class property
199-
if (isNumber(node) && isParentTSReadonlyClassProperty(node)) {
75+
if (
76+
typeof node.value === 'number' &&
77+
isParentTSReadonlyClassProperty(node)
78+
) {
20079
if (options.ignoreReadonlyClassProperties) {
20180
return;
20281
}
@@ -207,8 +86,10 @@ export default util.createRule<Options, MessageIds>({
20786
let raw = node.raw;
20887

20988
if (
210-
node.parent &&
211-
node.parent.type === AST_NODE_TYPES.UnaryExpression
89+
node.parent?.type === AST_NODE_TYPES.UnaryExpression &&
90+
// the base rule only shows the operator for negative numbers
91+
// https://github.com/eslint/eslint/blob/9dfc8501fb1956c90dc11e6377b4cb38a6bea65d/lib/rules/no-magic-numbers.js#L126
92+
node.parent.operator === '-'
21293
) {
21394
fullNumberNode = node.parent;
21495
raw = `${node.parent.operator}${node.raw}`;
@@ -229,3 +110,111 @@ export default util.createRule<Options, MessageIds>({
229110
};
230111
},
231112
});
113+
114+
/**
115+
* Gets the true parent of the literal, handling prefixed numbers (-1 / +1)
116+
*/
117+
function getLiteralParent(node: TSESTree.Literal): TSESTree.Node | undefined {
118+
if (
119+
node.parent?.type === AST_NODE_TYPES.UnaryExpression &&
120+
['-', '+'].includes(node.parent.operator)
121+
) {
122+
return node.parent.parent;
123+
}
124+
125+
return node.parent;
126+
}
127+
128+
/**
129+
* Checks if the node grandparent is a Typescript type alias declaration
130+
* @param node the node to be validated.
131+
* @returns true if the node grandparent is a Typescript type alias declaration
132+
* @private
133+
*/
134+
function isGrandparentTSTypeAliasDeclaration(node: TSESTree.Node): boolean {
135+
return node.parent?.parent?.type === AST_NODE_TYPES.TSTypeAliasDeclaration;
136+
}
137+
138+
/**
139+
* Checks if the node grandparent is a Typescript union type and its parent is a type alias declaration
140+
* @param node the node to be validated.
141+
* @returns true if the node grandparent is a Typescript union type and its parent is a type alias declaration
142+
* @private
143+
*/
144+
function isGrandparentTSUnionType(node: TSESTree.Node): boolean {
145+
if (node.parent?.parent?.type === AST_NODE_TYPES.TSUnionType) {
146+
return isGrandparentTSTypeAliasDeclaration(node.parent);
147+
}
148+
149+
return false;
150+
}
151+
152+
/**
153+
* Checks if the node parent is a Typescript enum member
154+
* @param node the node to be validated.
155+
* @returns true if the node parent is a Typescript enum member
156+
* @private
157+
*/
158+
function isParentTSEnumDeclaration(node: TSESTree.Literal): boolean {
159+
const parent = getLiteralParent(node);
160+
return parent?.type === AST_NODE_TYPES.TSEnumMember;
161+
}
162+
163+
/**
164+
* Checks if the node parent is a Typescript literal type
165+
* @param node the node to be validated.
166+
* @returns true if the node parent is a Typescript literal type
167+
* @private
168+
*/
169+
function isParentTSLiteralType(node: TSESTree.Node): boolean {
170+
return node.parent?.type === AST_NODE_TYPES.TSLiteralType;
171+
}
172+
173+
/**
174+
* Checks if the node is a valid TypeScript numeric literal type.
175+
* @param node the node to be validated.
176+
* @returns true if the node is a TypeScript numeric literal type.
177+
* @private
178+
*/
179+
function isTSNumericLiteralType(node: TSESTree.Node): boolean {
180+
// For negative numbers, use the parent node
181+
if (
182+
node.parent?.type === AST_NODE_TYPES.UnaryExpression &&
183+
node.parent.operator === '-'
184+
) {
185+
node = node.parent;
186+
}
187+
188+
// If the parent node is not a TSLiteralType, early return
189+
if (!isParentTSLiteralType(node)) {
190+
return false;
191+
}
192+
193+
// If the grandparent is a TSTypeAliasDeclaration, ignore
194+
if (isGrandparentTSTypeAliasDeclaration(node)) {
195+
return true;
196+
}
197+
198+
// If the grandparent is a TSUnionType and it's parent is a TSTypeAliasDeclaration, ignore
199+
if (isGrandparentTSUnionType(node)) {
200+
return true;
201+
}
202+
203+
return false;
204+
}
205+
206+
/**
207+
* Checks if the node parent is a readonly class property
208+
* @param node the node to be validated.
209+
* @returns true if the node parent is a readonly class property
210+
* @private
211+
*/
212+
function isParentTSReadonlyClassProperty(node: TSESTree.Literal): boolean {
213+
const parent = getLiteralParent(node);
214+
215+
if (parent?.type === AST_NODE_TYPES.ClassProperty && parent.readonly) {
216+
return true;
217+
}
218+
219+
return false;
220+
}

0 commit comments

Comments
 (0)