Repository: eslint-community/eslint-plugin-es-x
Pull request #128 (2024-02-25 06:58:47)

Add `es-x/no-arraybuffer-prototype-transfer` rule
close  #126


Patch:
diff --git a/docs/rules/index.md b/docs/rules/index.md
index 48e1578..bbce844 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -10,6 +10,7 @@ There is a config that enables the rules in this category: `plugin:es-x/no-new-i
 
 | Rule ID | Description |    |
 |:--------|:------------|:--:|
+| [es-x/no-arraybuffer-prototype-transfer](./no-arraybuffer-prototype-transfer.md) | disallow the `ArrayBuffer.prototype.transfer` method. |  |
 | [es-x/no-atomics-waitasync](./no-atomics-waitasync.md) | disallow the `Atomics.waitAsync` method. |  |
 | [es-x/no-object-map-groupby](./no-object-map-groupby.md) | disallow the `{Object,Map}.groupBy()` function (array grouping). |  |
 | [es-x/no-promise-withresolvers](./no-promise-withresolvers.md) | disallow the `Promise.withResolvers()` method. |  |
diff --git a/docs/rules/no-arraybuffer-prototype-transfer.md b/docs/rules/no-arraybuffer-prototype-transfer.md
new file mode 100644
index 0000000..164a561
--- /dev/null
+++ b/docs/rules/no-arraybuffer-prototype-transfer.md
@@ -0,0 +1,56 @@
+---
+title: "es-x/no-arraybuffer-prototype-transfer"
+description: "disallow the `ArrayBuffer.prototype.transfer` method"
+---
+
+# es-x/no-arraybuffer-prototype-transfer
+> disallow the `ArrayBuffer.prototype.transfer` 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-esnext`
+
+This rule reports ES2024 [``ArrayBuffer.prototype.transfer`` methods](https://github.com/tc39/proposal-arraybuffer-transfer) 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-arraybuffer-prototype-transfer: [error, { aggressive: true }] */
+async function validateAndWriteSafeAndFast(arrayBuffer) {
+  // Transfer to take ownership, which implementations can choose to
+  // implement as a zero-copy move.
+  const owned = arrayBuffer.transfer();
+
+  // arrayBuffer is detached after this point.
+  assert(arrayBuffer.detached);
+
+  await validate(owned);
+  await fs.writeFile("data.bin", owned);
+}
+```
+
+</eslint-playground>
+
+## 🔧 Options
+
+This rule has an option.
+
+```yaml
+rules:
+  es-x/no-arraybuffer-prototype-transfer: [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-arraybuffer-prototype-transfer.js)
+- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-arraybuffer-prototype-transfer.js)
diff --git a/lib/configs/no-new-in-esnext.js b/lib/configs/no-new-in-esnext.js
index 1467b02..ff1314e 100644
--- a/lib/configs/no-new-in-esnext.js
+++ b/lib/configs/no-new-in-esnext.js
@@ -7,6 +7,7 @@
 module.exports = {
     plugins: ["es-x"],
     rules: {
+        "es-x/no-arraybuffer-prototype-transfer": "error",
         "es-x/no-atomics-waitasync": "error",
         "es-x/no-object-map-groupby": "error",
         "es-x/no-promise-withresolvers": "error",
diff --git a/lib/index.js b/lib/index.js
index 060abd6..5164466 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -103,6 +103,7 @@ module.exports = {
         "no-array-prototype-values": require("./rules/no-array-prototype-values"),
         "no-array-prototype-with": require("./rules/no-array-prototype-with"),
         "no-array-string-prototype-at": require("./rules/no-array-string-prototype-at"),
+        "no-arraybuffer-prototype-transfer": require("./rules/no-arraybuffer-prototype-transfer"),
         "no-arrow-functions": require("./rules/no-arrow-functions"),
         "no-async-functions": require("./rules/no-async-functions"),
         "no-async-iteration": require("./rules/no-async-iteration"),
diff --git a/lib/rules/no-arraybuffer-prototype-transfer.js b/lib/rules/no-arraybuffer-prototype-transfer.js
new file mode 100644
index 0000000..9d2041d
--- /dev/null
+++ b/lib/rules/no-arraybuffer-prototype-transfer.js
@@ -0,0 +1,51 @@
+"use strict"
+
+const {
+    definePrototypeMethodHandler,
+} = require("../util/define-prototype-method-handler")
+
+module.exports = {
+    meta: {
+        docs: {
+            description:
+                "disallow the `ArrayBuffer.prototype.transfer` method.",
+            category: "ES2024",
+            recommended: false,
+            url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-arraybuffer-prototype-transfer.html",
+        },
+        fixable: null,
+        messages: {
+            forbidden: "ES2024 '{{name}}' method is forbidden.",
+            forbiddenForDetached:
+                "ES2024 'ArrayBuffer.prototype.detached' property is forbidden.",
+        },
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    aggressive: { type: "boolean" },
+                },
+                additionalProperties: false,
+            },
+        ],
+        type: "problem",
+    },
+    create(context) {
+        return definePrototypeMethodHandler(
+            context,
+            {
+                ArrayBuffer: ["detached", "transfer", "transferToFixedLength"],
+            },
+            {
+                createReport({ propertyName }) {
+                    return {
+                        messageId:
+                            propertyName === "detached"
+                                ? "forbiddenForDetached"
+                                : "forbidden",
+                    }
+                },
+            },
+        )
+    },
+}
diff --git a/lib/util/define-prototype-method-handler/object-type-checker.js b/lib/util/define-prototype-method-handler/object-type-checker.js
index 229d505..8d6d39e 100644
--- a/lib/util/define-prototype-method-handler/object-type-checker.js
+++ b/lib/util/define-prototype-method-handler/object-type-checker.js
@@ -91,6 +91,8 @@ const WELLKNOWN_GLOBALS = {
     Float64Array: { type: "Function", returnType: "Float64Array" },
     BigInt64Array: { type: "Function", returnType: "BigInt64Array" },
     BigUint64Array: { type: "Function", returnType: "BigUint64Array" },
+    ArrayBuffer: { type: "Function", returnType: "ArrayBuffer" },
+    SharedArrayBuffer: { type: "Function", returnType: "SharedArrayBuffer" },
     Intl: {
         type: "Object",
         properties: {
diff --git a/package.json b/package.json
index 6c9ed90..cf511ba 100644
--- a/package.json
+++ b/package.json
@@ -15,10 +15,10 @@
   "dependencies": {
     "@eslint-community/eslint-utils": "^4.1.2",
     "@eslint-community/regexpp": "^4.6.0",
-    "eslint-compat-utils": "^0.4.1"
+    "eslint-compat-utils": "^0.5.0"
   },
   "devDependencies": {
-    "@typescript-eslint/parser": "^6.1.0",
+    "@typescript-eslint/parser": "^7.0.2",
     "acorn": "^8.7.0",
     "env-cmd": "^10.1.0",
     "eslint": "^8.11.0",
@@ -27,11 +27,11 @@
     "eslint-plugin-n": "^16.0.1",
     "eslint-plugin-prettier": "^5.0.0",
     "eslint-plugin-vue": "^9.0.0",
-    "espree": "^9.3.1",
-    "globals": "^13.0.0",
-    "jsdom": "^23.0.1",
+    "espree": "^10.0.1",
+    "globals": "^14.0.0",
+    "jsdom": "^24.0.0",
     "mocha": "^10.0.0",
-    "monaco-editor": "^0.46.0",
+    "monaco-editor": "^0.47.0",
     "npm-run-all": "^4.1.5",
     "nyc": "^15.0.0",
     "opener": "^1.5.1",


Test patch:
diff --git a/tests/lib/rules/no-arraybuffer-prototype-transfer.js b/tests/lib/rules/no-arraybuffer-prototype-transfer.js
new file mode 100644
index 0000000..834f066
--- /dev/null
+++ b/tests/lib/rules/no-arraybuffer-prototype-transfer.js
@@ -0,0 +1,97 @@
+"use strict"
+
+const RuleTester = require("../../tester")
+const rule = require("../../../lib/rules/no-arraybuffer-prototype-transfer.js")
+
+new RuleTester().run("no-arraybuffer-prototype-transfer", rule, {
+    valid: [
+        "transfer()",
+        "transferToFixedLength()",
+        "detached",
+        "foo.resize()",
+        "foo.transfer()",
+        "foo.transferToFixedLength()",
+        "foo.detached",
+        { code: "transfer()", settings: { "es-x": { aggressive: true } } },
+        {
+            code: "transferToFixedLength()",
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            code: "detached",
+            settings: { "es-x": { aggressive: true } },
+        },
+        { code: "foo.resize()", settings: { "es-x": { aggressive: true } } },
+        {
+            code: "foo.transfer()",
+            options: [{ aggressive: false }],
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            code: "foo.transferToFixedLength()",
+            options: [{ aggressive: false }],
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            code: "foo.detached",
+            options: [{ aggressive: false }],
+            settings: { "es-x": { aggressive: true } },
+        },
+    ],
+    invalid: [
+        {
+            code: "foo.transfer()",
+            errors: [
+                "ES2024 'ArrayBuffer.prototype.transfer' method is forbidden.",
+            ],
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            code: "foo.transferToFixedLength()",
+            errors: [
+                "ES2024 'ArrayBuffer.prototype.transferToFixedLength' method is forbidden.",
+            ],
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            code: "foo.detached",
+            errors: [
+                "ES2024 'ArrayBuffer.prototype.detached' property is forbidden.",
+            ],
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            code: "const foo = new ArrayBuffer(8); foo.transfer()",
+            errors: [
+                "ES2024 'ArrayBuffer.prototype.transfer' method is forbidden.",
+            ],
+        },
+        {
+            code: "const foo = new ArrayBuffer(8); foo.transferToFixedLength()",
+            errors: [
+                "ES2024 'ArrayBuffer.prototype.transferToFixedLength' method is forbidden.",
+            ],
+        },
+        {
+            code: "const foo = new ArrayBuffer(8); foo.detached",
+            errors: [
+                "ES2024 'ArrayBuffer.prototype.detached' property is forbidden.",
+            ],
+        },
+        {
+            code: `
+            const arrayBuffer = new ArrayBuffer(8);
+            // Transfer to take ownership, which implementations can choose to
+            // implement as a zero-copy move.
+            const owned = arrayBuffer.transfer();
+        
+            // arrayBuffer is detached after this point.
+            assert(arrayBuffer.detached);
+            `,
+            errors: [
+                "ES2024 'ArrayBuffer.prototype.transfer' method is forbidden.",
+                "ES2024 'ArrayBuffer.prototype.detached' property is forbidden.",
+            ],
+        },
+    ],
+})
diff --git a/tests/lib/rules/no-resizable-and-growable-arraybuffers.js b/tests/lib/rules/no-resizable-and-growable-arraybuffers.js
index 09051ca..cc98017 100644
--- a/tests/lib/rules/no-resizable-and-growable-arraybuffers.js
+++ b/tests/lib/rules/no-resizable-and-growable-arraybuffers.js
@@ -132,7 +132,7 @@ new RuleTester(testerOptions).run(ruleId, rule, {
                     column: 53,
                 },
                 {
-                    message: "ES2024 Resizable ArrayBuffer is forbidden.",
+                    message: "ES2024 Growable SharedArrayBuffer is forbidden.",
                     line: 3,
                     column: 25,
                 },