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
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@ ruleTester.run(RULE_NAME, rule, {
code: tsx`<App dangerouslySetInnerHTML={{ __html: "HTML" }}> </App>`,
errors: [{ messageId: "noDangerouslySetInnerhtmlWithChildren" }],
},
{
// https://github.com/Rel1cx/eslint-react/issues/1163
code: tsx`
function Abc() {
return (
<>
{/* Error on div 1: A DOM component cannot use both 'children' and 'dangerouslySetInnerHTML'. eslint @eslint-react/dom/no-dangerously-set-innerhtml-with-children */}
<div dangerouslySetInnerHTML={{ __html: 'Hello World' }}>
Goodbye World
</div>

{/* No error on div 2 */}
<div dangerouslySetInnerHTML={{ __html: 'Hello World' }}>
<p>Goodbye World</p>
</div>
</>
);
}
`,
errors: [
{ messageId: "noDangerouslySetInnerhtmlWithChildren" },
{ messageId: "noDangerouslySetInnerhtmlWithChildren" },
],
},
],
valid: [
...allValid,
Expand All @@ -60,6 +84,10 @@ ruleTester.run(RULE_NAME, rule, {
const { a, b, ...props } = otherProps
const div = <div {...props} />
`,
tsx`
<App dangerouslySetInnerHTML={{ __html: "HTML" }}>
</App>
`,
"<App>Children</App>",
'<App dangerouslySetInnerHTML={{ __html: "HTML" }} />',
'<App dangerouslySetInnerHTML={{ __html: "HTML" }}>\n</App>',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { RuleContext, RuleFeature } from "@eslint-react/kit";
import type { TSESTree } from "@typescript-eslint/types";
import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
import type { CamelCase } from "string-ts";
import * as AST from "@eslint-react/ast";
import * as ER from "@eslint-react/core";

import { AST_NODE_TYPES, type TSESTree } from "@typescript-eslint/types";
import { createRule } from "../utils";

export const RULE_NAME = "no-dangerously-set-innerhtml-with-children";
Expand Down Expand Up @@ -40,7 +39,8 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
JSXElement(node) {
const attributes = node.openingElement.attributes;
const initialScope = context.sourceCode.getScope(node);
const hasChildren = hasChildrenWithin(node) || ER.hasAttribute(context, "children", attributes, initialScope);
const hasChildren = node.children.some(isSignificantChildren)
|| ER.hasAttribute(context, "children", attributes, initialScope);
if (hasChildren && ER.hasAttribute(context, dangerouslySetInnerHTML, attributes, initialScope)) {
context.report({
messageId: "noDangerouslySetInnerhtmlWithChildren",
Expand All @@ -51,8 +51,26 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
};
}

function hasChildrenWithin(node: TSESTree.JSXElement): boolean {
return node.children.length > 0
&& node.children[0] != null
&& !AST.isLineBreak(node.children[0]);
/**
* Check if a Literal or JSXText node is whitespace
* @param node The AST node to check
* @returns boolean `true` if the node is whitespace
*/
function isWhiteSpace(node: TSESTree.JSXText | TSESTree.Literal) {
return typeof node.value === "string" && node.raw.trim() === "";
}

/**
* Check if a Literal or JSXText node is padding spaces
* @param node The AST node to check
* @returns boolean
*/
function isPaddingSpaces(node: TSESTree.Node) {
return ER.isJsxText(node)
&& isWhiteSpace(node)
&& node.raw.includes("\n");
}

function isSignificantChildren(node: TSESTree.JSXElement["children"][number]) {
return node.type !== AST_NODE_TYPES.JSXText || !isPaddingSpaces(node);
}
Loading
Loading