eslint-plugin-react-dom
Version:
ESLint React's ESLint plugin for React DOM related rules.
1,930 lines (1,909 loc) • 54.6 kB
JavaScript
import { DEFAULT_ESLINT_REACT_SETTINGS, RE_JAVASCRIPT_PROTOCOL, WEBSITE_URL, getConfigAdapters, getSettingsFromContext } from "@eslint-react/shared";
import { getJsxAttribute, getJsxElementType, isJsxHostElement, isJsxText, resolveJsxAttributeValue } from "@eslint-react/core";
import { ESLintUtils } from "@typescript-eslint/utils";
import { AST_NODE_TYPES } from "@typescript-eslint/types";
import { compare } from "compare-versions";
//#region rolldown:runtime
var __defProp = Object.defineProperty;
var __export = (all, symbols) => {
let target = {};
for (var name$3 in all) {
__defProp(target, name$3, {
get: all[name$3],
enumerable: true
});
}
if (symbols) {
__defProp(target, Symbol.toStringTag, { value: "Module" });
}
return target;
};
//#endregion
//#region package.json
var name$2 = "eslint-plugin-react-dom";
var version = "2.4.0";
//#endregion
//#region src/utils/create-jsx-element-resolver.ts
/**
* Creates a resolver for JSX elements that determines both the JSX element type
* and the underlying DOM element type.
*
* This resolver handles:
* 1. Regular HTML elements (div, span, etc.)
* 2. Polymorphic components (components that can render as different elements via a prop)
*
* @param context The ESLint rule context
* @returns An object with a resolve method to determine element types
*/
function createJsxElementResolver(context) {
const { polymorphicPropName } = getSettingsFromContext(context);
return { resolve(node) {
const elementName = getJsxElementType(context, node);
const result = {
domElementType: elementName,
jsxElementType: elementName
};
if (elementName === elementName.toLowerCase() || polymorphicPropName == null) return result;
const polymorphicProp = getJsxAttribute(context, node)(polymorphicPropName);
if (polymorphicProp != null) {
const staticValue = resolveJsxAttributeValue(context, polymorphicProp).toStatic(polymorphicPropName);
if (typeof staticValue === "string") return {
...result,
domElementType: staticValue
};
}
return result;
} };
}
//#endregion
//#region src/utils/create-rule.ts
function getDocsUrl(ruleName) {
return `${WEBSITE_URL}/docs/rules/dom-${ruleName}`;
}
const createRule = ESLintUtils.RuleCreator(getDocsUrl);
//#endregion
//#region src/rules/no-dangerously-set-innerhtml.ts
const RULE_NAME$17 = "no-dangerously-set-innerhtml";
const DSIH$1 = "dangerouslySetInnerHTML";
var no_dangerously_set_innerhtml_default = createRule({
meta: {
type: "problem",
docs: { description: "Disallow `dangerouslySetInnerHTML`." },
messages: { noDangerouslySetInnerhtml: "Using 'dangerouslySetInnerHTML' may have security implications." },
schema: []
},
name: RULE_NAME$17,
create: create$17,
defaultOptions: []
});
function create$17(context) {
if (!context.sourceCode.text.includes(DSIH$1)) return {};
return { JSXElement(node) {
const dsihProp = getJsxAttribute(context, node)(DSIH$1);
if (dsihProp == null) return;
context.report({
messageId: "noDangerouslySetInnerhtml",
node: dsihProp
});
} };
}
//#endregion
//#region src/rules/no-dangerously-set-innerhtml-with-children.ts
const RULE_NAME$16 = "no-dangerously-set-innerhtml-with-children";
var no_dangerously_set_innerhtml_with_children_default = createRule({
meta: {
type: "problem",
docs: { description: "Disallow `dangerouslySetInnerHTML` and `children` at the same time." },
messages: { noDangerouslySetInnerhtmlWithChildren: "A DOM component cannot use both children and 'dangerouslySetInnerHTML'." },
schema: []
},
name: RULE_NAME$16,
create: create$16,
defaultOptions: []
});
const DSIH = "dangerouslySetInnerHTML";
/**
* Checks if a JSX child node is considered significant (i.e., not just whitespace for formatting)
* @param node The JSX child node to check
* @returns `true` if the node is significant, `false` otherwise
*/
function isSignificantChildren(node) {
if (!isJsxText(node)) return true;
return !(node.raw.trim() === "" && node.raw.includes("\n"));
}
function create$16(context) {
if (!context.sourceCode.text.includes(DSIH)) return {};
return { JSXElement(node) {
const findJsxAttribute = getJsxAttribute(context, node);
if (findJsxAttribute(DSIH) == null) return;
const childrenPropOrNode = findJsxAttribute("children") ?? node.children.find(isSignificantChildren);
if (childrenPropOrNode == null) return;
context.report({
messageId: "noDangerouslySetInnerhtmlWithChildren",
node: childrenPropOrNode
});
} };
}
//#endregion
//#region src/rules/no-find-dom-node.ts
const RULE_NAME$15 = "no-find-dom-node";
var no_find_dom_node_default = createRule({
meta: {
type: "problem",
docs: { description: "Disallow `findDOMNode`." },
messages: { noFindDomNode: "[Deprecated] Use alternatives instead." },
schema: []
},
name: RULE_NAME$15,
create: create$15,
defaultOptions: []
});
const findDOMNode = "findDOMNode";
function create$15(context) {
if (!context.sourceCode.text.includes(findDOMNode)) return {};
return { CallExpression(node) {
const { callee } = node;
switch (callee.type) {
case AST_NODE_TYPES.Identifier:
if (callee.name === findDOMNode) context.report({
messageId: "noFindDomNode",
node
});
return;
case AST_NODE_TYPES.MemberExpression:
if (callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === findDOMNode) context.report({
messageId: "noFindDomNode",
node
});
return;
}
} };
}
//#endregion
//#region src/rules/no-flush-sync.ts
const RULE_NAME$14 = "no-flush-sync";
var no_flush_sync_default = createRule({
meta: {
type: "problem",
docs: { description: "Disallow `flushSync`." },
messages: { noFlushSync: "Using 'flushSync' is uncommon and can hurt the performance of your app." },
schema: []
},
name: RULE_NAME$14,
create: create$14,
defaultOptions: []
});
const flushSync = "flushSync";
function create$14(context) {
if (!context.sourceCode.text.includes(flushSync)) return {};
return { CallExpression(node) {
const { callee } = node;
switch (callee.type) {
case AST_NODE_TYPES.Identifier:
if (callee.name === flushSync) context.report({
messageId: "noFlushSync",
node
});
return;
case AST_NODE_TYPES.MemberExpression:
if (callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === flushSync) context.report({
messageId: "noFlushSync",
node
});
return;
}
} };
}
//#endregion
//#region src/rules/no-hydrate.ts
const RULE_NAME$13 = "no-hydrate";
var no_hydrate_default = createRule({
meta: {
type: "problem",
docs: { description: "Replaces usages of `ReactDom.hydrate()` with `hydrateRoot()`." },
fixable: "code",
messages: { noHydrate: "[Deprecated] Use 'hydrateRoot()' instead." },
schema: []
},
name: RULE_NAME$13,
create: create$13,
defaultOptions: []
});
const hydrate = "hydrate";
function create$13(context) {
if (!context.sourceCode.text.includes(hydrate)) return {};
if (compare(getSettingsFromContext(context).version, "18.0.0", "<")) return {};
const reactDomNames = /* @__PURE__ */ new Set();
const hydrateNames = /* @__PURE__ */ new Set();
return {
CallExpression(node) {
switch (true) {
case node.callee.type === AST_NODE_TYPES.Identifier && hydrateNames.has(node.callee.name):
context.report({
messageId: "noHydrate",
node,
fix: getFix$2(context, node)
});
return;
case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.object.type === AST_NODE_TYPES.Identifier && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === hydrate && reactDomNames.has(node.callee.object.name):
context.report({
messageId: "noHydrate",
node,
fix: getFix$2(context, node)
});
return;
}
},
ImportDeclaration(node) {
const [baseSource] = node.source.value.split("/");
if (baseSource !== "react-dom") return;
for (const specifier of node.specifiers) switch (specifier.type) {
case AST_NODE_TYPES.ImportSpecifier:
if (specifier.imported.type !== AST_NODE_TYPES.Identifier) continue;
if (specifier.imported.name === hydrate) hydrateNames.add(specifier.local.name);
continue;
case AST_NODE_TYPES.ImportDefaultSpecifier:
case AST_NODE_TYPES.ImportNamespaceSpecifier:
reactDomNames.add(specifier.local.name);
continue;
}
}
};
}
function getFix$2(context, node) {
const getText$1 = (n) => context.sourceCode.getText(n);
return (fixer) => {
const [arg0, arg1] = node.arguments;
if (arg0 == null || arg1 == null) return null;
return [fixer.insertTextBefore(context.sourceCode.ast, "import { hydrateRoot } from \"react-dom/client\";\n"), fixer.replaceText(node, `hydrateRoot(${getText$1(arg1)}, ${getText$1(arg0)})`)];
};
}
//#endregion
//#region src/rules/no-missing-button-type.ts
const RULE_NAME$12 = "no-missing-button-type";
const BUTTON_TYPES = [
"button",
"submit",
"reset"
];
var no_missing_button_type_default = createRule({
meta: {
type: "problem",
docs: { description: "Enforces explicit `type` attribute for `button` elements." },
hasSuggestions: true,
messages: {
addButtonType: "Add 'type' attribute with value '{{type}}'.",
noMissingButtonType: "Add missing 'type' attribute on 'button' component."
},
schema: []
},
name: RULE_NAME$12,
create: create$12,
defaultOptions: []
});
function create$12(context) {
const resolver = createJsxElementResolver(context);
return { JSXElement(node) {
if (resolver.resolve(node).domElementType !== "button") return;
if (getJsxAttribute(context, node)("type") != null) return;
context.report({
messageId: "noMissingButtonType",
node: node.openingElement,
suggest: BUTTON_TYPES.map((type) => ({
messageId: "addButtonType",
data: { type },
fix: (fixer) => fixer.insertTextAfter(node.openingElement.name, ` type="${type}"`)
}))
});
} };
}
//#endregion
//#region src/rules/no-missing-iframe-sandbox.ts
const RULE_NAME$11 = "no-missing-iframe-sandbox";
var no_missing_iframe_sandbox_default = createRule({
meta: {
type: "problem",
docs: { description: "Enforces explicit `sandbox` prop for `iframe` elements." },
fixable: "code",
hasSuggestions: true,
messages: {
addIframeSandbox: "Add 'sandbox' prop with value '{{value}}'.",
noMissingIframeSandbox: "Add missing 'sandbox' prop on 'iframe' component."
},
schema: []
},
name: RULE_NAME$11,
create: create$11,
defaultOptions: []
});
function create$11(context) {
const resolver = createJsxElementResolver(context);
return { JSXElement(node) {
const { domElementType } = resolver.resolve(node);
if (domElementType !== "iframe") return;
const sandboxProp = getJsxAttribute(context, node)("sandbox");
if (sandboxProp == null) {
context.report({
messageId: "noMissingIframeSandbox",
node: node.openingElement,
suggest: [{
messageId: "addIframeSandbox",
data: { value: "" },
fix(fixer) {
return fixer.insertTextAfter(node.openingElement.name, ` sandbox=""`);
}
}]
});
return;
}
const sandboxValue = resolveJsxAttributeValue(context, sandboxProp);
if (typeof sandboxValue.toStatic("sandbox") === "string") return;
context.report({
messageId: "noMissingIframeSandbox",
node: sandboxValue.node ?? sandboxProp,
suggest: [{
messageId: "addIframeSandbox",
data: { value: "" },
fix(fixer) {
if (sandboxValue.kind.startsWith("spread")) return null;
return fixer.replaceText(sandboxProp, `sandbox=""`);
}
}]
});
} };
}
//#endregion
//#region src/rules/no-namespace.ts
const RULE_NAME$10 = "no-namespace";
var no_namespace_default = createRule({
meta: {
type: "problem",
docs: { description: "Enforces the absence of a `namespace` in React elements." },
messages: { noNamespace: "A React component '{{name}}' must not be in a namespace, as React does not support them." },
schema: []
},
name: RULE_NAME$10,
create: create$10,
defaultOptions: []
});
function create$10(context) {
return { JSXElement(node) {
const name$3 = getJsxElementType(context, node);
if (typeof name$3 !== "string" || !name$3.includes(":")) return;
context.report({
messageId: "noNamespace",
node: node.openingElement.name,
data: { name: name$3 }
});
} };
}
//#endregion
//#region src/rules/no-render.ts
const RULE_NAME$9 = "no-render";
var no_render_default = createRule({
meta: {
type: "problem",
docs: { description: "Replaces usages of `ReactDom.render()` with `createRoot(node).render()`." },
fixable: "code",
messages: { noRender: "[Deprecated] Use 'createRoot(node).render()' instead." },
schema: []
},
name: RULE_NAME$9,
create: create$9,
defaultOptions: []
});
function create$9(context) {
if (!context.sourceCode.text.includes("render")) return {};
if (compare(getSettingsFromContext(context).version, "18.0.0", "<")) return {};
const reactDomNames = new Set(["ReactDOM", "ReactDom"]);
const renderNames = /* @__PURE__ */ new Set();
return {
CallExpression(node) {
switch (true) {
case node.callee.type === AST_NODE_TYPES.Identifier && renderNames.has(node.callee.name):
context.report({
messageId: "noRender",
node,
fix: getFix$1(context, node)
});
return;
case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.object.type === AST_NODE_TYPES.Identifier && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "render" && reactDomNames.has(node.callee.object.name):
context.report({
messageId: "noRender",
node,
fix: getFix$1(context, node)
});
return;
}
},
ImportDeclaration(node) {
const [baseSource] = node.source.value.split("/");
if (baseSource !== "react-dom") return;
for (const specifier of node.specifiers) switch (specifier.type) {
case AST_NODE_TYPES.ImportSpecifier:
if (specifier.imported.type !== AST_NODE_TYPES.Identifier) continue;
if (specifier.imported.name === "render") renderNames.add(specifier.local.name);
continue;
case AST_NODE_TYPES.ImportDefaultSpecifier:
case AST_NODE_TYPES.ImportNamespaceSpecifier:
reactDomNames.add(specifier.local.name);
continue;
}
}
};
}
/**
* Provides a fixer function to replace `render(app, container)` with `createRoot(container).render(app)`
* @param context The rule context
* @param node The `CallExpression` node to fix
* @returns A fixer function or null if the fix cannot be applied
*/
function getFix$1(context, node) {
const getText$1 = (n) => context.sourceCode.getText(n);
return (fixer) => {
const [arg0, arg1] = node.arguments;
if (arg0 == null || arg1 == null) return null;
return [fixer.insertTextBefore(context.sourceCode.ast, "import { createRoot } from \"react-dom/client\";\n"), fixer.replaceText(node, `createRoot(${getText$1(arg1)}).render(${getText$1(arg0)})`)];
};
}
//#endregion
//#region src/rules/no-render-return-value.ts
const RULE_NAME$8 = "no-render-return-value";
const banParentTypes = [
AST_NODE_TYPES.VariableDeclarator,
AST_NODE_TYPES.Property,
AST_NODE_TYPES.ReturnStatement,
AST_NODE_TYPES.ArrowFunctionExpression,
AST_NODE_TYPES.AssignmentExpression
];
var no_render_return_value_default = createRule({
meta: {
type: "problem",
docs: { description: "Disallow the return value of `ReactDOM.render`." },
messages: { noRenderReturnValue: "Do not depend on the return value from 'ReactDOM.render'." },
schema: []
},
name: RULE_NAME$8,
create: create$8,
defaultOptions: []
});
function create$8(context) {
const reactDomNames = new Set(["ReactDOM", "ReactDom"]);
const renderNames = /* @__PURE__ */ new Set();
return {
CallExpression(node) {
switch (true) {
case node.callee.type === AST_NODE_TYPES.Identifier && renderNames.has(node.callee.name) && banParentTypes.includes(node.parent.type):
context.report({
messageId: "noRenderReturnValue",
node
});
return;
case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.object.type === AST_NODE_TYPES.Identifier && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "render" && reactDomNames.has(node.callee.object.name) && banParentTypes.includes(node.parent.type):
context.report({
messageId: "noRenderReturnValue",
node
});
return;
}
},
ImportDeclaration(node) {
const [baseSource] = node.source.value.split("/");
if (baseSource !== "react-dom") return;
for (const specifier of node.specifiers) switch (specifier.type) {
case AST_NODE_TYPES.ImportSpecifier:
if (specifier.imported.type !== AST_NODE_TYPES.Identifier) continue;
if (specifier.imported.name === "render") renderNames.add(specifier.local.name);
continue;
case AST_NODE_TYPES.ImportDefaultSpecifier:
case AST_NODE_TYPES.ImportNamespaceSpecifier:
reactDomNames.add(specifier.local.name);
continue;
}
}
};
}
//#endregion
//#region src/rules/no-script-url.ts
const RULE_NAME$7 = "no-script-url";
var no_script_url_default = createRule({
meta: {
type: "problem",
docs: { description: "Disallow `javascript:` URLs as attribute values." },
messages: { noScriptUrl: "Using a `javascript:` URL is a security risk and should be avoided." },
schema: []
},
name: RULE_NAME$7,
create: create$7,
defaultOptions: []
});
function create$7(context) {
return { JSXAttribute(node) {
if (node.name.type !== AST_NODE_TYPES.JSXIdentifier || node.value == null) return;
const value = resolveJsxAttributeValue(context, node).toStatic();
if (typeof value === "string" && RE_JAVASCRIPT_PROTOCOL.test(value)) context.report({
messageId: "noScriptUrl",
node: node.value
});
} };
}
//#endregion
//#region src/rules/no-string-style-prop.ts
const RULE_NAME$6 = "no-string-style-prop";
var no_string_style_prop_default = createRule({
meta: {
type: "problem",
docs: { description: "Disallows the use of string style prop." },
messages: { noStringStyleProp: "Do not use string style prop. Use an object instead." },
schema: []
},
name: RULE_NAME$6,
create: create$6,
defaultOptions: []
});
function create$6(context) {
return { JSXElement(node) {
if (!isJsxHostElement(context, node)) return;
const styleProp = getJsxAttribute(context, node)("style");
if (styleProp == null) return;
const styleValue = resolveJsxAttributeValue(context, styleProp);
if (typeof styleValue.toStatic() === "string") context.report({
messageId: "noStringStyleProp",
node: styleValue.node ?? styleProp
});
} };
}
//#endregion
//#region src/rules/no-unknown-property.ts
const RULE_NAME$5 = "no-unknown-property";
const DEFAULTS = {
ignore: [],
requireDataLowercase: false
};
/**
* Map of standard HTML attributes to their React counterparts
*/
const DOM_ATTRIBUTE_NAMES = {
"accept-charset": "acceptCharset",
class: "className",
crossorigin: "crossOrigin",
for: "htmlFor",
"http-equiv": "httpEquiv",
nomodule: "noModule"
};
/**
* Map of SVG attributes to their React camelCase equivalents
*/
const SVGDOM_ATTRIBUTE_NAMES = {
"accent-height": "accentHeight",
"alignment-baseline": "alignmentBaseline",
"arabic-form": "arabicForm",
"baseline-shift": "baselineShift",
"cap-height": "capHeight",
"clip-path": "clipPath",
"clip-rule": "clipRule",
"color-interpolation": "colorInterpolation",
"color-interpolation-filters": "colorInterpolationFilters",
"color-profile": "colorProfile",
"color-rendering": "colorRendering",
"dominant-baseline": "dominantBaseline",
"enable-background": "enableBackground",
"fill-opacity": "fillOpacity",
"fill-rule": "fillRule",
"flood-color": "floodColor",
"flood-opacity": "floodOpacity",
"font-family": "fontFamily",
"font-size": "fontSize",
"font-size-adjust": "fontSizeAdjust",
"font-stretch": "fontStretch",
"font-style": "fontStyle",
"font-variant": "fontVariant",
"font-weight": "fontWeight",
"glyph-name": "glyphName",
"glyph-orientation-horizontal": "glyphOrientationHorizontal",
"glyph-orientation-vertical": "glyphOrientationVertical",
"horiz-adv-x": "horizAdvX",
"horiz-origin-x": "horizOriginX",
"image-rendering": "imageRendering",
"letter-spacing": "letterSpacing",
"lighting-color": "lightingColor",
"marker-end": "markerEnd",
"marker-mid": "markerMid",
"marker-start": "markerStart",
"overline-position": "overlinePosition",
"overline-thickness": "overlineThickness",
"paint-order": "paintOrder",
"panose-1": "panose1",
"pointer-events": "pointerEvents",
"rendering-intent": "renderingIntent",
"shape-rendering": "shapeRendering",
"stop-color": "stopColor",
"stop-opacity": "stopOpacity",
"strikethrough-position": "strikethroughPosition",
"strikethrough-thickness": "strikethroughThickness",
"stroke-dasharray": "strokeDasharray",
"stroke-dashoffset": "strokeDashoffset",
"stroke-linecap": "strokeLinecap",
"stroke-linejoin": "strokeLinejoin",
"stroke-miterlimit": "strokeMiterlimit",
"stroke-opacity": "strokeOpacity",
"stroke-width": "strokeWidth",
"text-anchor": "textAnchor",
"text-decoration": "textDecoration",
"text-rendering": "textRendering",
"underline-position": "underlinePosition",
"underline-thickness": "underlineThickness",
"unicode-bidi": "unicodeBidi",
"unicode-range": "unicodeRange",
"units-per-em": "unitsPerEm",
"v-alphabetic": "vAlphabetic",
"v-hanging": "vHanging",
"v-ideographic": "vIdeographic",
"v-mathematical": "vMathematical",
"vector-effect": "vectorEffect",
"vert-adv-y": "vertAdvY",
"vert-origin-x": "vertOriginX",
"vert-origin-y": "vertOriginY",
"word-spacing": "wordSpacing",
"writing-mode": "writingMode",
"x-height": "xHeight",
"xlink:actuate": "xlinkActuate",
"xlink:arcrole": "xlinkArcrole",
"xlink:href": "xlinkHref",
"xlink:role": "xlinkRole",
"xlink:show": "xlinkShow",
"xlink:title": "xlinkTitle",
"xlink:type": "xlinkType",
"xml:base": "xmlBase",
"xml:lang": "xmlLang",
"xml:space": "xmlSpace"
};
/**
* Map of attributes that are only valid on specific HTML tags
*/
const ATTRIBUTE_TAGS_MAP = {
as: ["link"],
abbr: ["th", "td"],
align: [
"applet",
"caption",
"col",
"colgroup",
"hr",
"iframe",
"img",
"table",
"tbody",
"td",
"tfoot",
"th",
"thead",
"tr"
],
allowFullScreen: ["iframe", "video"],
autoPictureInPicture: ["video"],
charset: ["meta"],
checked: ["input"],
controls: ["audio", "video"],
controlsList: ["audio", "video"],
crossOrigin: [
"script",
"img",
"video",
"audio",
"link",
"image"
],
disablePictureInPicture: ["video"],
disableRemotePlayback: ["audio", "video"],
displaystyle: ["math"],
download: ["a", "area"],
fill: [
"altGlyph",
"circle",
"ellipse",
"g",
"line",
"marker",
"mask",
"path",
"polygon",
"polyline",
"rect",
"svg",
"symbol",
"text",
"textPath",
"tref",
"tspan",
"use",
"animate",
"animateColor",
"animateMotion",
"animateTransform",
"set"
],
focusable: ["svg"],
imageSizes: ["link"],
imageSrcSet: ["link"],
loop: ["audio", "video"],
mozAllowFullScreen: ["iframe", "video"],
muted: ["audio", "video"],
noModule: ["script"],
onAbort: ["audio", "video"],
onCanPlay: ["audio", "video"],
onCanPlayThrough: ["audio", "video"],
onCancel: ["dialog"],
onClose: ["dialog"],
onDurationChange: ["audio", "video"],
onEmptied: ["audio", "video"],
onEncrypted: ["audio", "video"],
onEnded: ["audio", "video"],
onError: [
"audio",
"video",
"img",
"link",
"source",
"script",
"picture",
"iframe"
],
onLoad: [
"script",
"img",
"link",
"picture",
"iframe",
"object",
"source"
],
onLoadStart: ["audio", "video"],
onLoadedData: ["audio", "video"],
onLoadedMetadata: ["audio", "video"],
onPause: ["audio", "video"],
onPlay: ["audio", "video"],
onPlaying: ["audio", "video"],
onProgress: ["audio", "video"],
onRateChange: ["audio", "video"],
onResize: ["audio", "video"],
onSeeked: ["audio", "video"],
onSeeking: ["audio", "video"],
onStalled: ["audio", "video"],
onSuspend: ["audio", "video"],
onTimeUpdate: ["audio", "video"],
onVolumeChange: ["audio", "video"],
onWaiting: ["audio", "video"],
playsInline: ["video"],
poster: ["video"],
preload: ["audio", "video"],
property: ["meta"],
returnValue: ["dialog"],
scrolling: ["iframe"],
valign: [
"tr",
"td",
"th",
"thead",
"tbody",
"tfoot",
"colgroup",
"col"
],
viewBox: [
"marker",
"pattern",
"svg",
"symbol",
"view"
],
webkitAllowFullScreen: ["iframe", "video"],
webkitDirectory: ["input"]
};
/**
* Single-word HTML/DOM properties
*/
const DOM_PROPERTY_NAMES_ONE_WORD = [
"dir",
"draggable",
"hidden",
"id",
"lang",
"nonce",
"part",
"slot",
"style",
"title",
"translate",
"inert",
"accept",
"action",
"allow",
"alt",
"as",
"async",
"buffered",
"capture",
"challenge",
"cite",
"code",
"cols",
"content",
"coords",
"csp",
"data",
"decoding",
"default",
"defer",
"disabled",
"form",
"headers",
"height",
"high",
"href",
"icon",
"importance",
"integrity",
"kind",
"label",
"language",
"loading",
"list",
"loop",
"low",
"manifest",
"max",
"media",
"method",
"min",
"multiple",
"muted",
"name",
"open",
"optimum",
"pattern",
"ping",
"placeholder",
"poster",
"preload",
"profile",
"rel",
"required",
"reversed",
"role",
"rows",
"sandbox",
"scope",
"seamless",
"selected",
"shape",
"size",
"sizes",
"span",
"src",
"start",
"step",
"summary",
"target",
"type",
"value",
"width",
"wmode",
"wrap",
"accumulate",
"additive",
"alphabetic",
"amplitude",
"ascent",
"azimuth",
"bbox",
"begin",
"bias",
"by",
"clip",
"color",
"cursor",
"cx",
"cy",
"d",
"decelerate",
"descent",
"direction",
"display",
"divisor",
"dur",
"dx",
"dy",
"elevation",
"end",
"exponent",
"fill",
"filter",
"format",
"from",
"fr",
"fx",
"fy",
"g1",
"g2",
"hanging",
"height",
"hreflang",
"ideographic",
"in",
"in2",
"intercept",
"k",
"k1",
"k2",
"k3",
"k4",
"kerning",
"local",
"mask",
"mode",
"offset",
"opacity",
"operator",
"order",
"orient",
"orientation",
"origin",
"overflow",
"path",
"ping",
"points",
"r",
"radius",
"rel",
"restart",
"result",
"rotate",
"rx",
"ry",
"scale",
"seed",
"slope",
"spacing",
"speed",
"stemh",
"stemv",
"string",
"stroke",
"to",
"transform",
"u1",
"u2",
"unicode",
"values",
"version",
"visibility",
"widths",
"x",
"x1",
"x2",
"xmlns",
"y",
"y1",
"y2",
"z",
"property",
"ref",
"key",
"children",
"results",
"security",
"controls"
];
/**
* Multi-word (camelCase) HTML/DOM properties
*/
const DOM_PROPERTY_NAMES_TWO_WORDS = [
"accessKey",
"autoCapitalize",
"autoFocus",
"contentEditable",
"enterKeyHint",
"exportParts",
"inputMode",
"itemID",
"itemRef",
"itemProp",
"itemScope",
"itemType",
"spellCheck",
"tabIndex",
"acceptCharset",
"autoComplete",
"autoPlay",
"border",
"cellPadding",
"cellSpacing",
"classID",
"codeBase",
"colSpan",
"contextMenu",
"dateTime",
"encType",
"formAction",
"formEncType",
"formMethod",
"formNoValidate",
"formTarget",
"frameBorder",
"hrefLang",
"httpEquiv",
"imageSizes",
"imageSrcSet",
"isMap",
"keyParams",
"keyType",
"marginHeight",
"marginWidth",
"maxLength",
"mediaGroup",
"minLength",
"noValidate",
"onAnimationEnd",
"onAnimationIteration",
"onAnimationStart",
"onBlur",
"onChange",
"onClick",
"onContextMenu",
"onCopy",
"onCompositionEnd",
"onCompositionStart",
"onCompositionUpdate",
"onCut",
"onDoubleClick",
"onDrag",
"onDragEnd",
"onDragEnter",
"onDragExit",
"onDragLeave",
"onError",
"onFocus",
"onInput",
"onKeyDown",
"onKeyPress",
"onKeyUp",
"onLoad",
"onWheel",
"onDragOver",
"onDragStart",
"onDrop",
"onMouseDown",
"onMouseEnter",
"onMouseLeave",
"onMouseMove",
"onMouseOut",
"onMouseOver",
"onMouseUp",
"onPaste",
"onScroll",
"onSelect",
"onSubmit",
"onToggle",
"onTransitionEnd",
"radioGroup",
"readOnly",
"referrerPolicy",
"rowSpan",
"srcDoc",
"srcLang",
"srcSet",
"useMap",
"fetchPriority",
"crossOrigin",
"accentHeight",
"alignmentBaseline",
"arabicForm",
"attributeName",
"attributeType",
"baseFrequency",
"baselineShift",
"baseProfile",
"calcMode",
"capHeight",
"clipPathUnits",
"clipPath",
"clipRule",
"colorInterpolation",
"colorInterpolationFilters",
"colorProfile",
"colorRendering",
"contentScriptType",
"contentStyleType",
"diffuseConstant",
"dominantBaseline",
"edgeMode",
"enableBackground",
"fillOpacity",
"fillRule",
"filterRes",
"filterUnits",
"floodColor",
"floodOpacity",
"fontFamily",
"fontSize",
"fontSizeAdjust",
"fontStretch",
"fontStyle",
"fontVariant",
"fontWeight",
"glyphName",
"glyphOrientationHorizontal",
"glyphOrientationVertical",
"glyphRef",
"gradientTransform",
"gradientUnits",
"horizAdvX",
"horizOriginX",
"imageRendering",
"kernelMatrix",
"kernelUnitLength",
"keyPoints",
"keySplines",
"keyTimes",
"lengthAdjust",
"letterSpacing",
"lightingColor",
"limitingConeAngle",
"markerEnd",
"markerMid",
"markerStart",
"markerHeight",
"markerUnits",
"markerWidth",
"maskContentUnits",
"maskUnits",
"mathematical",
"numOctaves",
"overlinePosition",
"overlineThickness",
"panose1",
"paintOrder",
"pathLength",
"patternContentUnits",
"patternTransform",
"patternUnits",
"pointerEvents",
"pointsAtX",
"pointsAtY",
"pointsAtZ",
"preserveAlpha",
"preserveAspectRatio",
"primitiveUnits",
"referrerPolicy",
"refX",
"refY",
"rendering-intent",
"repeatCount",
"repeatDur",
"requiredExtensions",
"requiredFeatures",
"shapeRendering",
"specularConstant",
"specularExponent",
"spreadMethod",
"startOffset",
"stdDeviation",
"stitchTiles",
"stopColor",
"stopOpacity",
"strikethroughPosition",
"strikethroughThickness",
"strokeDasharray",
"strokeDashoffset",
"strokeLinecap",
"strokeLinejoin",
"strokeMiterlimit",
"strokeOpacity",
"strokeWidth",
"surfaceScale",
"systemLanguage",
"tableValues",
"targetX",
"targetY",
"textAnchor",
"textDecoration",
"textRendering",
"textLength",
"transformOrigin",
"underlinePosition",
"underlineThickness",
"unicodeBidi",
"unicodeRange",
"unitsPerEm",
"vAlphabetic",
"vHanging",
"vIdeographic",
"vMathematical",
"vectorEffect",
"vertAdvY",
"vertOriginX",
"vertOriginY",
"viewBox",
"viewTarget",
"wordSpacing",
"writingMode",
"xHeight",
"xChannelSelector",
"xlinkActuate",
"xlinkArcrole",
"xlinkHref",
"xlinkRole",
"xlinkShow",
"xlinkTitle",
"xlinkType",
"xmlBase",
"xmlLang",
"xmlnsXlink",
"xmlSpace",
"yChannelSelector",
"zoomAndPan",
"autoCorrect",
"autoSave",
"className",
"dangerouslySetInnerHTML",
"defaultValue",
"defaultChecked",
"htmlFor",
"onBeforeInput",
"onChange",
"onInvalid",
"onReset",
"onTouchCancel",
"onTouchEnd",
"onTouchMove",
"onTouchStart",
"suppressContentEditableWarning",
"suppressHydrationWarning",
"onAbort",
"onCanPlay",
"onCanPlayThrough",
"onDurationChange",
"onEmptied",
"onEncrypted",
"onEnded",
"onLoadedData",
"onLoadedMetadata",
"onLoadStart",
"onPause",
"onPlay",
"onPlaying",
"onProgress",
"onRateChange",
"onResize",
"onSeeked",
"onSeeking",
"onStalled",
"onSuspend",
"onTimeUpdate",
"onVolumeChange",
"onWaiting",
"onCopyCapture",
"onCutCapture",
"onPasteCapture",
"onCompositionEndCapture",
"onCompositionStartCapture",
"onCompositionUpdateCapture",
"onFocusCapture",
"onBlurCapture",
"onChangeCapture",
"onBeforeInputCapture",
"onInputCapture",
"onResetCapture",
"onSubmitCapture",
"onInvalidCapture",
"onLoadCapture",
"onErrorCapture",
"onKeyDownCapture",
"onKeyPressCapture",
"onKeyUpCapture",
"onAbortCapture",
"onCanPlayCapture",
"onCanPlayThroughCapture",
"onDurationChangeCapture",
"onEmptiedCapture",
"onEncryptedCapture",
"onEndedCapture",
"onLoadedDataCapture",
"onLoadedMetadataCapture",
"onLoadStartCapture",
"onPauseCapture",
"onPlayCapture",
"onPlayingCapture",
"onProgressCapture",
"onRateChangeCapture",
"onSeekedCapture",
"onSeekingCapture",
"onStalledCapture",
"onSuspendCapture",
"onTimeUpdateCapture",
"onVolumeChangeCapture",
"onWaitingCapture",
"onSelectCapture",
"onTouchCancelCapture",
"onTouchEndCapture",
"onTouchMoveCapture",
"onTouchStartCapture",
"onScrollCapture",
"onWheelCapture",
"onAnimationEndCapture",
"onAnimationIteration",
"onAnimationStartCapture",
"onTransitionEndCapture",
"onAuxClick",
"onAuxClickCapture",
"onClickCapture",
"onContextMenuCapture",
"onDoubleClickCapture",
"onDragCapture",
"onDragEndCapture",
"onDragEnterCapture",
"onDragExitCapture",
"onDragLeaveCapture",
"onDragOverCapture",
"onDragStartCapture",
"onDropCapture",
"onMouseDown",
"onMouseDownCapture",
"onMouseMoveCapture",
"onMouseOutCapture",
"onMouseOverCapture",
"onMouseUpCapture",
"autoPictureInPicture",
"controlsList",
"disablePictureInPicture",
"disableRemotePlayback"
];
/**
* DOM properties that are exempt from case sensitivity checks
*/
const DOM_PROPERTIES_IGNORE_CASE = [
"charset",
"allowFullScreen",
"webkitAllowFullScreen",
"mozAllowFullScreen",
"webkitDirectory"
];
/**
* List of ARIA attributes
*/
const ARIA_PROPERTIES = [
"aria-atomic",
"aria-braillelabel",
"aria-brailleroledescription",
"aria-busy",
"aria-controls",
"aria-current",
"aria-describedby",
"aria-description",
"aria-details",
"aria-disabled",
"aria-dropeffect",
"aria-errormessage",
"aria-flowto",
"aria-grabbed",
"aria-haspopup",
"aria-hidden",
"aria-invalid",
"aria-keyshortcuts",
"aria-label",
"aria-labelledby",
"aria-live",
"aria-owns",
"aria-relevant",
"aria-roledescription",
"aria-autocomplete",
"aria-checked",
"aria-expanded",
"aria-level",
"aria-modal",
"aria-multiline",
"aria-multiselectable",
"aria-orientation",
"aria-placeholder",
"aria-pressed",
"aria-readonly",
"aria-required",
"aria-selected",
"aria-sort",
"aria-valuemax",
"aria-valuemin",
"aria-valuenow",
"aria-valuetext",
"aria-activedescendant",
"aria-colcount",
"aria-colindex",
"aria-colindextext",
"aria-colspan",
"aria-posinset",
"aria-rowcount",
"aria-rowindex",
"aria-rowindextext",
"aria-rowspan",
"aria-setsize"
];
/**
* React-specific pointer event handlers added in React 16.4
*/
const REACT_ON_PROPS = [
"onGotPointerCapture",
"onGotPointerCaptureCapture",
"onLostPointerCapture",
"onLostPointerCapture",
"onLostPointerCaptureCapture",
"onPointerCancel",
"onPointerCancelCapture",
"onPointerDown",
"onPointerDownCapture",
"onPointerEnter",
"onPointerEnterCapture",
"onPointerLeave",
"onPointerLeaveCapture",
"onPointerMove",
"onPointerMoveCapture",
"onPointerOut",
"onPointerOutCapture",
"onPointerOver",
"onPointerOverCapture",
"onPointerUp",
"onPointerUpCapture"
];
/**
* Popover API properties added in React 19
*/
const POPOVER_API_PROPS = [
"popover",
"popoverTarget",
"popoverTargetAction",
"onToggle",
"onBeforeToggle"
];
/**
* Gets all valid DOM property names based on React version
* @param context ESLint rule context
* @returns Array of valid DOM property names
*/
function getDOMPropertyNames(context) {
const ALL_DOM_PROPERTY_NAMES = DOM_PROPERTY_NAMES_TWO_WORDS.concat(DOM_PROPERTY_NAMES_ONE_WORD);
if (testReactVersion(context, "<=", "16.1.0")) {
ALL_DOM_PROPERTY_NAMES.push("allowTransparency");
return ALL_DOM_PROPERTY_NAMES;
}
if (testReactVersion(context, ">=", "16.4.0")) ALL_DOM_PROPERTY_NAMES.push(...REACT_ON_PROPS);
testReactVersion(context, ">=", "19.0.0-rc.0") ? ALL_DOM_PROPERTY_NAMES.push(...POPOVER_API_PROPS) : ALL_DOM_PROPERTY_NAMES.push(...POPOVER_API_PROPS.map((prop) => prop.toLowerCase()));
return ALL_DOM_PROPERTY_NAMES;
}
/**
* Checks if a node's parent is a JSX tag that is written with lowercase letters,
* and is not a custom web component.
* @param childNode JSX element being tested
* @returns Whether the node is a valid HTML tag in JSX
*/
function isValidHTMLTagInJSX(childNode) {
if (/^[a-z][^-]*$/.test(childNode.parent.name.name)) return !childNode.parent.attributes.some((attrNode) => attrNode.type === "JSXAttribute" && attrNode.name.type === "JSXIdentifier" && attrNode.name.name === "is");
return false;
}
/**
* Normalizes attribute names that should be case-insensitive
* @param name Attribute name to normalize
* @returns Normalized attribute name
*/
function normalizeAttributeCase(name$3) {
return DOM_PROPERTIES_IGNORE_CASE.find((element) => element.toLowerCase() === name$3.toLowerCase()) || name$3;
}
/**
* Checks if an attribute name is a valid data-* attribute
* @param name Attribute name to test
* @returns Whether the attribute is a valid data attribute
*/
function isValidDataAttribute(name$3) {
return !/^data-xml/i.test(name$3) && /^data-[^:]*$/.test(name$3);
}
/**
* Checks if an attribute name has uppercase characters
* @param name Attribute name to test
* @returns Whether the name has uppercase characters
*/
function hasUpperCaseCharacter(name$3) {
return name$3.toLowerCase() !== name$3;
}
/**
* Checks if an attribute is a valid ARIA attribute
* @param name Attribute name to test
* @returns Whether the attribute is a valid ARIA attribute
*/
function isValidAriaAttribute(name$3) {
return ARIA_PROPERTIES.some((element) => element === name$3);
}
/**
* Gets the tag name for a JSXAttribute
* @param node JSXAttribute to get tag name from
* @returns Tag name or null
*/
function getTagName(node) {
if (node?.parent?.name) return node.parent.name.name;
return null;
}
/**
* Checks if the tag name has a dot (member expression)
* @param node JSXAttribute to check
* @returns Whether the tag name has a dot
*/
function tagNameHasDot(node) {
return !!(node.parent?.name && node.parent.name.type === "JSXMemberExpression");
}
/**
* Gets the standard name of an attribute
* @param name Attribute name
* @param context ESLint context
* @returns Standard name or undefined
*/
function getStandardName(name$3, context) {
if (has(DOM_ATTRIBUTE_NAMES, name$3)) return DOM_ATTRIBUTE_NAMES[name$3];
if (has(SVGDOM_ATTRIBUTE_NAMES, name$3)) return SVGDOM_ATTRIBUTE_NAMES[name$3];
return getDOMPropertyNames(context).find((element) => element.toLowerCase() === name$3.toLowerCase());
}
/**
* Checks if an object has a property
* @param obj Object to check
* @param key Key to check for
* @returns Whether the object has the property
*/
function has(obj, key) {
return Object.hasOwn(obj, key);
}
/**
* Gets text of a node
* @param context ESLint context
* @param node Node to get text from
* @returns Node's text
*/
function getText(context, node) {
return context.sourceCode.getText(node);
}
/**
* Tests React version against a comparator
* @param context ESLint context
* @param comparator Comparison operator
* @param version Version to compare against
* @returns Comparison result
*/
function testReactVersion(context, comparator, version$1) {
const { version: localVersion } = getSettingsFromContext(context);
return compare(localVersion, version$1, comparator);
}
const messages = {
dataLowercaseRequired: "React does not recognize data-* props with uppercase characters on a DOM element. Found '{{name}}', use '{{lowerCaseName}}' instead",
invalidPropOnTag: "Invalid property '{{name}}' found on tag '{{tagName}}', but it is only allowed on: {{allowedTags}}",
unknownProp: "Unknown property '{{name}}' found",
unknownPropWithStandardName: "Unknown property '{{name}}' found, use '{{standardName}}' instead"
};
var no_unknown_property_default = createRule({
meta: {
type: "problem",
docs: { description: "Disallow unknown `DOM` property." },
fixable: "code",
messages,
schema: [{
type: "object",
additionalProperties: false,
properties: {
ignore: {
type: "array",
items: { type: "string" }
},
requireDataLowercase: {
type: "boolean",
default: false
}
}
}]
},
name: RULE_NAME$5,
create: create$5,
defaultOptions: []
});
/**
* Create function for the ESLint rule
* @param context ESLint rule context
* @returns Rule listener
*/
function create$5(context) {
/**
* Gets the ignore configuration from rule options
* @returns Array of attribute names to ignore
*/
function getIgnoreConfig() {
return context.options[0]?.ignore || DEFAULTS.ignore;
}
/**
* Gets the requireDataLowercase option from rule options
* @returns Whether data attributes must be lowercase
*/
function getRequireDataLowercase() {
return context.options[0] && typeof context.options[0].requireDataLowercase !== "undefined" ? !!context.options[0].requireDataLowercase : DEFAULTS.requireDataLowercase;
}
return { JSXAttribute(node) {
const ignoreNames = getIgnoreConfig();
const actualName = getText(context, node.name);
if (ignoreNames.indexOf(actualName) >= 0) return;
const name$3 = normalizeAttributeCase(actualName);
if (tagNameHasDot(node)) return;
if (isValidDataAttribute(name$3)) {
if (getRequireDataLowercase() && hasUpperCaseCharacter(name$3)) context.report({
node,
messageId: "dataLowercaseRequired",
data: {
name: actualName,
lowerCaseName: actualName.toLowerCase()
}
});
return;
}
if (isValidAriaAttribute(name$3)) return;
const tagName = getTagName(node);
if (tagName === "fbt" || tagName === "fbs") return;
if (!isValidHTMLTagInJSX(node)) return;
const allowedTags = has(ATTRIBUTE_TAGS_MAP, name$3) ? ATTRIBUTE_TAGS_MAP[name$3] : null;
if (tagName && allowedTags) {
if (allowedTags.indexOf(tagName) === -1) context.report({
node,
messageId: "invalidPropOnTag",
data: {
name: actualName,
allowedTags: allowedTags.join(", "),
tagName
}
});
return;
}
const standardName = getStandardName(name$3, context);
const hasStandardNameButIsNotUsed = !!standardName && standardName !== name$3;
if (!!standardName && standardName === name$3) return;
if (hasStandardNameButIsNotUsed) {
context.report({
node,
messageId: "unknownPropWithStandardName",
data: {
name: actualName,
standardName
},
fix(fixer) {
return fixer.replaceText(node.name, standardName);
}
});
return;
}
context.report({
node,
messageId: "unknownProp",
data: { name: actualName }
});
} };
}
//#endregion
//#region src/rules/no-unsafe-iframe-sandbox.ts
const RULE_NAME$4 = "no-unsafe-iframe-sandbox";
const UNSAFE_SANDBOX_VALUES = ["allow-scripts", "allow-same-origin"];
/**
* Checks if the sandbox attribute value contains an unsafe combination
* An iframe with both "allow-scripts" and "allow-same-origin" can remove its sandbox attribute,
* making it as insecure as an iframe without any sandboxing
* @param value The value of the sandbox attribute
* @returns `true` if the value is a string and contains an unsafe combination, `false` otherwise
*/
function isUnsafeSandboxCombination(value) {
if (typeof value !== "string") return false;
return UNSAFE_SANDBOX_VALUES.every((v) => value.includes(v));
}
var no_unsafe_iframe_sandbox_default = createRule({
meta: {
type: "problem",
docs: { description: "Enforces `sandbox` attribute for `iframe` elements is not set to unsafe combinations." },
messages: { noUnsafeIframeSandbox: "Unsafe 'sandbox' attribute value on 'iframe' component." },
schema: []
},
name: RULE_NAME$4,
create: create$4,
defaultOptions: []
});
function create$4(context) {
const resolver = createJsxElementResolver(context);
return { JSXElement(node) {
if (resolver.resolve(node).domElementType !== "iframe") return;
const sandboxProp = getJsxAttribute(context, node)("sandbox");
if (sandboxProp == null) return;
const sandboxValue = resolveJsxAttributeValue(context, sandboxProp);
if (isUnsafeSandboxCombination(sandboxValue.toStatic("sandbox"))) context.report({
messageId: "noUnsafeIframeSandbox",
node: sandboxValue.node ?? sandboxProp
});
} };
}
//#endregion
//#region src/rules/no-unsafe-target-blank.ts
const RULE_NAME$3 = "no-unsafe-target-blank";
/**
* Checks if a value appears to be an external link.
* External links typically start with http(s):// or have protocol-relative format.
* @param value The value to check
* @returns Whether the value represents an external link
*/
function isExternalLinkLike(value) {
if (typeof value !== "string") return false;
return value.startsWith("https://") || /^(?:\w+:|\/\/)/u.test(value);
}
/**
* Checks if a rel prop value contains the necessary security attributes.
* At minimum, it should contain "noreferrer".
* @param value The rel prop value to check
* @returns Whether the rel value is considered secure
*/
function isSafeRel(value) {
if (typeof value !== "string") return false;
return value === "noreferrer" || /\bnoreferrer\b/u.test(value);
}
var no_unsafe_target_blank_default = createRule({
meta: {
type: "problem",
docs: { description: "Disallow `target=\"_blank\"` without `rel=\"noreferrer noopener\"`." },
fixable: "code",
hasSuggestions: true,
messages: {
addRelNoreferrerNoopener: `Add 'rel="noreferrer noopener"' to the link to prevent security risks.`,
noUnsafeTargetBlank: `Using 'target="_blank"' on an external link without 'rel="noreferrer noopener"' is a security risk.`
},
schema: []
},
name: RULE_NAME$3,
create: create$3,
defaultOptions: []
});
function create$3(context) {
const resolver = createJsxElementResolver(context);
return { JSXElement(node) {
const { domElementType } = resolver.resolve(node);
if (domElementType !== "a") return;
const findAttribute = getJsxAttribute(context, node);
const targetProp = findAttribute("target");
if (targetProp == null) return;
if (resolveJsxAttributeValue(context, targetProp).toStatic("target") !== "_blank") return;
const hrefProp = findAttribute("href");
if (hrefProp == null) return;
if (!isExternalLinkLike(resolveJsxAttributeValue(context, hrefProp).toStatic("href"))) return;
const relProp = findAttribute("rel");
if (relProp == null) {
context.report({
messageId: "noUnsafeTargetBlank",
node: node.openingElement,
suggest: [{
messageId: "addRelNoreferrerNoopener",
fix(fixer) {
return fixer.insertTextAfter(node.openingElement.name, ` rel="noreferrer noopener"`);
}
}]
});
return;
}
if (isSafeRel(resolveJsxAttributeValue(context, relProp).toStatic("rel"))) return;
context.report({
messageId: "noUnsafeTargetBlank",
node: relProp,
suggest: [{
messageId: "addRelNoreferrerNoopener",
fix(fixer) {
return fixer.replaceText(relProp, `rel="noreferrer noopener"`);
}
}]
});
} };
}
//#endregion
//#region src/rules/no-use-form-state.ts
const RULE_NAME$2 = "no-use-form-state";
var no_use_form_state_default = createRule({
meta: {
type: "problem",
docs: { description: "Replaces usages of `useFormState` with `useActionState`." },
fixable: "code",
messages: { noUseFormState: "[Deprecated] Use 'useActionState' from 'react' package instead." },
schema: []
},
name: RULE_NAME$2,
create: create$2,
defaultOptions: []
});
function create$2(context) {
if (!context.sourceCode.text.includes("useFormState")) return {};
if (compare(getSettingsFromContext(context).version, "19.0.0", "<")) return {};
const reactDomNames = /* @__PURE__ */ new Set();
const useFormStateNames = /* @__PURE__ */ new Set();
return {
CallExpression(node) {
switch (true) {
case node.callee.type === AST_NODE_TYPES.Identifier && useFormStateNames.has(node.callee.name):
context.report({
messageId: "noUseFormState",
node,
fix: getFix(context, node)
});
return;
case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.object.type === AST_NODE_TYPES.Identifier && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "useFormState" && reactDomNames.has(node.callee.object.name):
context.report({
messageId: "noUseFormState",
node,
fix: getFix(context, node)
});
return;
}
},
ImportDeclaration(node) {
const [baseSource] = node.source.value.split("/");
if (baseSource !== "react-dom") return;
for (const specifier of node.specifiers) switch (specifier.type) {
case AST_NODE_TYPES.ImportSpecifier:
if (specifier.imported.type !== AST_NODE_TYPES.Identifier) continue;
if (specifier.imported.name === "useFormState") useFormStateNames.add(specifier.local.name);
continue;
case AST_NODE_TYPES.ImportDefaultSpecifier:
case AST_NODE_TYPES.ImportNamespaceSpecifier:
reactDomNames.add(specifier.local.name);
continue;
}
}
};
}
function getFix(context, node) {
const { importSource } = getSettingsFromContext(context);
return (fixer) => {
return [fixer.insertTextBefore(context.sourceCode.ast, `import { useActionState } from "${importSource}";\n`), fixer.replaceText(node.callee, "useActionState")];
};
}
//#endregion
//#region src/rules/no-void-elements-with-children.ts
const RULE_NAME$1 = "no-void-elements-with-children";
const voidElements = new Set([
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"keygen",
"link",
"menuitem",
"meta",
"param",
"source",
"track",
"wbr"
]);
var no_void_elements_with_children_default = createRule({
meta: {
type: "problem",
docs: { description: "Disallow `children` in void DOM elements." },
messages: { noVoidElementsWithChildren: "'{{elementType}}' is a void element tag and must not have children." },
schema: []
},
name: RULE_NAME$1,
create: create$1,
defaultOptions: []
});
function create$1(context) {
const resolver = createJsxElementResolver(context);
return { JSXElement(node) {
const { domElementType } = resolver.resolve(node);
if (!voidElements.has(domElementType)) return;
const findJsxAttribute = getJsxAttribute(context, node);
const hasChildrenProp = findJsxAttribute("children") != null;
const hasDangerouslySetInnerHTML = findJsxAttribute("dangerouslySetInnerHTML") != null;
if (node.children.length > 0 || hasChildrenProp || hasDangerouslySetInnerHTML) context.report({
messageId: "noVoidElementsWithChildren",
n