Repository: eslint-community/eslint-plugin-es-x
Pull request #53 (2023-01-20 15:50:12)

Add rules for ES2018 Intl API
related to #45 


Patch:
diff --git a/docs/rules/index.md b/docs/rules/index.md
index d8b3b31..12a1891 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -117,6 +117,15 @@ There are multiple configs that enable all rules in this category: `plugin:es-x/
 | [es-x/no-regexp-unicode-property-escapes](./no-regexp-unicode-property-escapes.md) | disallow RegExp Unicode property escape sequences. |  |
 | [es-x/no-rest-spread-properties](./no-rest-spread-properties.md) | disallow rest/spread properties. |  |
 
+## ES2018 Intl API
+
+There are multiple configs that enable all rules in this category: `plugin:es-x/no-new-in-es2018-intl-api`, `plugin:es-x/restrict-to-es-intl-api-1st-edition`, `plugin:es-x/restrict-to-es2015-intl-api`, `plugin:es-x/restrict-to-es2016-intl-api`, and `plugin:es-x/restrict-to-es2017-intl-api`
+
+| Rule ID | Description |    |
+|:--------|:------------|:--:|
+| [es-x/no-intl-numberformat-prototype-formattoparts](./no-intl-numberformat-prototype-formattoparts.md) | disallow the `NumberFormat.prototype.formatToParts` method. |  |
+| [es-x/no-intl-pluralrules](./no-intl-pluralrules.md) | disallow the `Intl.PluralRules` object. |  |
+
 ## ES2017
 
 There are multiple configs that enable all rules in this category: `plugin:es-x/no-new-in-es2017`, `plugin:es-x/restrict-to-es3`, `plugin:es-x/restrict-to-es5`, `plugin:es-x/restrict-to-es2015`, and `plugin:es-x/restrict-to-es2016`
diff --git a/docs/rules/no-intl-numberformat-prototype-formattoparts.md b/docs/rules/no-intl-numberformat-prototype-formattoparts.md
new file mode 100644
index 0000000..029c228
--- /dev/null
+++ b/docs/rules/no-intl-numberformat-prototype-formattoparts.md
@@ -0,0 +1,50 @@
+---
+title: "es-x/no-intl-numberformat-prototype-formattoparts"
+description: "disallow the `NumberFormat.prototype.formatToParts` method"
+---
+
+# es-x/no-intl-numberformat-prototype-formattoparts
+> disallow the `NumberFormat.prototype.formatToParts` method
+
+- ❗ <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+- ✅ The following configurations enable this rule: `plugin:es-x/no-new-in-es2018-intl-api`, `plugin:es-x/restrict-to-es-intl-api-1st-edition`, `plugin:es-x/restrict-to-es2015-intl-api`, `plugin:es-x/restrict-to-es2016-intl-api`, and `plugin:es-x/restrict-to-es2017-intl-api`
+
+This rule reports ES2018 Intl API `NumberFormat.prototype.formatToParts` as errors.
+
+This rule is silent by default because it's hard to know types. You need to configure [the aggressive mode](../#the-aggressive-mode) or TypeScript in order to enable this rule.
+
+## 💡 Examples
+
+⛔ Examples of **incorrect** code for this rule:
+
+<eslint-playground type="bad">
+
+```js
+/*eslint es-x/no-intl-numberformat-prototype-formattoparts: [error, { aggressive: true }] */
+const formatter = new Intl.NumberFormat('de-DE', {
+  style: 'currency',
+  currency: 'EUR'
+});
+console.log(formatter.formatToParts(number));
+```
+
+</eslint-playground>
+
+## 🔧 Options
+
+This rule has an option.
+
+```yml
+rules:
+  es-x/no-intl-numberformat-prototype-formattoparts: [error, { aggressive: false }]
+```
+
+### aggressive: boolean
+
+Configure the aggressive mode for only this rule.
+This is prior to the `settings['es-x'].aggressive` setting.
+
+## 📚 References
+
+- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-intl-numberformat-prototype-formattoparts.js)
+- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-intl-numberformat-prototype-formattoparts.js)
diff --git a/docs/rules/no-intl-pluralrules.md b/docs/rules/no-intl-pluralrules.md
new file mode 100644
index 0000000..e805832
--- /dev/null
+++ b/docs/rules/no-intl-pluralrules.md
@@ -0,0 +1,30 @@
+---
+title: "es-x/no-intl-pluralrules"
+description: "disallow the `Intl.PluralRules` object"
+---
+
+# es-x/no-intl-pluralrules
+> disallow the `Intl.PluralRules` object
+
+- ❗ <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+- ✅ The following configurations enable this rule: `plugin:es-x/no-new-in-es2018-intl-api`, `plugin:es-x/restrict-to-es-intl-api-1st-edition`, `plugin:es-x/restrict-to-es2015-intl-api`, `plugin:es-x/restrict-to-es2016-intl-api`, and `plugin:es-x/restrict-to-es2017-intl-api`
+
+This rule reports ES2018 Intl API `Intl.PluralRules` object as errors.
+
+## 💡 Examples
+
+⛔ Examples of **incorrect** code for this rule:
+
+<eslint-playground type="bad">
+
+```js
+/*eslint es-x/no-intl-pluralrules: error */
+const pr = new Intl.PluralRules();
+```
+
+</eslint-playground>
+
+## 📚 References
+
+- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-intl-pluralrules.js)
+- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-intl-pluralrules.js)
diff --git a/lib/configs/no-new-in-es2018-intl-api.js b/lib/configs/no-new-in-es2018-intl-api.js
index f42c599..4468b24 100644
--- a/lib/configs/no-new-in-es2018-intl-api.js
+++ b/lib/configs/no-new-in-es2018-intl-api.js
@@ -4,4 +4,10 @@
  */
 "use strict"
 
-module.exports = { plugins: ["es-x"], rules: {} }
+module.exports = {
+    plugins: ["es-x"],
+    rules: {
+        "es-x/no-intl-numberformat-prototype-formattoparts": "error",
+        "es-x/no-intl-pluralrules": "error",
+    },
+}
diff --git a/lib/index.js b/lib/index.js
index 510bc92..f0e7e2d 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -125,6 +125,8 @@ module.exports = {
         "no-hashbang": require("./rules/no-hashbang"),
         "no-import-meta": require("./rules/no-import-meta"),
         "no-initializers-in-for-in": require("./rules/no-initializers-in-for-in"),
+        "no-intl-numberformat-prototype-formattoparts": require("./rules/no-intl-numberformat-prototype-formattoparts"),
+        "no-intl-pluralrules": require("./rules/no-intl-pluralrules"),
         "no-intl-locale": require("./rules/no-intl-locale"),
         "no-intl-relativetimeformat": require("./rules/no-intl-relativetimeformat"),
         "no-intl-datetimeformat-prototype-formatrange": require("./rules/no-intl-datetimeformat-prototype-formatrange"),
diff --git a/lib/rules/no-intl-numberformat-prototype-formattoparts.js b/lib/rules/no-intl-numberformat-prototype-formattoparts.js
new file mode 100644
index 0000000..e08e0c7
--- /dev/null
+++ b/lib/rules/no-intl-numberformat-prototype-formattoparts.js
@@ -0,0 +1,36 @@
+"use strict"
+
+const {
+    definePrototypeMethodHandler,
+} = require("../util/define-prototype-method-handler")
+
+module.exports = {
+    meta: {
+        docs: {
+            description:
+                "disallow the `NumberFormat.prototype.formatToParts` method.",
+            category: "ES2018-Intl-API",
+            recommended: false,
+            url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-intl-numberformat-prototype-formattoparts.html",
+        },
+        fixable: null,
+        messages: {
+            forbidden: "ES2018 Intl API '{{name}}' method is forbidden.",
+        },
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    aggressive: { type: "boolean" },
+                },
+                additionalProperties: false,
+            },
+        ],
+        type: "problem",
+    },
+    create(context) {
+        return definePrototypeMethodHandler(context, {
+            "Intl.NumberFormat": ["formatToParts"],
+        })
+    },
+}
diff --git a/lib/rules/no-intl-pluralrules.js b/lib/rules/no-intl-pluralrules.js
new file mode 100644
index 0000000..56b04e5
--- /dev/null
+++ b/lib/rules/no-intl-pluralrules.js
@@ -0,0 +1,38 @@
+"use strict"
+
+const { READ, ReferenceTracker } = require("@eslint-community/eslint-utils")
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "disallow the `Intl.PluralRules` object.",
+            category: "ES2018-Intl-API",
+            recommended: false,
+            url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-intl-pluralrules.html",
+        },
+        fixable: null,
+        messages: {
+            forbidden: "ES2018 Intl API '{{name}}' object is forbidden.",
+        },
+        schema: [],
+        type: "problem",
+    },
+    create(context) {
+        return {
+            "Program:exit"() {
+                const tracker = new ReferenceTracker(context.getScope())
+                for (const { node, path } of tracker.iterateGlobalReferences({
+                    Intl: {
+                        PluralRules: { [READ]: true },
+                    },
+                })) {
+                    context.report({
+                        node,
+                        messageId: "forbidden",
+                        data: { name: path.join(".") },
+                    })
+                }
+            },
+        }
+    },
+}


Test patch:
diff --git a/tests/lib/rules/no-intl-numberformat-prototype-formattoparts.js b/tests/lib/rules/no-intl-numberformat-prototype-formattoparts.js
new file mode 100644
index 0000000..f07ad58
--- /dev/null
+++ b/tests/lib/rules/no-intl-numberformat-prototype-formattoparts.js
@@ -0,0 +1,136 @@
+"use strict"
+
+const path = require("path")
+const RuleTester = require("../../tester")
+const rule = require("../../../lib/rules/no-intl-numberformat-prototype-formattoparts.js")
+const ruleId = "no-intl-numberformat-prototype-formattoparts"
+
+new RuleTester().run(ruleId, rule, {
+    valid: [
+        "formatToParts(num)",
+        "foo.unknown(0)",
+        "foo.formatToParts(num)",
+        {
+            code: "formatToParts(num)",
+            settings: { "es-x": { aggressive: true } },
+        },
+        { code: "foo.unknown(0)", settings: { "es-x": { aggressive: true } } },
+        {
+            code: "foo.formatToParts(num)",
+            options: [{ aggressive: false }],
+            settings: { "es-x": { aggressive: true } },
+        },
+    ],
+    invalid: [
+        {
+            code: "foo.formatToParts(num)",
+            errors: [
+                "ES2018 Intl API 'Intl.NumberFormat.prototype.formatToParts' method is forbidden.",
+            ],
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            code: "foo.formatToParts(num)",
+            options: [{ aggressive: true }],
+            errors: [
+                "ES2018 Intl API 'Intl.NumberFormat.prototype.formatToParts' method is forbidden.",
+            ],
+            settings: { "es-x": { aggressive: false } },
+        },
+    ],
+})
+
+// -----------------------------------------------------------------------------
+// TypeScript
+// -----------------------------------------------------------------------------
+const parser = require.resolve("@typescript-eslint/parser")
+const tsconfigRootDir = path.resolve(__dirname, "../../fixtures")
+const project = "tsconfig.json"
+const filename = path.join(tsconfigRootDir, "test.ts")
+
+new RuleTester({ parser }).run(`${ruleId} TS`, rule, {
+    valid: [
+        { filename, code: "formatToParts(num)" },
+        { filename, code: "foo.unknown(0)" },
+        { filename, code: "foo.formatToParts(num)" },
+        { filename, code: "let foo = {}; foo.formatToParts(num)" },
+        {
+            filename,
+            code: "formatToParts(num)",
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            filename,
+            code: "foo.unknown(0)",
+            settings: { "es-x": { aggressive: true } },
+        },
+    ],
+    invalid: [
+        {
+            filename,
+            code: "let foo = new Unknown(); foo.formatToParts(num)",
+            errors: [
+                "ES2018 Intl API 'Intl.NumberFormat.prototype.formatToParts' method is forbidden.",
+            ],
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            filename,
+            code: "foo.formatToParts(num)",
+            errors: [
+                "ES2018 Intl API 'Intl.NumberFormat.prototype.formatToParts' method is forbidden.",
+            ],
+            settings: { "es-x": { aggressive: true } },
+        },
+    ],
+})
+
+new RuleTester({ parser, parserOptions: { tsconfigRootDir, project } }).run(
+    `${ruleId} TS Full Type Information`,
+    rule,
+    {
+        valid: [
+            { filename, code: "formatToParts(num)" },
+            { filename, code: "foo.unknown(0)" },
+            { filename, code: "foo.formatToParts(num)" },
+            {
+                filename,
+                code: "let foo = {}; foo.formatToParts(num)",
+            },
+            {
+                filename,
+                code: "formatToParts(num)",
+                settings: { "es-x": { aggressive: true } },
+            },
+            {
+                filename,
+                code: "foo.unknown(0)",
+                settings: { "es-x": { aggressive: true } },
+            },
+        ],
+        invalid: [
+            {
+                filename,
+                code: "let foo = new Intl.NumberFormat(); foo.formatToParts(num)",
+                errors: [
+                    "ES2018 Intl API 'Intl.NumberFormat.prototype.formatToParts' method is forbidden.",
+                ],
+            },
+            {
+                filename,
+                code: "function f(a: Intl.NumberFormat) { a.formatToParts(num) }",
+                errors: [
+                    "ES2018 Intl API 'Intl.NumberFormat.prototype.formatToParts' method is forbidden.",
+                ],
+            },
+            {
+                filename,
+                code: "foo.formatToParts(num)",
+                errors: [
+                    "ES2018 Intl API 'Intl.NumberFormat.prototype.formatToParts' method is forbidden.",
+                ],
+                settings: { "es-x": { aggressive: true } },
+            },
+        ],
+    },
+)
diff --git a/tests/lib/rules/no-intl-pluralrules.js b/tests/lib/rules/no-intl-pluralrules.js
new file mode 100644
index 0000000..9521ab0
--- /dev/null
+++ b/tests/lib/rules/no-intl-pluralrules.js
@@ -0,0 +1,19 @@
+"use strict"
+
+const RuleTester = require("../../tester")
+const rule = require("../../../lib/rules/no-intl-pluralrules")
+
+new RuleTester().run("no-intl-pluralrules", rule, {
+    valid: [
+        "Intl",
+        "Intl.DateTimeFormat",
+        "Intl.NumberFormat",
+        "let Intl = 0; Intl.PluralRules",
+    ],
+    invalid: [
+        {
+            code: "Intl.PluralRules",
+            errors: ["ES2018 Intl API 'Intl.PluralRules' object is forbidden."],
+        },
+    ],
+})