Repository: eslint-community/eslint-plugin-es-x (js)

Issue:
Add rule for Resizable and growable ArrayBuffers
[Resizable and growable ArrayBuffers](https://github.com/tc39/proposal-resizablearraybuffer) has reached stage 4.

https://github.com/tc39/proposal-resizablearraybuffer

I think we need to add a rule to prohibit them.

- The second (`option`) argument to `ArrayBuffer` constructor.
- ArrayBuffer.prototype.maxByteLength
- ArrayBuffer.prototype.resizable
- ArrayBuffer.prototype.resize()
- The second (`option`) argument to `SharedArrayBuffer` constructor.
- SharedArrayBuffer.prototype.grow()
- SharedArrayBuffer.prototype.growable
- SharedArrayBuffer.prototype.maxByteLength

Pull request description:
Add `es-x/no-resizable-and-growable-arraybuffers` rule
close #94

Patch:
diff --git a/docs/rules/index.md b/docs/rules/index.md
index 67df829..1fef76a 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -12,6 +12,7 @@ There is a config that enables the rules in this category: `plugin:es-x/no-new-i
 |:--------|:------------|:--:|
 | [es-x/no-atomics-waitasync](./no-atomics-waitasync.md) | disallow the `Atomics.waitAsync` method. |  |
 | [es-x/no-regexp-v-flag](./no-regexp-v-flag.md) | disallow RegExp `v` flag. |  |
+| [es-x/no-resizable-and-growable-arraybuffers](./no-resizable-and-growable-arraybuffers.md) | disallow resizable and growable ArrayBuffers. |  |
 | [es-x/no-string-prototype-iswellformed-towellformed](./no-string-prototype-iswellformed-towellformed.md) | disallow the `String.prototype.{isWellFormed,toWellFormed}` methods. |  |
 
 ## ES2023
diff --git a/docs/rules/no-resizable-and-growable-arraybuffers.md b/docs/rules/no-resizable-and-growable-arraybuffers.md
new file mode 100644
index 0000000..814a7d2
--- /dev/null
+++ b/docs/rules/no-resizable-and-growable-arraybuffers.md
@@ -0,0 +1,56 @@
+---
+title: "es-x/no-resizable-and-growable-arraybuffers"
+description: "disallow resizable and growable ArrayBuffers"
+---
+
+# es-x/no-resizable-and-growable-arraybuffers
+> disallow resizable and growable ArrayBuffers
+
+- ❗ <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 [Resizable and growable ArrayBuffers](https://github.com/tc39/proposal-resizablearraybuffer) 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-resizable-and-growable-arraybuffers: [error, { aggressive: true }] */
+const ab = new ArrayBuffer(8, { maxByteLength: 16 });
+if (sab.resizable) {
+  console.log("AB is resizable!");
+  sab.resize(12);
+}
+
+const sab = new SharedArrayBuffer(8, { maxByteLength: 16 });
+if (sab.growable) {
+  console.log("SAB is growable!");
+  sab.grow(12);
+}
+```
+
+</eslint-playground>
+
+## 🔧 Options
+
+This rule has an option.
+
+```yaml
+rules:
+  es-x/no-resizable-and-growable-arraybuffers: [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-resizable-and-growable-arraybuffers.js)
+- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-resizable-and-growable-arraybuffers.js)
diff --git a/lib/configs/no-new-in-esnext.js b/lib/configs/no-new-in-esnext.js
index 4e16ea2..41d2ee1 100644
--- a/lib/configs/no-new-in-esnext.js
+++ b/lib/configs/no-new-in-esnext.js
@@ -9,6 +9,7 @@ module.exports = {
     rules: {
         "es-x/no-atomics-waitasync": "error",
         "es-x/no-regexp-v-flag": "error",
+        "es-x/no-resizable-and-growable-arraybuffers": "error",
         "es-x/no-string-prototype-iswellformed-towellformed": "error",
     },
 }
diff --git a/lib/index.js b/lib/index.js
index fff10d3..f487755 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -235,6 +235,7 @@ module.exports = {
         "no-regexp-unicode-property-escapes-2023": require("./rules/no-regexp-unicode-property-escapes-2023"),
         "no-regexp-v-flag": require("./rules/no-regexp-v-flag"),
         "no-regexp-y-flag": require("./rules/no-regexp-y-flag"),
+        "no-resizable-and-growable-arraybuffers": require("./rules/no-resizable-and-growable-arraybuffers"),
         "no-rest-parameters": require("./rules/no-rest-parameters"),
         "no-rest-spread-properties": require("./rules/no-rest-spread-properties"),
         "no-set": require("./rules/no-set"),
diff --git a/lib/rules/no-resizable-and-growable-arraybuffers.js b/lib/rules/no-resizable-and-growable-arraybuffers.js
new file mode 100644
index 0000000..05df3b8
--- /dev/null
+++ b/lib/rules/no-resizable-and-growable-arraybuffers.js
@@ -0,0 +1,91 @@
+"use strict"
+
+const {
+    CONSTRUCT,
+    ReferenceTracker,
+} = require("@eslint-community/eslint-utils")
+const {
+    definePrototypeMethodHandler,
+} = require("../util/define-prototype-method-handler")
+
+/**
+ * @param {Node|undefined} node
+ * @returns {boolean}
+ */
+function isSpreadElement(node) {
+    return node && node.type === "SpreadElement"
+}
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "disallow resizable and growable ArrayBuffers",
+            category: "ES2024",
+            recommended: false,
+            url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-resizable-and-growable-arraybuffers.html",
+        },
+        fixable: null,
+        messages: {
+            forbiddenForResizableArrayBuffer:
+                "ES2024 Resizable ArrayBuffer is forbidden.",
+            forbiddenForGrowableSharedArrayBuffer:
+                "ES2024 Growable SharedArrayBuffer is forbidden.",
+        },
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    aggressive: { type: "boolean" },
+                },
+                additionalProperties: false,
+            },
+        ],
+        type: "problem",
+    },
+    create(context) {
+        return {
+            ...definePrototypeMethodHandler(
+                context,
+                {
+                    ArrayBuffer: ["maxByteLength", "resizable", "resize"],
+                    SharedArrayBuffer: ["grow", "growable", "maxByteLength"],
+                },
+                {
+                    createReport({ className }) {
+                        return {
+                            messageId:
+                                className === "ArrayBuffer"
+                                    ? "forbiddenForResizableArrayBuffer"
+                                    : "forbiddenForGrowableSharedArrayBuffer",
+                        }
+                    },
+                },
+            ),
+            "Program:exit"() {
+                const tracker = new ReferenceTracker(context.getScope())
+                for (const { node, path } of tracker.iterateGlobalReferences({
+                    ArrayBuffer: { [CONSTRUCT]: true },
+                    SharedArrayBuffer: { [CONSTRUCT]: true },
+                })) {
+                    if (node.type !== "NewExpression") {
+                        continue
+                    }
+                    const args = node.arguments.slice(0, 2)
+                    if (args.some(isSpreadElement)) {
+                        continue
+                    }
+                    const reportedNode = args[1]
+                    if (reportedNode) {
+                        context.report({
+                            node: reportedNode,
+                            messageId:
+                                path[0] === "ArrayBuffer"
+                                    ? "forbiddenForResizableArrayBuffer"
+                                    : "forbiddenForGrowableSharedArrayBuffer",
+                        })
+                    }
+                }
+            },
+        }
+    },
+}
diff --git a/lib/util/define-prototype-method-handler/index.js b/lib/util/define-prototype-method-handler/index.js
index e4ce6d1..432185c 100644
--- a/lib/util/define-prototype-method-handler/index.js
+++ b/lib/util/define-prototype-method-handler/index.js
@@ -7,6 +7,7 @@ const { buildObjectTypeCheckerForTS } = require("./object-type-checker-for-ts")
 /**
  * @typedef {object} CreateReportArgument
  * @property {true | 'aggressive'} objectTypeResult
+ * @property {string} className
  * @property {string} propertyName
  * @property {MemberExpression} node
  */
@@ -87,6 +88,7 @@ function definePrototypeMethodHandler(context, nameMap, options) {
                             options.createReport &&
                             options.createReport({
                                 objectTypeResult,
+                                className,
                                 propertyName,
                                 node,
                             })) ||
@@ -101,9 +103,10 @@ function definePrototypeMethodHandler(context, nameMap, options) {
         MemberExpression(node) {
             const propertyName = getPropertyName(node, context.getScope())
             for (const [className, methodNames] of nameMapEntries) {
+                let objectTypeResult = undefined
                 if (
                     methodNames.includes(propertyName) &&
-                    checkObjectType(node, className)
+                    (objectTypeResult = checkObjectType(node, className))
                 ) {
                     context.report({
                         node,
@@ -111,6 +114,15 @@ function definePrototypeMethodHandler(context, nameMap, options) {
                         data: {
                             name: `${className}.prototype.${propertyName}`,
                         },
+                        ...((options &&
+                            options.createReport &&
+                            options.createReport({
+                                objectTypeResult,
+                                className,
+                                propertyName,
+                                node,
+                            })) ||
+                            {}),
                     })
                     return
                 }


Test patch:
diff --git a/tests/lib/rules/no-resizable-and-growable-arraybuffers.js b/tests/lib/rules/no-resizable-and-growable-arraybuffers.js
new file mode 100644
index 0000000..b00edd1
--- /dev/null
+++ b/tests/lib/rules/no-resizable-and-growable-arraybuffers.js
@@ -0,0 +1,223 @@
+"use strict"
+
+const path = require("path")
+const RuleTester = require("../../tester")
+const rule = require("../../../lib/rules/no-resizable-and-growable-arraybuffers.js")
+const ruleId = "no-resizable-and-growable-arraybuffers"
+
+const testerOptions = {
+    env: { browser: true },
+    globals: { ArrayBuffer: false, SharedArrayBuffer: false },
+}
+new RuleTester(testerOptions).run(ruleId, rule, {
+    valid: [
+        "new ArrayBuffer(8);",
+        "new SharedArrayBuffer(1024);",
+        "resize(8); grow(8);",
+        "foo.reverse()",
+        "foo.resize(8); foo.grow(8);",
+        {
+            code: "resize(8); grow(8);",
+            settings: { "es-x": { aggressive: true } },
+        },
+        { code: "foo.reverse()", settings: { "es-x": { aggressive: true } } },
+        {
+            code: "foo.resize(8); foo.grow(8);",
+            options: [{ aggressive: false }],
+            settings: { "es-x": { aggressive: true } },
+        },
+    ],
+    invalid: [
+        {
+            code: "const buffer = new ArrayBuffer(8, { maxByteLength: 16 });",
+            errors: [
+                {
+                    message: "ES2024 Resizable ArrayBuffer is forbidden.",
+                    line: 1,
+                    column: 35,
+                },
+            ],
+        },
+        {
+            code: "const buffer = new SharedArrayBuffer(8, { maxByteLength: 16 });",
+            errors: [
+                {
+                    message: "ES2024 Growable SharedArrayBuffer is forbidden.",
+                    line: 1,
+                    column: 41,
+                },
+            ],
+        },
+        {
+            code: "const buffer = new window.ArrayBuffer(8, { maxByteLength: 16 });",
+            errors: [
+                {
+                    message: "ES2024 Resizable ArrayBuffer is forbidden.",
+                    line: 1,
+                    column: 42,
+                },
+            ],
+        },
+        {
+            code: "const buffer = new ArrayBuffer(8, { maxByteLength: 16 }); buffer.resize(8)",
+            errors: [
+                {
+                    message: "ES2024 Resizable ArrayBuffer is forbidden.",
+                    line: 1,
+                    column: 35,
+                },
+                {
+                    message: "ES2024 Resizable ArrayBuffer is forbidden.",
+                    line: 1,
+                    column: 59,
+                },
+            ],
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            code: "const buffer = new SharedArrayBuffer(8, { maxByteLength: 16 }); buffer.grow(8)",
+            options: [{ aggressive: true }],
+            errors: [
+                {
+                    message: "ES2024 Growable SharedArrayBuffer is forbidden.",
+                    line: 1,
+                    column: 41,
+                },
+                {
+                    message: "ES2024 Growable SharedArrayBuffer is forbidden.",
+                    line: 1,
+                    column: 65,
+                },
+            ],
+            settings: { "es-x": { aggressive: false } },
+        },
+        {
+            code: `
+            const buffer = new ArrayBuffer(8, { maxByteLength: 16 });
+            console.log(buffer.maxByteLength);
+            console.log(buffer.resizable);`,
+            errors: [
+                {
+                    message: "ES2024 Resizable ArrayBuffer is forbidden.",
+                    line: 2,
+                    column: 47,
+                },
+                {
+                    message: "ES2024 Resizable ArrayBuffer is forbidden.",
+                    line: 3,
+                    column: 25,
+                },
+                {
+                    message: "ES2024 Resizable ArrayBuffer is forbidden.",
+                    line: 4,
+                    column: 25,
+                },
+            ],
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            code: `
+            const buffer = new SharedArrayBuffer(8, { maxByteLength: 16 });
+            console.log(buffer.maxByteLength);
+            console.log(buffer.growable);`,
+            errors: [
+                {
+                    message: "ES2024 Growable SharedArrayBuffer is forbidden.",
+                    line: 2,
+                    column: 53,
+                },
+                {
+                    message: "ES2024 Resizable ArrayBuffer is forbidden.",
+                    line: 3,
+                    column: 25,
+                },
+                {
+                    message: "ES2024 Growable SharedArrayBuffer is forbidden.",
+                    line: 4,
+                    column: 25,
+                },
+            ],
+            settings: { "es-x": { aggressive: true } },
+        },
+    ],
+})
+
+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({
+    ...testerOptions,
+    parser,
+    parserOptions: { tsconfigRootDir, project },
+}).run(`${ruleId} TS Full Type Information`, rule, {
+    valid: [
+        { filename, code: "resize(8); grow(8);" },
+        { filename, code: "foo.reverse()" },
+        { filename, code: "foo.resize(8); foo.grow(8);" },
+        { filename, code: "let foo = {}; foo.resize(8); foo.grow(8);" },
+        {
+            filename,
+            code: "resize(8); grow(8);",
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            filename,
+            code: "foo.reverse()",
+            settings: { "es-x": { aggressive: true } },
+        },
+        {
+            filename,
+            code: "function fn(a: any) { a.resize(8) }",
+        },
+        {
+            filename,
+            code: "function fn(a: any) { console.log(a.resizable) }",
+        },
+        {
+            filename,
+            code: "function fn(a: any) { console.log(a.maxByteLength) }",
+        },
+        {
+            filename,
+            code: "function fn(a: any) { a.grow(8) }",
+        },
+        {
+            filename,
+            code: "function fn(a: any) { console.log(a.growable) }",
+        },
+    ],
+    invalid: [
+        {
+            filename,
+            code: "function fn(a: ArrayBuffer) { a.resize(8) }",
+            errors: ["ES2024 Resizable ArrayBuffer is forbidden."],
+        },
+        {
+            filename,
+            code: "function fn(a: ArrayBuffer) { console.log(a.resizable) }",
+            errors: ["ES2024 Resizable ArrayBuffer is forbidden."],
+        },
+        {
+            filename,
+            code: "function fn(a: ArrayBuffer) { console.log(a.maxByteLength) }",
+            errors: ["ES2024 Resizable ArrayBuffer is forbidden."],
+        },
+        {
+            filename,
+            code: "function fn(a: SharedArrayBuffer) { a.grow(8) }",
+            errors: ["ES2024 Growable SharedArrayBuffer is forbidden."],
+        },
+        {
+            filename,
+            code: "function fn(a: SharedArrayBuffer) { console.log(a.growable) }",
+            errors: ["ES2024 Growable SharedArrayBuffer is forbidden."],
+        },
+        {
+            filename,
+            code: "function fn(a: SharedArrayBuffer) { console.log(a.maxByteLength) }",
+            errors: ["ES2024 Growable SharedArrayBuffer is forbidden."],
+        },
+    ],
+})