Skip to content
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

Breaking: some rules recognize bigint literals (fixes #11803) #12701

Merged
merged 14 commits into from Jan 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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