-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat(eslint-plugin): add no-magic-numbers rule #373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
bradzacher
merged 6 commits into
typescript-eslint:master
from
scottohara:no-magic-numbers
May 8, 2019
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f39cc85
feat(eslint-plugin): add no-magic-numbers rule
scottohara aca6d08
feat(eslint-plugin): update docs and fix nits
scottohara c188361
feat(eslint-plugin) improve schema extension technique
scottohara 63bae67
feat(eslint-plugin): formatting
scottohara acc17f7
Merge branch 'master' into no-magic-numbers
bradzacher 2812291
Merge branch 'master' into no-magic-numbers
bradzacher File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# Disallow Magic Numbers (@typescript-eslint/no-magic-numbers) | ||
|
||
'Magic numbers' are numbers that occur multiple times in code without an explicit meaning. | ||
They should preferably be replaced by named constants. | ||
|
||
## Rule Details | ||
|
||
The `@typescript-eslint/no-magic-numbers` rule extends the `no-magic-numbers` rule from ESLint core, and adds support for handling Typescript specific code that would otherwise trigger the rule. | ||
|
||
See the [ESLint documentation](https://eslint.org/docs/rules/no-magic-numbers) for more details on the `no-magic-numbers` rule. | ||
|
||
## Rule Changes | ||
|
||
```cjson | ||
{ | ||
// note you must disable the base rule as it can report incorrect errors | ||
"no-magic-numbers": "off", | ||
"@typescript-eslint/no-magic-numbers": ["error", { "ignoreNumericLiteralTypes": true }] | ||
} | ||
``` | ||
|
||
In addition to the options supported by the `no-magic-numbers` rule in ESLint core, the rule adds the following options: | ||
|
||
### ignoreNumericLiteralTypes | ||
|
||
A boolean to specify if numbers used in Typescript numeric literal types are considered okay. `false` by default. | ||
|
||
Examples of **incorrect** code for the `{ "ignoreNumericLiteralTypes": false }` option: | ||
|
||
```ts | ||
/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreNumericLiteralTypes": false }]*/ | ||
|
||
type SmallPrimes = 2 | 3 | 5 | 7 | 11; | ||
``` | ||
|
||
Examples of **correct** code for the `{ "ignoreNumericLiteralTypes": true }` option: | ||
|
||
```ts | ||
/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreNumericLiteralTypes": true }]*/ | ||
|
||
type SmallPrimes = 2 | 3 | 5 | 7 | 11; | ||
``` | ||
|
||
<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-magic-numbers.md)</sup> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
/** | ||
* @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js) | ||
* @author Scott O'Hara | ||
*/ | ||
|
||
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; | ||
import baseRule from 'eslint/lib/rules/no-magic-numbers'; | ||
import * as util from '../util'; | ||
import { JSONSchema4 } from 'json-schema'; | ||
|
||
type Options = util.InferOptionsTypeFromRule<typeof baseRule>; | ||
type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>; | ||
|
||
const baseRuleSchema = (baseRule.meta.schema as JSONSchema4[])[0]; | ||
|
||
export default util.createRule<Options, MessageIds>({ | ||
name: 'no-magic-numbers', | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: 'Disallow magic numbers', | ||
category: 'Best Practices', | ||
recommended: false, | ||
}, | ||
// Extend base schema with additional property to ignore TS numeric literal types | ||
schema: [ | ||
{ | ||
...baseRuleSchema, | ||
properties: { | ||
...baseRuleSchema.properties, | ||
ignoreNumericLiteralTypes: { | ||
type: 'boolean', | ||
}, | ||
}, | ||
}, | ||
], | ||
messages: baseRule.meta.messages, | ||
}, | ||
defaultOptions: [ | ||
{ | ||
ignore: [], | ||
ignoreArrayIndexes: false, | ||
enforceConst: false, | ||
detectObjects: false, | ||
ignoreNumericLiteralTypes: false, | ||
}, | ||
], | ||
create(context, [options]) { | ||
const rules = baseRule.create(context); | ||
|
||
/** | ||
* Returns whether the node is number literal | ||
* @param node the node literal being evaluated | ||
* @returns true if the node is a number literal | ||
*/ | ||
function isNumber(node: TSESTree.Literal): boolean { | ||
return typeof node.value === 'number'; | ||
} | ||
|
||
/** | ||
* Checks if the node grandparent is a Typescript type alias declaration | ||
* @param node the node to be validated. | ||
* @returns true if the node grandparent is a Typescript type alias declaration | ||
* @private | ||
*/ | ||
function isGrandparentTSTypeAliasDeclaration(node: TSESTree.Node): boolean { | ||
return node.parent && node.parent.parent | ||
? node.parent.parent.type === AST_NODE_TYPES.TSTypeAliasDeclaration | ||
: false; | ||
} | ||
|
||
/** | ||
* Checks if the node grandparent is a Typescript union type and its parent is a type alias declaration | ||
* @param node the node to be validated. | ||
* @returns true if the node grandparent is a Typescript untion type and its parent is a type alias declaration | ||
* @private | ||
*/ | ||
function isGrandparentTSUnionType(node: TSESTree.Node): boolean { | ||
if ( | ||
node.parent && | ||
node.parent.parent && | ||
node.parent.parent.type === AST_NODE_TYPES.TSUnionType | ||
) { | ||
return isGrandparentTSTypeAliasDeclaration(node.parent); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Checks if the node parent is a Typescript literal type | ||
* @param node the node to be validated. | ||
* @returns true if the node parent is a Typescript literal type | ||
* @private | ||
*/ | ||
function isParentTSLiteralType(node: TSESTree.Node): boolean { | ||
return node.parent | ||
? node.parent.type === AST_NODE_TYPES.TSLiteralType | ||
: false; | ||
} | ||
|
||
/** | ||
* Checks if the node is a valid TypeScript numeric literal type. | ||
* @param node the node to be validated. | ||
* @returns true if the node is a TypeScript numeric literal type. | ||
* @private | ||
*/ | ||
function isTSNumericLiteralType(node: TSESTree.Node): boolean { | ||
// For negative numbers, update the parent node | ||
if ( | ||
node.parent && | ||
node.parent.type === AST_NODE_TYPES.UnaryExpression && | ||
node.parent.operator === '-' | ||
) { | ||
node = node.parent; | ||
} | ||
|
||
// If the parent node is not a TSLiteralType, early return | ||
if (!isParentTSLiteralType(node)) { | ||
return false; | ||
} | ||
|
||
// If the grandparent is a TSTypeAliasDeclaration, ignore | ||
if (isGrandparentTSTypeAliasDeclaration(node)) { | ||
return true; | ||
} | ||
|
||
// If the grandparent is a TSUnionType and it's parent is a TSTypeAliasDeclaration, ignore | ||
if (isGrandparentTSUnionType(node)) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
return { | ||
Literal(node) { | ||
// Check TypeScript specific nodes | ||
if ( | ||
options.ignoreNumericLiteralTypes && | ||
isNumber(node) && | ||
isTSNumericLiteralType(node) | ||
) { | ||
return; | ||
} | ||
|
||
// Let the base rule deal with the rest | ||
rules.Literal(node); | ||
}, | ||
}; | ||
}, | ||
}); |
134 changes: 134 additions & 0 deletions
134
packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import rule from '../../src/rules/no-magic-numbers'; | ||
import { RuleTester } from '../RuleTester'; | ||
|
||
const ruleTester = new RuleTester({ | ||
parser: '@typescript-eslint/parser', | ||
}); | ||
|
||
ruleTester.run('no-magic-numbers', rule, { | ||
valid: [ | ||
{ | ||
code: 'const FOO = 10;', | ||
options: [{ ignoreNumericLiteralTypes: true }], | ||
}, | ||
{ | ||
code: 'type Foo = "bar";', | ||
}, | ||
{ | ||
code: 'type Foo = true;', | ||
}, | ||
{ | ||
code: 'type Foo = 1;', | ||
options: [{ ignoreNumericLiteralTypes: true }], | ||
}, | ||
{ | ||
code: 'type Foo = -1;', | ||
options: [{ ignoreNumericLiteralTypes: true }], | ||
}, | ||
{ | ||
code: 'type Foo = 1 | 2 | 3;', | ||
options: [{ ignoreNumericLiteralTypes: true }], | ||
}, | ||
{ | ||
code: 'type Foo = 1 | -1;', | ||
options: [{ ignoreNumericLiteralTypes: true }], | ||
}, | ||
], | ||
|
||
invalid: [ | ||
{ | ||
code: 'type Foo = 1;', | ||
options: [{ ignoreNumericLiteralTypes: false }], | ||
errors: [ | ||
{ | ||
messageId: 'noMagic', | ||
data: { | ||
raw: '1', | ||
}, | ||
line: 1, | ||
column: 12, | ||
}, | ||
], | ||
}, | ||
{ | ||
code: 'type Foo = -1;', | ||
options: [{ ignoreNumericLiteralTypes: false }], | ||
errors: [ | ||
{ | ||
messageId: 'noMagic', | ||
data: { | ||
raw: '-1', | ||
}, | ||
line: 1, | ||
column: 12, | ||
}, | ||
], | ||
}, | ||
{ | ||
code: 'type Foo = 1 | 2 | 3;', | ||
options: [{ ignoreNumericLiteralTypes: false }], | ||
errors: [ | ||
{ | ||
messageId: 'noMagic', | ||
data: { | ||
raw: '1', | ||
}, | ||
line: 1, | ||
column: 12, | ||
}, | ||
{ | ||
messageId: 'noMagic', | ||
data: { | ||
raw: '2', | ||
}, | ||
line: 1, | ||
column: 16, | ||
}, | ||
{ | ||
messageId: 'noMagic', | ||
data: { | ||
raw: '3', | ||
}, | ||
line: 1, | ||
column: 20, | ||
}, | ||
], | ||
}, | ||
{ | ||
code: 'type Foo = 1 | -1;', | ||
options: [{ ignoreNumericLiteralTypes: false }], | ||
errors: [ | ||
{ | ||
messageId: 'noMagic', | ||
data: { | ||
raw: '1', | ||
}, | ||
line: 1, | ||
column: 12, | ||
}, | ||
{ | ||
messageId: 'noMagic', | ||
data: { | ||
raw: '-1', | ||
}, | ||
line: 1, | ||
column: 16, | ||
}, | ||
], | ||
}, | ||
{ | ||
code: 'interface Foo { bar: 1; }', | ||
options: [{ ignoreNumericLiteralTypes: true }], | ||
errors: [ | ||
{ | ||
messageId: 'noMagic', | ||
data: { | ||
raw: '1', | ||
}, | ||
line: 1, | ||
column: 22, | ||
}, | ||
], | ||
}, | ||
], | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.