Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/website/content/docs/rules/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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` | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ react-dom/no-missing-iframe-sandbox
@eslint-react/dom/no-missing-iframe-sandbox
```

**Features**

`🔧`

**Presets**

- `dom`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ ruleTester.run(RULE_NAME, rule, {
errors: [
{
messageId: "noMissingIframeSandbox",
suggestions: [
{
messageId: "addIframeSandbox",
data: { value: "" },
output: tsx`<iframe sandbox="" />;`,
},
],
},
],
},
Expand All @@ -18,14 +25,28 @@ ruleTester.run(RULE_NAME, rule, {
errors: [
{
messageId: "noMissingIframeSandbox",
suggestions: [
{
messageId: "addIframeSandbox",
data: { value: "" },
output: tsx`<iframe sandbox="" />;`,
},
],
},
],
},
{
code: tsx`<PolyComponent as="iframe"/>;`,
code: tsx`<PolyComponent as="iframe" />;`,
errors: [
{
messageId: "noMissingIframeSandbox",
suggestions: [
{
messageId: "addIframeSandbox",
data: { value: "" },
output: tsx`<PolyComponent sandbox="" as="iframe" />;`,
},
],
},
],
settings: {
Expand All @@ -39,6 +60,13 @@ ruleTester.run(RULE_NAME, rule, {
errors: [
{
messageId: "noMissingIframeSandbox",
suggestions: [
{
messageId: "addIframeSandbox",
data: { value: "" },
output: tsx`<PolyComponent as="iframe" sandbox="" />;`,
},
],
},
],
settings: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof RULE_NAME>;
export type MessageID = CamelCase<typeof RULE_NAME> | 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: {
Expand All @@ -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: [],
Expand Down Expand Up @@ -74,17 +55,36 @@ export function create(context: RuleContext<MessageID, []>): 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}=""`);
},
},
],
});
}
},
Expand Down
Loading