diff --git a/apps/website/content/docs/rules/overview.mdx b/apps/website/content/docs/rules/overview.mdx
index 731009d35f..ef77447088 100644
--- a/apps/website/content/docs/rules/overview.mdx
+++ b/apps/website/content/docs/rules/overview.mdx
@@ -102,7 +102,7 @@ The `jsx-*` rules check for issues exclusive to JSX syntax, which are absent fro
| [`no-flush-sync`](./dom-no-flush-sync) | 2️⃣ | | Disallow `flushSync` | |
| [`no-hydrate`](./dom-no-hydrate) | 2️⃣ | `🔄` | Replaces usages of `ReactDom.hydrate()` with `hydrateRoot()` | >=18.0.0 |
| [`no-missing-button-type`](./dom-no-missing-button-type) | 1️⃣ | `🔧` | Enforces explicit `type` attribute for `button` elements | |
-| [`no-missing-iframe-sandbox`](./dom-no-missing-iframe-sandbox) | 1️⃣ | | Enforces explicit `sandbox` attribute for `iframe` elements | |
+| [`no-missing-iframe-sandbox`](./dom-no-missing-iframe-sandbox) | 1️⃣ | `🔧` | Enforces explicit `sandbox` attribute for `iframe` elements | |
| [`no-namespace`](./dom-no-namespace) | 2️⃣ | | Enforces the absence of a `namespace` in React elements | |
| [`no-render`](./dom-no-render) | 2️⃣ | `🔄` | Replaces usages of `ReactDom.render()` with `createRoot(node).render()` | >=18.0.0 |
| [`no-render-return-value`](./dom-no-render-return-value) | 2️⃣ | | Disallow the return value of `ReactDOM.render` | |
diff --git a/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.md b/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.md
index 6caf43f83b..933c0ac2c9 100644
--- a/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.md
+++ b/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.md
@@ -14,6 +14,10 @@ react-dom/no-missing-iframe-sandbox
@eslint-react/dom/no-missing-iframe-sandbox
```
+**Features**
+
+`🔧`
+
**Presets**
- `dom`
diff --git a/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.spec.ts b/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.spec.ts
index 240b4c8fc7..dcea7ffb6e 100644
--- a/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.spec.ts
+++ b/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.spec.ts
@@ -10,6 +10,13 @@ ruleTester.run(RULE_NAME, rule, {
errors: [
{
messageId: "noMissingIframeSandbox",
+ suggestions: [
+ {
+ messageId: "addIframeSandbox",
+ data: { value: "" },
+ output: tsx`;`,
+ },
+ ],
},
],
},
@@ -18,14 +25,28 @@ ruleTester.run(RULE_NAME, rule, {
errors: [
{
messageId: "noMissingIframeSandbox",
+ suggestions: [
+ {
+ messageId: "addIframeSandbox",
+ data: { value: "" },
+ output: tsx`;`,
+ },
+ ],
},
],
},
{
- code: tsx`;`,
+ code: tsx`;`,
errors: [
{
messageId: "noMissingIframeSandbox",
+ suggestions: [
+ {
+ messageId: "addIframeSandbox",
+ data: { value: "" },
+ output: tsx`;`,
+ },
+ ],
},
],
settings: {
@@ -39,6 +60,13 @@ ruleTester.run(RULE_NAME, rule, {
errors: [
{
messageId: "noMissingIframeSandbox",
+ suggestions: [
+ {
+ messageId: "addIframeSandbox",
+ data: { value: "" },
+ output: tsx`;`,
+ },
+ ],
},
],
settings: {
diff --git a/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.ts b/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.ts
index 884fe45ec5..139911e9d6 100644
--- a/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.ts
+++ b/packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.ts
@@ -7,35 +7,13 @@ import { createJsxElementResolver, createRule, findCustomComponentProp } from ".
export const RULE_NAME = "no-missing-iframe-sandbox";
-export const RULE_FEATURES = [] as const satisfies RuleFeature[];
+export const RULE_FEATURES = [
+ "FIX",
+] as const satisfies RuleFeature[];
-export type MessageID = CamelCase;
+export type MessageID = CamelCase | RuleSuggestMessageID;
-const validTypes = [
- "",
- "allow-downloads",
- "allow-downloads-without-user-activation",
- "allow-forms",
- "allow-modals",
- "allow-orientation-lock",
- "allow-pointer-lock",
- "allow-popups",
- "allow-popups-to-escape-sandbox",
- "allow-presentation",
- "allow-same-origin",
- "allow-scripts",
- "allow-storage-access-by-user-activation",
- "allow-top-navigation",
- "allow-top-navigation-by-user-activation",
- "allow-top-navigation-to-custom-protocols",
-] as const;
-
-function hasValidSandBox(value: unknown) {
- return typeof value === "string"
- && value
- .split(" ")
- .every((value) => validTypes.some((valid) => valid === value));
-}
+export type RuleSuggestMessageID = "addIframeSandbox";
export default createRule<[], MessageID>({
meta: {
@@ -44,7 +22,10 @@ export default createRule<[], MessageID>({
description: "Enforces explicit `sandbox` attribute for `iframe` elements.",
[Symbol.for("rule_features")]: RULE_FEATURES,
},
+ fixable: "code",
+ hasSuggestions: true,
messages: {
+ addIframeSandbox: "Add 'sandbox' attribute with value '{{value}}'.",
noMissingIframeSandbox: "Add missing 'sandbox' attribute on 'iframe' component.",
},
schema: [],
@@ -74,17 +55,36 @@ export function create(context: RuleContext): RuleListener {
attributeNode,
propNameOnJsx,
);
- if (attributeValue.kind === "some" && hasValidSandBox(attributeValue.value)) return;
- context.report({
- messageId: "noMissingIframeSandbox",
- node: attributeNode,
- });
+ if (attributeValue.kind !== "some" || typeof attributeValue.value !== "string") {
+ context.report({
+ messageId: "noMissingIframeSandbox",
+ node: attributeNode,
+ suggest: [
+ {
+ messageId: "addIframeSandbox",
+ data: { value: "" },
+ fix(fixer) {
+ return fixer.replaceText(attributeNode, `${propNameOnJsx}=""`);
+ },
+ },
+ ],
+ });
+ }
return;
}
- if (!hasValidSandBox(customComponentProp?.defaultValue)) {
+ if (typeof customComponentProp?.defaultValue !== "string") {
context.report({
messageId: "noMissingIframeSandbox",
node,
+ suggest: [
+ {
+ messageId: "addIframeSandbox",
+ data: { value: "" },
+ fix(fixer) {
+ return fixer.insertTextAfter(node.openingElement.name, ` ${propNameOnJsx}=""`);
+ },
+ },
+ ],
});
}
},