Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Breaking: some rules recognize bigint literals (fixes #11803) (#12701)
* update no-magic-numbers to recognize bigint

* update yoda to recognize bigint

* add a no-extend-native test

* update ci.yml temporary (this PR is blocked by #12700)

* add astUtils.isNumericLiteral and use it in some rules

* update no-dupe-class-members

* update no-magic-number to support bigint in options

* update some rules to use getStaticPropertyName

* update quote-props

* revert no-useless-computed-key change

* revert "allowing {type: 'bigint'}" and update no-magic-number

* no-magic-number 'ignores' allows negative bigint
  • Loading branch information
mysticatea authored and kaicataldo committed Jan 17, 2020
1 parent 16a1c1f commit 7350589
Show file tree
Hide file tree
Showing 15 changed files with 206 additions and 51 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.js
Expand Up @@ -19,6 +19,9 @@ module.exports = {
"eslint",
"plugin:eslint-plugin/recommended"
],
parserOptions: {
ecmaVersion: 2020
},
rules: {
"eslint-plugin/consistent-output": "error",
"eslint-plugin/no-deprecated-context-methods": "error",
Expand Down
11 changes: 11 additions & 0 deletions docs/rules/no-magic-numbers.md
Expand Up @@ -56,6 +56,9 @@ var dutyFreePrice = 100,
An array of numbers to ignore. It's set to `[]` by default.
If provided, it must be an `Array`.

The array can contain values of `number` and `string` types.
If it's a string, the text must be parsed as `bigint` literal (e.g., `"100n"`).

Examples of **correct** code for the sample `{ "ignore": [1] }` option:

```js
Expand All @@ -65,6 +68,14 @@ var data = ['foo', 'bar', 'baz'];
var dataLast = data.length && data[data.length - 1];
```

Examples of **correct** code for the sample `{ "ignore": ["1n"] }` option:

```js
/*eslint no-magic-numbers: ["error", { "ignore": ["1n"] }]*/

foo(1n);
```

### ignoreArrayIndexes

A boolean to specify if numbers used as array indexes are considered okay. `false` by default.
Expand Down
3 changes: 1 addition & 2 deletions lib/rules/key-spacing.js
Expand Up @@ -414,8 +414,7 @@ module.exports = {
if (property.computed) {
return sourceCode.getText().slice(key.range[0], key.range[1]);
}

return property.key.name || property.key.value;
return astUtils.getStaticPropertyName(property);
}

/**
Expand Down
10 changes: 3 additions & 7 deletions lib/rules/new-cap.js
Expand Up @@ -9,6 +9,8 @@
// Requirements
//------------------------------------------------------------------------------

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -160,13 +162,7 @@ module.exports = {
let name = "";

if (node.callee.type === "MemberExpression") {
const property = node.callee.property;

if (property.type === "Literal" && (typeof property.value === "string")) {
name = property.value;
} else if (property.type === "Identifier" && !node.callee.computed) {
name = property.name;
}
name = astUtils.getStaticPropertyName(node.callee) || "";
} else {
name = node.callee.name;
}
Expand Down
19 changes: 3 additions & 16 deletions lib/rules/no-dupe-class-members.js
Expand Up @@ -5,6 +5,8 @@

"use strict";

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -53,21 +55,6 @@ module.exports = {
return stateMap[key][isStatic ? "static" : "nonStatic"];
}

/**
* Gets the name text of a given node.
* @param {ASTNode} node A node to get the name.
* @returns {string} The name text of the node.
*/
function getName(node) {
switch (node.type) {
case "Identifier": return node.name;
case "Literal": return String(node.value);

/* istanbul ignore next: syntax error */
default: return "";
}
}

return {

// Initializes the stack of state of member declarations.
Expand All @@ -91,7 +78,7 @@ module.exports = {
return;
}

const name = getName(node.key);
const name = astUtils.getStaticPropertyName(node) || "";
const state = getState(name, node.static);
let isDuplicate = false;

Expand Down
32 changes: 20 additions & 12 deletions lib/rules/no-magic-numbers.js
Expand Up @@ -5,10 +5,24 @@

"use strict";

const { isNumericLiteral } = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/**
* Convert the value to bigint if it's a string. Otherwise return the value as-is.
* @param {bigint|number|string} x The value to normalize.
* @returns {bigint|number} The normalized value.
*/
function normalizeIgnoreValue(x) {
if (typeof x === "string") {
return BigInt(x.slice(0, -1));
}
return x;
}

module.exports = {
meta: {
type: "suggestion",
Expand All @@ -34,7 +48,10 @@ module.exports = {
ignore: {
type: "array",
items: {
type: "number"
anyOf: [
{ type: "number" },
{ type: "string", pattern: "^[+-]?(?:0|[1-9][0-9]*)n$" }
]
},
uniqueItems: true
},
Expand All @@ -56,18 +73,9 @@ module.exports = {
const config = context.options[0] || {},
detectObjects = !!config.detectObjects,
enforceConst = !!config.enforceConst,
ignore = config.ignore || [],
ignore = (config.ignore || []).map(normalizeIgnoreValue),
ignoreArrayIndexes = !!config.ignoreArrayIndexes;

/**
* Returns whether the node is number literal
* @param {Node} node the node literal being evaluated
* @returns {boolean} true if the node is a number literal
*/
function isNumber(node) {
return typeof node.value === "number";
}

/**
* Returns whether the number should be ignored
* @param {number} num the number
Expand Down Expand Up @@ -113,7 +121,7 @@ module.exports = {
Literal(node) {
const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];

if (!isNumber(node)) {
if (!isNumericLiteral(node)) {
return;
}

Expand Down
11 changes: 4 additions & 7 deletions lib/rules/no-useless-computed-key.js
Expand Up @@ -71,14 +71,11 @@ module.exports = {
message: MESSAGE_UNNECESSARY_COMPUTED,
data: { property: sourceCode.getText(key) },
fix(fixer) {
const leftSquareBracket = sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken);
const rightSquareBracket = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken);
const tokensBetween = sourceCode.getTokensBetween(leftSquareBracket, rightSquareBracket, 1);
const leftSquareBracket = sourceCode.getTokenBefore(key, astUtils.isOpeningBracketToken);
const rightSquareBracket = sourceCode.getTokenAfter(key, astUtils.isClosingBracketToken);

if (tokensBetween.slice(0, -1).some((token, index) =>
sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) {

// If there are comments between the brackets and the property name, don't do a fix.
// If there are comments between the brackets and the property name, don't do a fix.
if (sourceCode.commentsExistBetween(leftSquareBracket, rightSquareBracket)) {
return null;
}

Expand Down
7 changes: 4 additions & 3 deletions lib/rules/quote-props.js
Expand Up @@ -8,8 +8,9 @@
// Requirements
//------------------------------------------------------------------------------

const espree = require("espree"),
keywords = require("./utils/keywords");
const espree = require("espree");
const astUtils = require("./utils/ast-utils");
const keywords = require("./utils/keywords");

//------------------------------------------------------------------------------
// Rule Definition
Expand Down Expand Up @@ -177,7 +178,7 @@ module.exports = {
data: { property: key.name },
fix: fixer => fixer.replaceText(key, getQuotedKey(key))
});
} else if (NUMBERS && key.type === "Literal" && typeof key.value === "number") {
} else if (NUMBERS && key.type === "Literal" && astUtils.isNumericLiteral(key)) {
context.report({
node,
message: MESSAGE_NUMERIC,
Expand Down
16 changes: 16 additions & 0 deletions lib/rules/utils/ast-utils.js
Expand Up @@ -884,6 +884,9 @@ module.exports = {
if (node.regex) {
return `/${node.regex.pattern}/${node.regex.flags}`;
}
if (node.bigint) {
return node.bigint;
}

// Otherwise, this is an unknown literal. The function will return null.

Expand Down Expand Up @@ -1014,6 +1017,7 @@ module.exports = {
* 0o5 // false
* 5e0 // false
* '5' // false
* 5n // false
*/
isDecimalInteger(node) {
return node.type === "Literal" && typeof node.value === "number" &&
Expand Down Expand Up @@ -1333,6 +1337,18 @@ module.exports = {
return node.type === "Literal" && node.value === null && !node.regex && !node.bigint;
},

/**
* Check if a given node is a numeric literal or not.
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node is a number or bigint literal.
*/
isNumericLiteral(node) {
return (
node.type === "Literal" &&
(typeof node.value === "number" || Boolean(node.bigint))
);
},

/**
* Determines whether two tokens can safely be placed next to each other without merging into a single token
* @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used.
Expand Down
3 changes: 1 addition & 2 deletions lib/rules/yoda.js
Expand Up @@ -53,8 +53,7 @@ function looksLikeLiteral(node) {
return (node.type === "UnaryExpression" &&
node.operator === "-" &&
node.prefix &&
node.argument.type === "Literal" &&
typeof node.argument.value === "number");
astUtils.isNumericLiteral(node.argument));
}

/**
Expand Down
8 changes: 8 additions & 0 deletions tests/lib/rules/no-extend-native.js
Expand Up @@ -56,6 +56,14 @@ ruleTester.run("no-extend-native", rule, {
data: { builtin: "Object" },
type: "AssignmentExpression"
}]
}, {
code: "BigInt.prototype.p = 0",
env: { es2020: true },
errors: [{
messageId: "unexpected",
data: { builtin: "BigInt" },
type: "AssignmentExpression"
}]
}, {
code: "Function.prototype['p'] = 0",
errors: [{
Expand Down
61 changes: 61 additions & 0 deletions tests/lib/rules/no-magic-numbers.js
Expand Up @@ -75,6 +75,16 @@ ruleTester.run("no-magic-numbers", rule, {
jsx: true
}
}
},
{
code: "f(100n)",
options: [{ ignore: ["100n"] }],
parserOptions: { ecmaVersion: 2020 }
},
{
code: "f(-100n)",
options: [{ ignore: ["-100n"] }],
parserOptions: { ecmaVersion: 2020 }
}
],
invalid: [
Expand All @@ -93,6 +103,26 @@ ruleTester.run("no-magic-numbers", rule, {
{ messageId: "noMagic", data: { raw: "1" } }
]
},
{
code: "var foo = 42n",
options: [{
enforceConst: true
}],
parserOptions: {
ecmaVersion: 2020
},
errors: [{ messageId: "useConst" }]
},
{
code: "var foo = 0n + 1n;",
parserOptions: {
ecmaVersion: 2020
},
errors: [
{ messageId: "noMagic", data: { raw: "0n" } },
{ messageId: "noMagic", data: { raw: "1n" } }
]
},
{
code: "a = a + 5;",
errors: [
Expand Down Expand Up @@ -227,6 +257,37 @@ ruleTester.run("no-magic-numbers", rule, {
{ messageId: "noMagic", data: { raw: "10" }, line: 1 },
{ messageId: "noMagic", data: { raw: "4" }, line: 1 }
]
},
{
code: "f(100n)",
options: [{ ignore: [100] }],
parserOptions: { ecmaVersion: 2020 },
errors: [
{ messageId: "noMagic", data: { raw: "100n" }, line: 1 }
]
},
{
code: "f(-100n)",
options: [{ ignore: ["100n"] }],
parserOptions: { ecmaVersion: 2020 },
errors: [
{ messageId: "noMagic", data: { raw: "-100n" }, line: 1 }
]
},
{
code: "f(100n)",
options: [{ ignore: ["-100n"] }],
parserOptions: { ecmaVersion: 2020 },
errors: [
{ messageId: "noMagic", data: { raw: "100n" }, line: 1 }
]
},
{
code: "f(100)",
options: [{ ignore: ["100n"] }],
errors: [
{ messageId: "noMagic", data: { raw: "100" }, line: 1 }
]
}
]
});
11 changes: 10 additions & 1 deletion tests/lib/rules/no-useless-computed-key.js
Expand Up @@ -40,7 +40,16 @@ ruleTester.run("no-useless-computed-key", rule, {
{ code: "class Foo { ['x']() {} }", options: [{ enforceForClassMembers: false }] },
{ code: "(class { ['x']() {} })", options: [{ enforceForClassMembers: false }] },
{ code: "class Foo { static ['constructor']() {} }", options: [{ enforceForClassMembers: false }] },
{ code: "class Foo { ['prototype']() {} }", options: [{ enforceForClassMembers: false }] }
{ code: "class Foo { ['prototype']() {} }", options: [{ enforceForClassMembers: false }] },

/*
* Well-known browsers throw syntax error bigint literals on property names,
* so, this rule doesn't touch those for now.
*/
{
code: "({ [99999999999999999n]: 0 })",
parserOptions: { ecmaVersion: 2020 }
}
],
invalid: [
{
Expand Down

0 comments on commit 7350589

Please sign in to comment.