Repository: eslint/eslint
Pull request #12655 (2019-12-11 01:48:49)

Fix: prefer-numeric-literals doesn't check types of literal arguments
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[X] Bug fix

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

This PR can produce only fewer warnings in `prefer-numeric-literals`.

From the documentation:

> This rule disallows calls to parseInt() or Number.parseInt() if called with two arguments: a string; and a radix option of 2 (binary), 8 (octal), or 16 (hexadecimal).

This PR fixes the following:

1. The rule didn't check the type of the first literal argument.

It did check if it's a `Literal`. Now it also checks if it's a `string` literal.

This probably didn't cause some notable problems, but it isn't by the spec (which is explicit about the first argument), and it might be unwanted in some cases.

```js
/* eslint prefer-numeric-literals: error */

parseInt(1e8, 2); // fixed to 0b100000000;
```

[Demo Link](https://eslint.org/demo#eyJ0ZXh0IjoiLyogZXNsaW50IHByZWZlci1udW1lcmljLWxpdGVyYWxzOiBlcnJvciAqL1xuXG5wYXJzZUludCgxZTgsIDIpO1xuXG5cbiIsIm9wdGlvbnMiOnsicGFyc2VyT3B0aW9ucyI6eyJlY21hVmVyc2lvbiI6MTEsInNvdXJjZVR5cGUiOiJzY3JpcHQiLCJlY21hRmVhdHVyZXMiOnt9fSwicnVsZXMiOnt9LCJlbnYiOnt9fX0=)

2. The rule didn't check the type of the second literal argument.

The only check was whether the literal's `value`, when coerced to string, exists as a key in the object map, so it ended up targeting the following radix arguments:

`2`, `8`, `16`, `"2"`, `"8"`, `"16"`, `2n`, `8n`, `16n`

It's changed now to target only `number` arguments `2`, `8`, and `16`.

Targeting `string` radix arguments is inconsistent with the `radix` rule, which reports these as invalid ([Demo Link](https://eslint.org/demo#eyJ0ZXh0IjoiLyogZXNsaW50IHJhZGl4OiBlcnJvciAqL1xuXG5wYXJzZUludCgnMTEnLCAnMicpO1xuXG5cbiIsIm9wdGlvbnMiOnsicGFyc2VyT3B0aW9ucyI6eyJlY21hVmVyc2lvbiI6MTEsInNvdXJjZVR5cGUiOiJzY3JpcHQiLCJlY21hRmVhdHVyZXMiOnt9fSwicnVsZXMiOnt9LCJlbnYiOnt9fX0=)).

Bigint radix argument causes ESLint to crash when the rule tries to evaluate the expression:

```js
/* eslint prefer-numeric-literals: error */

parseInt('11', 2n); // TypeError: Cannot convert a BigInt value to a number.
```

[Demo Link](https://eslint.org/demo#eyJ0ZXh0IjoiLyogZXNsaW50IHByZWZlci1udW1lcmljLWxpdGVyYWxzOiBlcnJvciAqL1xuXG5wYXJzZUludCgnMTEnLCAybik7Iiwib3B0aW9ucyI6eyJwYXJzZXJPcHRpb25zIjp7ImVjbWFWZXJzaW9uIjoxMSwic291cmNlVHlwZSI6InNjcmlwdCIsImVjbWFGZWF0dXJlcyI6e319LCJydWxlcyI6e30sImVudiI6e319fQ==)

3. The rule used objects as maps.

[Demo Link](https://eslint.org/demo#eyJ0ZXh0IjoiLyogZXNsaW50IHByZWZlci1udW1lcmljLWxpdGVyYWxzOiBlcnJvciAqL1xuXG5wYXJzZUludCgnMTEnLCAndG9TdHJpbmcnKTtcblxuXG4iLCJvcHRpb25zIjp7InBhcnNlck9wdGlvbnMiOnsiZWNtYVZlcnNpb24iOjExLCJzb3VyY2VUeXBlIjoic2NyaXB0IiwiZWNtYUZlYXR1cmVzIjp7fX0sInJ1bGVzIjp7fSwiZW52Ijp7fX19)

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

The rule now targets only `parseInt` and `Number.parseInt` that have a `string` literal as the first argument and a `number` literal as the second argument.

Also, converted objects used as maps to `Map`.

**Is there anything you'd like reviewers to focus on?**





Patch:
diff --git a/.azure-pipelines/job.yml b/.azure-pipelines/job.yml
deleted file mode 100644
index 2f8a55373..000000000
--- a/.azure-pipelines/job.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-parameters:
-  name: ""
-  displayName: ""
-  vmImage: ""
-  nodeVersion: ""
-
-jobs:
-  - job: ${{parameters.name}}
-    displayName: ${{parameters.displayName}}
-    pool:
-      vmImage: ${{parameters.vmImage}}
-    steps:
-      - task: NodeTool@0
-        displayName: Install Node.js
-        inputs:
-          versionSpec: ${{parameters.nodeVersion}}
-
-      - script: npm install
-        displayName: Install Packages
-
-      - script: npm test
-        displayName: Test
-
-      - task: PublishTestResults@2
-        displayName: Process Test Results
-        condition: succeededOrFailed()
-        inputs:
-          testRunner: JUnit
-          testResultsFiles: $(System.DefaultWorkingDirectory)/test-results.xml
-
-      - task: PublishCodeCoverageResults@1
-        displayName: Process Coverage Results
-        condition: succeededOrFailed()
-        inputs: 
-          codeCoverageTool: Cobertura
-          summaryFileLocation: $(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml
-          reportDirectory: $(System.DefaultWorkingDirectory)/coverage
diff --git a/Makefile.js b/Makefile.js
index 1fcfecb42..2dc8547f3 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -560,7 +560,7 @@ target.mocha = () => {
 target.karma = () => {
     echo("Running unit tests on browsers");
 
-    target.webpack();
+    target.webpack("production");
 
     const browserFileLintOutput = new CLIEngine({
         useEslintrc: false,
@@ -775,16 +775,7 @@ target.gensite = function(prereleaseVersion) {
     echo("> Removing the temporary directory (Step 12)");
     rm("-rf", TEMP_DIR);
 
-    // 13. Update demos, but only for non-prereleases
-    if (!prereleaseVersion) {
-        echo("> Updating the demos (Step 13)");
-        target.webpack("production");
-        cp("-f", "build/eslint.js", `${SITE_DIR}src/js/eslint.js`);
-    } else {
-        echo("> Skipped updating the demos (Step 13)");
-    }
-
-    // 14. Create Example Formatter Output Page
+    // 13. Create Example Formatter Output Page
     echo("> Creating the formatter examples (Step 14)");
     generateFormatterExamples(getFormatterResults(), prereleaseVersion);
 
diff --git a/README.md b/README.md
index 81a4a2eaa..0cde09fc6 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
 [![NPM version][npm-image]][npm-url]
-[![Build Status](https://dev.azure.com/eslint/eslint/_apis/build/status/eslint.eslint?branchName=master)](https://dev.azure.com/eslint/eslint/_build/latest?definitionId=1&branchName=master)
 [![Build Status](https://github.com/eslint/eslint/workflows/CI/badge.svg)](https://github.com/eslint/eslint/actions)
 [![Downloads][downloads-image]][downloads-url]
 [![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=282608)](https://www.bountysource.com/trackers/282608-eslint?utm_source=282608&utm_medium=shield&utm_campaign=TRACKER_BADGE)
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644
index 13a2f38c4..000000000
--- a/azure-pipelines.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-trigger:
-  - master
-
-jobs:
-  - template: .azure-pipelines/job.yml
-    parameters:
-      name: test_on_linux_node12
-      displayName: Test on Node.js 12 (Linux)
-      vmImage: Ubuntu-16.04
-      nodeVersion: 12.x
-
-  - template: .azure-pipelines/job.yml
-    parameters:
-      name: test_on_linux_node10
-      displayName: Test on Node.js 10 (Linux)
-      vmImage: Ubuntu-16.04
-      nodeVersion: 10.x
-
-  - template: .azure-pipelines/job.yml
-    parameters:
-      name: test_on_linux_node8
-      displayName: Test on Node.js 8 (Linux)
-      vmImage: Ubuntu-16.04
-      nodeVersion: 8.x
-
-  - template: .azure-pipelines/job.yml
-    parameters:
-      name: test_on_windows_node12
-      displayName: Test on Node.js 12 (Windows)
-      vmImage: Windows-2019
-      nodeVersion: 12.x
-
-  - template: .azure-pipelines/job.yml
-    parameters:
-      name: test_on_macos_node12
-      displayName: Test on Node.js 12 (macOS)
-      vmImage: macOS-10.14
-      nodeVersion: 12.x
diff --git a/conf/config-schema.js b/conf/config-schema.js
index 83addff87..712fc4235 100644
--- a/conf/config-schema.js
+++ b/conf/config-schema.js
@@ -6,6 +6,7 @@
 "use strict";
 
 const baseConfigProperties = {
+    $schema: { type: "string" },
     env: { type: "object" },
     extends: { $ref: "#/definitions/stringOrStrings" },
     globals: { type: "object" },
diff --git a/docs/rules/brace-style.md b/docs/rules/brace-style.md
index 37098b1cd..dce2d9649 100644
--- a/docs/rules/brace-style.md
+++ b/docs/rules/brace-style.md
@@ -129,6 +129,27 @@ if (foo) { bar(); }
 if (foo) { bar(); } else { baz(); }
 
 try { somethingRisky(); } catch(e) { handleError(); }
+
+if (foo) { baz(); } else {
+  boom();
+}
+
+if (foo) { baz(); } else if (bar) {
+  boom();
+}
+
+if (foo) { baz(); } else
+if (bar) {
+  boom();
+}
+
+if (foo) { baz); } else if (bar) {
+  boom();
+}
+
+try { somethingRisky(); } catch(e) {
+  handleError();
+}
 ```
 
 ### stroustrup
diff --git a/docs/rules/func-style.md b/docs/rules/func-style.md
index 14f01b0c4..e17fecf8f 100644
--- a/docs/rules/func-style.md
+++ b/docs/rules/func-style.md
@@ -77,6 +77,8 @@ Examples of **correct** code for this rule with the default `"expression"` optio
 var foo = function() {
     // ...
 };
+
+var foo = () => {};
 ```
 
 ### declaration
diff --git a/docs/rules/no-console.md b/docs/rules/no-console.md
index bc163b937..f2fb79ec4 100644
--- a/docs/rules/no-console.md
+++ b/docs/rules/no-console.md
@@ -14,7 +14,7 @@ This rule disallows calls to methods of the `console` object.
 Examples of **incorrect** code for this rule:
 
 ```js
-/*eslint no-console: "error"*/
+/* eslint no-console: "error" */
 
 console.log("Log a debug level message.");
 console.warn("Log a warn level message.");
@@ -24,7 +24,7 @@ console.error("Log an error level message.");
 Examples of **correct** code for this rule:
 
 ```js
-/*eslint no-console: "error"*/
+/* eslint no-console: "error" */
 
 // custom console
 Console.log("Hello world!");
@@ -39,7 +39,7 @@ This rule has an object option for exceptions:
 Examples of additional **correct** code for this rule with a sample `{ "allow": ["warn", "error"] }` option:
 
 ```js
-/*eslint no-console: ["error", { allow: ["warn", "error"] }] */
+/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
 
 console.warn("Log a warn level message.");
 console.error("Log an error level message.");
@@ -52,7 +52,7 @@ If you're using Node.js, however, `console` is used to output information to the
 Another case where you might not use this rule is if you want to enforce console calls and not console overwrites. For example:
 
 ```js
-/*eslint no-console: ["error", { allow: ["warn"] }] */
+/* eslint no-console: ["error", { allow: ["warn"] }] */
 console.error = function (message) {
   throw new Error(message);
 };
diff --git a/docs/user-guide/configuring.md b/docs/user-guide/configuring.md
index 5022fc44d..2a47a3e9e 100644
--- a/docs/user-guide/configuring.md
+++ b/docs/user-guide/configuring.md
@@ -664,6 +664,7 @@ In each case, the settings in the configuration file override default settings.
 ESLint supports configuration files in several formats:
 
 * **JavaScript** - use `.eslintrc.js` and export an object containing your configuration.
+* **JavaScript (ESM)** - use `.eslintrc.cjs` when running ESLint in JavaScript packages that specify `"type":"module"` in their `package.json`. Note that ESLint does not support ESM configuration at this time.
 * **YAML** - use `.eslintrc.yaml` or `.eslintrc.yml` to define the configuration structure.
 * **JSON** - use `.eslintrc.json` to define the configuration structure. ESLint's JSON files also allow JavaScript-style comments.
 * **Deprecated** - use `.eslintrc`, which can be either JSON or YAML.
@@ -672,13 +673,13 @@ ESLint supports configuration files in several formats:
 If there are multiple configuration files in the same directory, ESLint will only use one. The priority order is:
 
 1. `.eslintrc.js`
+1. `.eslintrc.cjs`
 1. `.eslintrc.yaml`
 1. `.eslintrc.yml`
 1. `.eslintrc.json`
 1. `.eslintrc`
 1. `package.json`
 
-
 ## Configuration Cascading and Hierarchy
 
 When using `.eslintrc.*` and `package.json` files for configuration, you can take advantage of configuration cascading. For instance, suppose you have the following structure:
diff --git a/lib/cli-engine/config-array-factory.js b/lib/cli-engine/config-array-factory.js
index c444031bc..76c4ccd70 100644
--- a/lib/cli-engine/config-array-factory.js
+++ b/lib/cli-engine/config-array-factory.js
@@ -62,6 +62,7 @@ const eslintRecommendedPath = path.resolve(__dirname, "../../conf/eslint-recomme
 const eslintAllPath = path.resolve(__dirname, "../../conf/eslint-all.js");
 const configFilenames = [
     ".eslintrc.js",
+    ".eslintrc.cjs",
     ".eslintrc.yaml",
     ".eslintrc.yml",
     ".eslintrc.json",
@@ -279,6 +280,7 @@ function configMissingError(configName, importerName) {
 function loadConfigFile(filePath) {
     switch (path.extname(filePath)) {
         case ".js":
+        case ".cjs":
             return loadJSConfigFile(filePath);
 
         case ".json":
diff --git a/lib/linter/config-comment-parser.js b/lib/linter/config-comment-parser.js
index d0e24c594..35862315d 100644
--- a/lib/linter/config-comment-parser.js
+++ b/lib/linter/config-comment-parser.js
@@ -90,7 +90,7 @@ module.exports = class ConfigCommentParser {
          * But we are supporting that. So this is a fallback for that.
          */
         items = {};
-        const normalizedString = string.replace(/([a-zA-Z0-9\-/]+):/gu, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/u, "$1,");
+        const normalizedString = string.replace(/([-a-zA-Z0-9/]+):/gu, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/u, "$1,");
 
         try {
             items = JSON.parse(`{${normalizedString}}`);
diff --git a/lib/rules/prefer-numeric-literals.js b/lib/rules/prefer-numeric-literals.js
index 672d9bcaa..c352d88dc 100644
--- a/lib/rules/prefer-numeric-literals.js
+++ b/lib/rules/prefer-numeric-literals.js
@@ -15,6 +15,12 @@ const astUtils = require("./utils/ast-utils");
 // Helpers
 //------------------------------------------------------------------------------
 
+const radixMap = new Map([
+    [2, { system: "binary", literalPrefix: "0b" }],
+    [8, { system: "octal", literalPrefix: "0o" }],
+    [16, { system: "hexadecimal", literalPrefix: "0x" }]
+]);
+
 /**
  * Checks to see if a CallExpression's callee node is `parseInt` or
  * `Number.parseInt`.
@@ -54,49 +60,44 @@ module.exports = {
         },
 
         schema: [],
+
+        messages: {
+            useLiteral: "Use {{system}} literals instead of {{functionName}}()."
+        },
+
         fixable: "code"
     },
 
     create(context) {
         const sourceCode = context.getSourceCode();
 
-        const radixMap = {
-            2: "binary",
-            8: "octal",
-            16: "hexadecimal"
-        };
-
-        const prefixMap = {
-            2: "0b",
-            8: "0o",
-            16: "0x"
-        };
-
         //----------------------------------------------------------------------
         // Public
         //----------------------------------------------------------------------
 
         return {
 
-            CallExpression(node) {
-
-                // doesn't check parseInt() if it doesn't have a radix argument
-                if (node.arguments.length !== 2) {
-                    return;
-                }
+            "CallExpression[arguments.length=2]"(node) {
+                const [strNode, radixNode] = node.arguments,
+                    str = strNode.value,
+                    radix = radixNode.value;
+
+                if (
+                    strNode.type === "Literal" &&
+                    radixNode.type === "Literal" &&
+                    typeof str === "string" &&
+                    typeof radix === "number" &&
+                    radixMap.has(radix) &&
+                    isParseInt(node.callee)
+                ) {
 
-                // only error if the radix is 2, 8, or 16
-                const radixName = radixMap[node.arguments[1].value];
+                    const { system, literalPrefix } = radixMap.get(radix);
 
-                if (isParseInt(node.callee) &&
-                    radixName &&
-                    node.arguments[0].type === "Literal"
-                ) {
                     context.report({
                         node,
-                        message: "Use {{radixName}} literals instead of {{functionName}}().",
+                        messageId: "useLiteral",
                         data: {
-                            radixName,
+                            system,
                             functionName: sourceCode.getText(node.callee)
                         },
                         fix(fixer) {
@@ -104,9 +105,9 @@ module.exports = {
                                 return null;
                             }
 
-                            const replacement = `${prefixMap[node.arguments[1].value]}${node.arguments[0].value}`;
+                            const replacement = `${literalPrefix}${str}`;
 
-                            if (+replacement !== parseInt(node.arguments[0].value, node.arguments[1].value)) {
+                            if (+replacement !== parseInt(str, radix)) {
 
                                 /*
                                  * If the newly-produced literal would be invalid, (e.g. 0b1234),


Test patch:
diff --git a/tests/fixtures/config-file/cjs/.eslintrc.cjs b/tests/fixtures/config-file/cjs/.eslintrc.cjs
new file mode 100644
index 000000000..b3100591d
--- /dev/null
+++ b/tests/fixtures/config-file/cjs/.eslintrc.cjs
@@ -0,0 +1,5 @@
+module.exports = {
+    rules: {
+        semi: [2, "always"]
+    }
+};
diff --git a/tests/lib/cli-engine/config-array-factory.js b/tests/lib/cli-engine/config-array-factory.js
index 4f3ade1e6..1fb5661fb 100644
--- a/tests/lib/cli-engine/config-array-factory.js
+++ b/tests/lib/cli-engine/config-array-factory.js
@@ -171,6 +171,7 @@ describe("ConfigArrayFactory", () => {
     describe("'loadFile(filePath, options)' method should load a config file.", () => {
         const basicFiles = {
             "js/.eslintrc.js": "exports.settings = { name: 'js/.eslintrc.js' }",
+            "cjs/.eslintrc.cjs": "exports.settings = { name: 'cjs/.eslintrc.cjs' }",
             "json/.eslintrc.json": "{ \"settings\": { \"name\": \"json/.eslintrc.json\" } }",
             "legacy-json/.eslintrc": "{ \"settings\": { \"name\": \"legacy-json/.eslintrc\" } }",
             "legacy-yml/.eslintrc": "settings:\n  name: legacy-yml/.eslintrc",
@@ -288,6 +289,7 @@ describe("ConfigArrayFactory", () => {
     describe("'loadInDirectory(directoryPath, options)' method should load the config file of a directory.", () => {
         const basicFiles = {
             "js/.eslintrc.js": "exports.settings = { name: 'js/.eslintrc.js' }",
+            "cjs/.eslintrc.cjs": "exports.settings = { name: 'cjs/.eslintrc.cjs' }",
             "json/.eslintrc.json": "{ \"settings\": { \"name\": \"json/.eslintrc.json\" } }",
             "legacy-json/.eslintrc": "{ \"settings\": { \"name\": \"legacy-json/.eslintrc\" } }",
             "legacy-yml/.eslintrc": "settings:\n  name: legacy-yml/.eslintrc",
@@ -1497,6 +1499,7 @@ describe("ConfigArrayFactory", () => {
             "node_modules/eslint-plugin-invalid-parser/index.js": "exports.configs = { foo: { parser: 'nonexistent-parser' } }",
             "node_modules/eslint-plugin-invalid-config/index.js": "exports.configs = { foo: {} }",
             "js/.eslintrc.js": "module.exports = { rules: { semi: [2, 'always'] } };",
+            "cjs/.eslintrc.cjs": "module.exports = { rules: { semi: [2, 'always'] } };",
             "json/.eslintrc.json": "{ \"rules\": { \"quotes\": [2, \"double\"] } }",
             "package-json/package.json": "{ \"eslintConfig\": { \"env\": { \"es6\": true } } }",
             "yaml/.eslintrc.yaml": "env:\n    browser: true"
@@ -1747,6 +1750,22 @@ describe("ConfigArrayFactory", () => {
             });
         });
 
+        it("should load information from a JavaScript file with a .cjs extension", () => {
+            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
+                files: {
+                    "cjs/.eslintrc.cjs": "module.exports = { rules: { semi: [2, 'always'] } };"
+                }
+            });
+            const factory = new ConfigArrayFactory();
+            const config = load(factory, "cjs/.eslintrc.cjs");
+
+            assertConfig(config, {
+                rules: {
+                    semi: [2, "always"]
+                }
+            });
+        });
+
         it("should throw error when loading invalid JavaScript file", () => {
             const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
                 files: {
diff --git a/tests/lib/rules/brace-style.js b/tests/lib/rules/brace-style.js
index 4444863b6..70211608b 100644
--- a/tests/lib/rules/brace-style.js
+++ b/tests/lib/rules/brace-style.js
@@ -82,6 +82,22 @@ ruleTester.run("brace-style", rule, {
         { code: "if (foo) {}\nelse {}", options: ["allman", { allowSingleLine: true }] },
         { code: "try {  bar(); }\ncatch (e) { baz();  }", options: ["allman", { allowSingleLine: true }] },
         { code: "var foo = () => { return; }", options: ["allman", { allowSingleLine: true }], parserOptions: { ecmaVersion: 6 } },
+        {
+            code: "if (foo) { baz(); } else {\n  boom();\n}",
+            options: ["1tbs", { allowSingleLine: true }]
+        },
+        {
+            code: "if (foo) { baz(); } else if (bar) {\n  boom();\n}",
+            options: ["1tbs", { allowSingleLine: true }]
+        },
+        {
+            code: "if (foo) { baz(); } else\nif (bar) {\n  boom();\n}",
+            options: ["1tbs", { allowSingleLine: true }]
+        },
+        {
+            code: "try { somethingRisky(); } catch(e) {\n  handleError();\n}",
+            options: ["1tbs", { allowSingleLine: true }]
+        },
         {
             code: "if (tag === 1) fontstack.name = pbf.readString(); \nelse if (tag === 2) fontstack.range = pbf.readString(); \nelse if (tag === 3) {\n var glyph = pbf.readMessage(readGlyph, {});\n fontstack.glyphs[glyph.id] = glyph; \n}",
             options: ["1tbs"]
diff --git a/tests/lib/rules/prefer-numeric-literals.js b/tests/lib/rules/prefer-numeric-literals.js
index ddd6e36c8..4d3037f44 100644
--- a/tests/lib/rules/prefer-numeric-literals.js
+++ b/tests/lib/rules/prefer-numeric-literals.js
@@ -31,7 +31,28 @@ ruleTester.run("prefer-numeric-literals", rule, {
         "parseInt(foo);",
         "parseInt(foo, 2);",
         "Number.parseInt(foo);",
-        "Number.parseInt(foo, 2);"
+        "Number.parseInt(foo, 2);",
+        "parseInt(11, 2);",
+        "Number.parseInt(1, 8);",
+        "parseInt(1e5, 16);",
+        "parseInt('11', '2');",
+        "Number.parseInt('11', '8');",
+        {
+            code: "parseInt('11', 2n);",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "Number.parseInt('11', 8n);",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "parseInt('11', 16n);",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "parseInt(1n, 2);",
+            parserOptions: { ecmaVersion: 2020 }
+        }
     ],
     invalid: [
         {
diff --git a/tests/lib/shared/config-validator.js b/tests/lib/shared/config-validator.js
index 1c4cfe895..95976fc9f 100644
--- a/tests/lib/shared/config-validator.js
+++ b/tests/lib/shared/config-validator.js
@@ -114,6 +114,7 @@ describe("Validator", () => {
         it("should do nothing with a valid eslint config", () => {
             validator.validate(
                 {
+                    $schema: "http://json.schemastore.org/eslintrc",
                     root: true,
                     globals: { globalFoo: "readonly" },
                     parser: "parserFoo",