Repository: eslint-community/eslint-plugin-es-x
Pull request #209 (2024-10-31 02:43:00)

Add `es-x/no-import-attributes` rules
This PR adds the following rules:

- `es-x/no-import-attributes`
- `es-x/no-dynamic-import-options`
- `es-x/no-trailing-dynamic-import-commas`

This PR also adds the `no-import-attributes` config


related to https://github.com/eslint-community/eslint-plugin-es-x/issues/201


Patch:
diff --git a/docs/configs/index.md b/docs/configs/index.md
index 110fdf1..dd99256 100644
--- a/docs/configs/index.md
+++ b/docs/configs/index.md
@@ -1003,6 +1003,34 @@ export default [
 }
 ```
 
+## no-import-attributes
+
+disallow proposal ES2025 [Import Attributes](https://github.com/tc39/proposal-import-attributes)\
+⚠️ This config will be changed in the minor versions of this plugin.
+
+This configs includes rules for [es-x/no-dynamic-import-options](../rules/no-dynamic-import-options.md), [es-x/no-import-attributes](../rules/no-import-attributes.md), and [es-x/no-trailing-dynamic-import-commas](../rules/no-trailing-dynamic-import-commas.md).
+
+### [Config (Flat Config)]
+
+eslint.config.js:
+
+```js
+import pluginESx from "eslint-plugin-es-x"
+export default [
+    pluginESx.configs['flat/no-import-attributes']
+]
+```
+
+### [Legacy Config]
+
+.eslintrc.*:
+
+```json
+{
+    "extends": ["plugin:es-x/no-import-attributes"],
+}
+```
+
 ## no-set-methods
 
 disallow proposal ES2025 [Set Methods for JavaScript](https://github.com/tc39/proposal-set-methods)\
diff --git a/docs/rules/index.md b/docs/rules/index.md
index ab101f2..707092a 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -10,6 +10,8 @@ There is a config that enables the rules in this category: [`no-new-in-esnext`]
 
 | Rule ID | Description |    |
 |:--------|:------------|:--:|
+| [es-x/no-dynamic-import-options](./no-dynamic-import-options.md) | disallow second parameter to `import()`. |  |
+| [es-x/no-import-attributes](./no-import-attributes.md) | disallow Import Attributes. |  |
 | [es-x/no-regexp-duplicate-named-capturing-groups](./no-regexp-duplicate-named-capturing-groups.md) | disallow RegExp duplicate named capturing groups. |  |
 | [es-x/no-regexp-modifiers](./no-regexp-modifiers.md) | disallow RegExp Modifiers. |  |
 | [es-x/no-set-prototype-difference](./no-set-prototype-difference.md) | disallow the `Set.prototype.difference` method. |  |
@@ -19,6 +21,7 @@ There is a config that enables the rules in this category: [`no-new-in-esnext`]
 | [es-x/no-set-prototype-issupersetof](./no-set-prototype-issupersetof.md) | disallow the `Set.prototype.isSupersetOf` method. |  |
 | [es-x/no-set-prototype-symmetricdifference](./no-set-prototype-symmetricdifference.md) | disallow the `Set.prototype.symmetricDifference` method. |  |
 | [es-x/no-set-prototype-union](./no-set-prototype-union.md) | disallow the `Set.prototype.union` method. |  |
+| [es-x/no-trailing-dynamic-import-commas](./no-trailing-dynamic-import-commas.md) | disallow trailing commas in `import()`. | 🔧 |
 
 ## ES2024
 
diff --git a/docs/rules/no-class-instance-fields.md b/docs/rules/no-class-instance-fields.md
index ef11dfa..7af640d 100644
--- a/docs/rules/no-class-instance-fields.md
+++ b/docs/rules/no-class-instance-fields.md
@@ -9,7 +9,7 @@ since: "v8.0.0"
 
 - ✅ The following configurations enable this rule: [no-class-fields], [no-new-in-es2022], [restrict-to-es3], [restrict-to-es5], [restrict-to-es2015], [restrict-to-es2016], [restrict-to-es2017], [restrict-to-es2018], [restrict-to-es2019], [restrict-to-es2020], and [restrict-to-es2021]
 
-This rule reports class fields as errors.
+This rule reports instance class fields as errors.
 
 ## 💡 Examples
 
diff --git a/docs/rules/no-class-private-fields.md b/docs/rules/no-class-private-fields.md
index 561ebfa..f0a707b 100644
--- a/docs/rules/no-class-private-fields.md
+++ b/docs/rules/no-class-private-fields.md
@@ -9,7 +9,7 @@ since: "v8.0.0"
 
 - ✅ The following configurations enable this rule: [no-class-fields], [no-new-in-es2022], [restrict-to-es3], [restrict-to-es5], [restrict-to-es2015], [restrict-to-es2016], [restrict-to-es2017], [restrict-to-es2018], [restrict-to-es2019], [restrict-to-es2020], and [restrict-to-es2021]
 
-This rule reports class fields as errors.
+This rule reports private class fields as errors.
 
 ## 💡 Examples
 
diff --git a/docs/rules/no-class-private-methods.md b/docs/rules/no-class-private-methods.md
index 6c10985..36e4e55 100644
--- a/docs/rules/no-class-private-methods.md
+++ b/docs/rules/no-class-private-methods.md
@@ -9,7 +9,7 @@ since: "v8.0.0"
 
 - ✅ The following configurations enable this rule: [no-class-fields], [no-new-in-es2022], [restrict-to-es3], [restrict-to-es5], [restrict-to-es2015], [restrict-to-es2016], [restrict-to-es2017], [restrict-to-es2018], [restrict-to-es2019], [restrict-to-es2020], and [restrict-to-es2021]
 
-This rule reports class fields as errors.
+This rule reports private class methods as errors.
 
 ## 💡 Examples
 
diff --git a/docs/rules/no-class-static-fields.md b/docs/rules/no-class-static-fields.md
index 4c09918..6a401bb 100644
--- a/docs/rules/no-class-static-fields.md
+++ b/docs/rules/no-class-static-fields.md
@@ -9,7 +9,7 @@ since: "v8.0.0"
 
 - ✅ The following configurations enable this rule: [no-class-fields], [no-new-in-es2022], [restrict-to-es3], [restrict-to-es5], [restrict-to-es2015], [restrict-to-es2016], [restrict-to-es2017], [restrict-to-es2018], [restrict-to-es2019], [restrict-to-es2020], and [restrict-to-es2021]
 
-This rule reports class fields as errors.
+This rule reports static class fields as errors.
 
 ## 💡 Examples
 
diff --git a/docs/rules/no-dynamic-import-options.md b/docs/rules/no-dynamic-import-options.md
new file mode 100644
index 0000000..3530ba0
--- /dev/null
+++ b/docs/rules/no-dynamic-import-options.md
@@ -0,0 +1,34 @@
+---
+title: "es-x/no-dynamic-import-options"
+description: "disallow second parameter to `import()`"
+---
+
+# es-x/no-dynamic-import-options
+> disallow second parameter to `import()`
+
+- ❗ <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: [no-import-attributes] and [no-new-in-esnext]
+
+This rule reports the second parameter to `import()` as errors.\
+The second parameter to `import()` is an options bag, which was added in the ES2025 [Import Attributes proposal](https://github.com/tc39/proposal-import-attributes#dynamic-import).
+
+## 💡 Examples
+
+⛔ Examples of **incorrect** code for this rule:
+
+<eslint-playground type="bad">
+
+```js
+/*eslint es-x/no-dynamic-import-options: error */
+const m = await import(source, options)
+```
+
+</eslint-playground>
+
+## 📚 References
+
+- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-dynamic-import-options.js)
+- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-dynamic-import-options.js)
+
+[no-import-attributes]: ../configs/index.md#no-import-attributes
+[no-new-in-esnext]: ../configs/index.md#no-new-in-esnext
diff --git a/docs/rules/no-import-attributes.md b/docs/rules/no-import-attributes.md
new file mode 100644
index 0000000..f6ead03
--- /dev/null
+++ b/docs/rules/no-import-attributes.md
@@ -0,0 +1,34 @@
+---
+title: "es-x/no-import-attributes"
+description: "disallow Import Attributes"
+---
+
+# es-x/no-import-attributes
+> disallow Import Attributes
+
+- ❗ <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: [no-import-attributes] and [no-new-in-esnext]
+
+This rule reports ES2025 [Import Attributes](https://github.com/tc39/proposal-import-attributes) as errors.
+
+## 💡 Examples
+
+⛔ Examples of **incorrect** code for this rule:
+
+<eslint-playground type="bad">
+
+```js
+/*eslint es-x/no-import-attributes: error */
+import json from "./foo.json" with { type: "json" };
+const a = await import("foo.json", { with: { type: "json" } });
+```
+
+</eslint-playground>
+
+## 📚 References
+
+- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-import-attributes.js)
+- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-import-attributes.js)
+
+[no-import-attributes]: ../configs/index.md#no-import-attributes
+[no-new-in-esnext]: ../configs/index.md#no-new-in-esnext
diff --git a/docs/rules/no-trailing-dynamic-import-commas.md b/docs/rules/no-trailing-dynamic-import-commas.md
new file mode 100644
index 0000000..b0afbae
--- /dev/null
+++ b/docs/rules/no-trailing-dynamic-import-commas.md
@@ -0,0 +1,43 @@
+---
+title: "es-x/no-trailing-dynamic-import-commas"
+description: "disallow trailing commas in `import()`"
+---
+
+# es-x/no-trailing-dynamic-import-commas
+> disallow trailing commas in `import()`
+
+- ❗ <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: [no-import-attributes] and [no-new-in-esnext]
+- 🔧 The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+
+This rule reports trailing commas in `import()` as errors.\
+Trailing commas in `import()` are now allowed in ES2025 [Import Attributes proposal](https://github.com/tc39/proposal-import-attributes#dynamic-import).
+
+## 💡 Examples
+
+⛔ Examples of **incorrect** code for this rule:
+
+<eslint-playground fix type="bad">
+
+```js
+/*eslint es-x/no-trailing-dynamic-import-commas: error */
+const m1 = await import(source, )
+const m2 = await import(source, options, )
+const m3 = await import(
+    source,
+)
+const m4 = await import(
+    source,
+    options,
+)
+```
+
+</eslint-playground>
+
+## 📚 References
+
+- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-trailing-dynamic-import-commas.js)
+- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-trailing-dynamic-import-commas.js)
+
+[no-import-attributes]: ../configs/index.md#no-import-attributes
+[no-new-in-esnext]: ../configs/index.md#no-new-in-esnext
diff --git a/lib/configs/flat/no-import-attributes.js b/lib/configs/flat/no-import-attributes.js
new file mode 100644
index 0000000..fe637f3
--- /dev/null
+++ b/lib/configs/flat/no-import-attributes.js
@@ -0,0 +1,18 @@
+/**
+ * DON'T EDIT THIS FILE.
+ * This file was generated by "scripts/update-lib-flat-configs.js" script.
+ */
+"use strict"
+
+module.exports = {
+    plugins: {
+        get "es-x"() {
+            return require("../../index.js")
+        },
+    },
+    rules: {
+        "es-x/no-dynamic-import-options": "error",
+        "es-x/no-import-attributes": "error",
+        "es-x/no-trailing-dynamic-import-commas": "error",
+    },
+}
diff --git a/lib/configs/flat/no-new-in-esnext.js b/lib/configs/flat/no-new-in-esnext.js
index ad55efc..ee6f4f8 100644
--- a/lib/configs/flat/no-new-in-esnext.js
+++ b/lib/configs/flat/no-new-in-esnext.js
@@ -11,6 +11,8 @@ module.exports = {
         },
     },
     rules: {
+        "es-x/no-dynamic-import-options": "error",
+        "es-x/no-import-attributes": "error",
         "es-x/no-regexp-duplicate-named-capturing-groups": "error",
         "es-x/no-regexp-modifiers": "error",
         "es-x/no-set-prototype-difference": "error",
@@ -20,5 +22,6 @@ module.exports = {
         "es-x/no-set-prototype-issupersetof": "error",
         "es-x/no-set-prototype-symmetricdifference": "error",
         "es-x/no-set-prototype-union": "error",
+        "es-x/no-trailing-dynamic-import-commas": "error",
     },
 }
diff --git a/lib/configs/no-import-attributes.js b/lib/configs/no-import-attributes.js
new file mode 100644
index 0000000..1ceaa59
--- /dev/null
+++ b/lib/configs/no-import-attributes.js
@@ -0,0 +1,14 @@
+/**
+ * DON'T EDIT THIS FILE.
+ * This file was generated by "scripts/update-lib-configs.js" script.
+ */
+"use strict"
+
+module.exports = {
+    plugins: ["es-x"],
+    rules: {
+        "es-x/no-dynamic-import-options": "error",
+        "es-x/no-import-attributes": "error",
+        "es-x/no-trailing-dynamic-import-commas": "error",
+    },
+}
diff --git a/lib/configs/no-new-in-esnext.js b/lib/configs/no-new-in-esnext.js
index ccd73fd..819f61d 100644
--- a/lib/configs/no-new-in-esnext.js
+++ b/lib/configs/no-new-in-esnext.js
@@ -7,6 +7,8 @@
 module.exports = {
     plugins: ["es-x"],
     rules: {
+        "es-x/no-dynamic-import-options": "error",
+        "es-x/no-import-attributes": "error",
         "es-x/no-regexp-duplicate-named-capturing-groups": "error",
         "es-x/no-regexp-modifiers": "error",
         "es-x/no-set-prototype-difference": "error",
@@ -16,5 +18,6 @@ module.exports = {
         "es-x/no-set-prototype-issupersetof": "error",
         "es-x/no-set-prototype-symmetricdifference": "error",
         "es-x/no-set-prototype-union": "error",
+        "es-x/no-trailing-dynamic-import-commas": "error",
     },
 }
diff --git a/lib/index.js b/lib/index.js
index 9e5aeb3..97ee0ef 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -13,6 +13,7 @@ module.exports = {
         "flat/no-array-grouping": require("./configs/flat/no-array-grouping"),
         "flat/no-change-array-by-copy": require("./configs/flat/no-change-array-by-copy"),
         "flat/no-class-fields": require("./configs/flat/no-class-fields"),
+        "flat/no-import-attributes": require("./configs/flat/no-import-attributes"),
         "flat/no-intl-numberformat-v3": require("./configs/flat/no-intl-numberformat-v3"),
         "flat/no-is-usv-string": require("./configs/flat/no-is-usv-string"),
         "flat/no-new-in-es5": require("./configs/flat/no-new-in-es5"),
@@ -64,6 +65,7 @@ module.exports = {
         "no-array-grouping": require("./configs/no-array-grouping"),
         "no-change-array-by-copy": require("./configs/no-change-array-by-copy"),
         "no-class-fields": require("./configs/no-class-fields"),
+        "no-import-attributes": require("./configs/no-import-attributes"),
         "no-intl-numberformat-v3": require("./configs/no-intl-numberformat-v3"),
         "no-is-usv-string": require("./configs/no-is-usv-string"),
         "no-new-in-es5": require("./configs/no-new-in-es5"),
@@ -192,6 +194,7 @@ module.exports = {
         "no-default-parameters": require("./rules/no-default-parameters"),
         "no-destructuring": require("./rules/no-destructuring"),
         "no-dynamic-import": require("./rules/no-dynamic-import"),
+        "no-dynamic-import-options": require("./rules/no-dynamic-import-options"),
         "no-error-cause": require("./rules/no-error-cause"),
         "no-escape-unescape": require("./rules/no-escape-unescape"),
         "no-exponential-operators": require("./rules/no-exponential-operators"),
@@ -202,6 +205,7 @@ module.exports = {
         "no-generators": require("./rules/no-generators"),
         "no-global-this": require("./rules/no-global-this"),
         "no-hashbang": require("./rules/no-hashbang"),
+        "no-import-attributes": require("./rules/no-import-attributes"),
         "no-import-meta": require("./rules/no-import-meta"),
         "no-initializers-in-for-in": require("./rules/no-initializers-in-for-in"),
         "no-intl-datetimeformat-prototype-formatrange": require("./rules/no-intl-datetimeformat-prototype-formatrange"),
@@ -351,6 +355,7 @@ module.exports = {
         "no-template-literals": require("./rules/no-template-literals"),
         "no-top-level-await": require("./rules/no-top-level-await"),
         "no-trailing-commas": require("./rules/no-trailing-commas"),
+        "no-trailing-dynamic-import-commas": require("./rules/no-trailing-dynamic-import-commas"),
         "no-trailing-function-commas": require("./rules/no-trailing-function-commas"),
         "no-typed-arrays": require("./rules/no-typed-arrays"),
         "no-unicode-codepoint-escapes": require("./rules/no-unicode-codepoint-escapes"),
diff --git a/lib/rules/no-dynamic-import-options.js b/lib/rules/no-dynamic-import-options.js
new file mode 100644
index 0000000..d6f0fd0
--- /dev/null
+++ b/lib/rules/no-dynamic-import-options.js
@@ -0,0 +1,32 @@
+"use strict"
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "disallow second parameter to `import()`",
+            category: "ES2025",
+            proposal: "import-attributes",
+            recommended: false,
+            url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-dynamic-import-options.html",
+        },
+        fixable: null,
+        messages: {
+            forbidden:
+                "ES2025 the second parameter to 'import()' is forbidden.",
+        },
+        schema: [],
+        type: "problem",
+    },
+    create(context) {
+        return {
+            ImportExpression(node) {
+                if (node.options) {
+                    context.report({
+                        node: node.options,
+                        messageId: "forbidden",
+                    })
+                }
+            },
+        }
+    },
+}
diff --git a/lib/rules/no-import-attributes.js b/lib/rules/no-import-attributes.js
new file mode 100644
index 0000000..0837ce9
--- /dev/null
+++ b/lib/rules/no-import-attributes.js
@@ -0,0 +1,95 @@
+"use strict"
+
+const {
+    findVariable,
+    getPropertyName,
+} = require("@eslint-community/eslint-utils")
+const { getSourceCode } = require("eslint-compat-utils")
+
+/**
+ * @typedef {import("estree").Expression} Expression
+ * @typedef {import("eslint").Scope.Variable} Variable
+ */
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "disallow Import Attributes.",
+            category: "ES2025",
+            recommended: false,
+            proposal: "import-attributes",
+            url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-import-attributes.html",
+        },
+        fixable: null,
+        messages: {
+            forbidden: "ES2025 Import Attributes are forbidden.",
+        },
+        schema: [],
+        type: "problem",
+    },
+    create(context) {
+        const sourceCode = getSourceCode(context)
+
+        /** @param {Expression} optionsNode  */
+        function findImportAttributeFromOptions(optionsNode) {
+            const traversed = new Set()
+            return find(optionsNode)
+
+            /** @param {Expression} node  */
+            function find(node) {
+                if (traversed.has(node)) {
+                    return null
+                }
+                traversed.add(node)
+                if (node.type === "ObjectExpression") {
+                    for (const prop of node.properties) {
+                        if (prop.type !== "Property") {
+                            continue
+                        }
+                        if (
+                            getPropertyName(prop, sourceCode.getScope(node)) ===
+                            "with"
+                        ) {
+                            return prop
+                        }
+                    }
+                }
+                if (node.type === "Identifier") {
+                    /** @type {Variable} */
+                    const variable = findVariable(
+                        sourceCode.getScope(node),
+                        node,
+                    )
+                    const def = variable?.defs[0]
+                    if (!def) {
+                        return null
+                    }
+                    if (def.type !== "Variable" || !def.node.init) {
+                        return null
+                    }
+                    return find(def.node.init)
+                }
+                return null
+            }
+        }
+
+        return {
+            ImportAttribute(node) {
+                context.report({ node, messageId: "forbidden" })
+            },
+            ImportExpression(node) {
+                if (!node.options) {
+                    return
+                }
+
+                const attributes = findImportAttributeFromOptions(node.options)
+                if (attributes) {
+                    context.report({
+                        node: attributes,
+                        messageId: "forbidden",
+                    })
+                }
+            },
+        }
+    },
+}
diff --git a/lib/rules/no-trailing-dynamic-import-commas.js b/lib/rules/no-trailing-dynamic-import-commas.js
new file mode 100644
index 0000000..0a55be1
--- /dev/null
+++ b/lib/rules/no-trailing-dynamic-import-commas.js
@@ -0,0 +1,40 @@
+"use strict"
+
+const { getSourceCode } = require("eslint-compat-utils")
+const { isCommaToken } = require("../utils")
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "disallow trailing commas in `import()`",
+            category: "ES2025",
+            proposal: "import-attributes",
+            recommended: false,
+            url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-trailing-dynamic-import-commas.html",
+        },
+        fixable: "code",
+        messages: {
+            forbidden: "ES2025 trailing commas in 'import()' are forbidden.",
+        },
+        schema: [],
+        type: "problem",
+    },
+    create(context) {
+        const sourceCode = getSourceCode(context)
+        return {
+            ImportExpression(node) {
+                const lastParam = node.options || node.source
+                const token = sourceCode.getTokenAfter(lastParam)
+                if (isCommaToken(token)) {
+                    context.report({
+                        loc: token.loc,
+                        messageId: "forbidden",
+                        fix(fixer) {
+                            return fixer.remove(token)
+                        },
+                    })
+                }
+            },
+        }
+    },
+}
diff --git a/scripts/proposals.js b/scripts/proposals.js
index 2ec0877..790b063 100644
--- a/scripts/proposals.js
+++ b/scripts/proposals.js
@@ -29,4 +29,8 @@ module.exports = {
         title: "Set Methods for JavaScript",
         link: "https://github.com/tc39/proposal-set-methods",
     },
+    "import-attributes": {
+        title: "Import Attributes",
+        link: "https://github.com/tc39/proposal-import-attributes",
+    },
 }


Test patch:
diff --git a/tests/lib/rules/no-dynamic-import-options.js b/tests/lib/rules/no-dynamic-import-options.js
new file mode 100644
index 0000000..24be8a8
--- /dev/null
+++ b/tests/lib/rules/no-dynamic-import-options.js
@@ -0,0 +1,22 @@
+"use strict"
+
+const RuleTester = require("../../tester")
+const rule = require("../../../lib/rules/no-dynamic-import-options.js")
+
+if (!RuleTester.isSupported(2025)) {
+    //eslint-disable-next-line no-console
+    console.log("Skip the tests of no-dynamic-import-options.")
+    return
+}
+
+new RuleTester({
+    languageOptions: { sourceType: "module" },
+}).run("no-dynamic-import-options", rule, {
+    valid: ["import(source)"],
+    invalid: [
+        {
+            code: "const module = await import(source, options)",
+            errors: ["ES2025 the second parameter to 'import()' is forbidden."],
+        },
+    ],
+})
diff --git a/tests/lib/rules/no-import-attributes.js b/tests/lib/rules/no-import-attributes.js
new file mode 100644
index 0000000..65b9b00
--- /dev/null
+++ b/tests/lib/rules/no-import-attributes.js
@@ -0,0 +1,44 @@
+"use strict"
+
+const RuleTester = require("../../tester")
+const rule = require("../../../lib/rules/no-import-attributes.js")
+
+if (!RuleTester.isSupported(2025)) {
+    //eslint-disable-next-line no-console
+    console.log("Skip the tests of no-import-attributes.")
+    return
+}
+
+new RuleTester({
+    languageOptions: { sourceType: "module" },
+}).run("no-import-attributes", rule, {
+    valid: [
+        "import foo from 'foo'",
+        "export {foo} from 'foo'",
+        "export * from 'foo'",
+        "import('foo')",
+        "import('foo', unknown)",
+    ],
+    invalid: [
+        {
+            code: "import foo from 'foo' with { type: 'json' }",
+            errors: ["ES2025 Import Attributes are forbidden."],
+        },
+        {
+            code: "export {foo} from 'foo' with { type: 'json' }",
+            errors: ["ES2025 Import Attributes are forbidden."],
+        },
+        {
+            code: "export * from 'foo' with { type: 'json' }",
+            errors: ["ES2025 Import Attributes are forbidden."],
+        },
+        {
+            code: "import('foo', { with: { type: 'json'} })",
+            errors: ["ES2025 Import Attributes are forbidden."],
+        },
+        {
+            code: "import('foo', { with: unknown })",
+            errors: ["ES2025 Import Attributes are forbidden."],
+        },
+    ],
+})
diff --git a/tests/lib/rules/no-trailing-dynamic-import-commas.js b/tests/lib/rules/no-trailing-dynamic-import-commas.js
new file mode 100644
index 0000000..d46332d
--- /dev/null
+++ b/tests/lib/rules/no-trailing-dynamic-import-commas.js
@@ -0,0 +1,28 @@
+"use strict"
+
+const RuleTester = require("../../tester")
+const rule = require("../../../lib/rules/no-trailing-dynamic-import-commas.js")
+
+if (!RuleTester.isSupported(2025)) {
+    //eslint-disable-next-line no-console
+    console.log("Skip the tests of no-trailing-dynamic-import-commas.")
+    return
+}
+
+new RuleTester({
+    languageOptions: { sourceType: "module" },
+}).run("no-trailing-dynamic-import-commas", rule, {
+    valid: ["import(source)", "import(source, options)"],
+    invalid: [
+        {
+            code: "import(source,)",
+            output: "import(source)",
+            errors: ["ES2025 trailing commas in 'import()' are forbidden."],
+        },
+        {
+            code: "import(source, options,)",
+            output: "import(source, options)",
+            errors: ["ES2025 trailing commas in 'import()' are forbidden."],
+        },
+    ],
+})