Skip to content

Commit 65d2069

Browse files
authored
fix: improve logic for detecting significant children in JSX elements, closes #1163 (#1165)
1 parent 683dfb7 commit 65d2069

File tree

3 files changed

+55
-7
lines changed

3 files changed

+55
-7
lines changed

packages/plugins/eslint-plugin-react-dom/src/rules/no-dangerously-set-innerhtml-with-children.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,30 @@ ruleTester.run(RULE_NAME, rule, {
3939
code: tsx`<App dangerouslySetInnerHTML={{ __html: "HTML" }}> </App>`,
4040
errors: [{ messageId: "noDangerouslySetInnerhtmlWithChildren" }],
4141
},
42+
{
43+
// https://github.com/Rel1cx/eslint-react/issues/1163
44+
code: tsx`
45+
function Abc() {
46+
return (
47+
<>
48+
{/* Error on div 1: A DOM component cannot use both 'children' and 'dangerouslySetInnerHTML'. eslint @eslint-react/dom/no-dangerously-set-innerhtml-with-children */}
49+
<div dangerouslySetInnerHTML={{ __html: 'Hello World' }}>
50+
Goodbye World
51+
</div>
52+
53+
{/* No error on div 2 */}
54+
<div dangerouslySetInnerHTML={{ __html: 'Hello World' }}>
55+
<p>Goodbye World</p>
56+
</div>
57+
</>
58+
);
59+
}
60+
`,
61+
errors: [
62+
{ messageId: "noDangerouslySetInnerhtmlWithChildren" },
63+
{ messageId: "noDangerouslySetInnerhtmlWithChildren" },
64+
],
65+
},
4266
],
4367
valid: [
4468
...allValid,
@@ -60,6 +84,10 @@ ruleTester.run(RULE_NAME, rule, {
6084
const { a, b, ...props } = otherProps
6185
const div = <div {...props} />
6286
`,
87+
tsx`
88+
<App dangerouslySetInnerHTML={{ __html: "HTML" }}>
89+
</App>
90+
`,
6391
"<App>Children</App>",
6492
'<App dangerouslySetInnerHTML={{ __html: "HTML" }} />',
6593
'<App dangerouslySetInnerHTML={{ __html: "HTML" }}>\n</App>',

packages/plugins/eslint-plugin-react-dom/src/rules/no-dangerously-set-innerhtml-with-children.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import type { RuleContext, RuleFeature } from "@eslint-react/kit";
2-
import type { TSESTree } from "@typescript-eslint/types";
32
import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
43
import type { CamelCase } from "string-ts";
5-
import * as AST from "@eslint-react/ast";
64
import * as ER from "@eslint-react/core";
75

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

109
export const RULE_NAME = "no-dangerously-set-innerhtml-with-children";
@@ -40,7 +39,8 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
4039
JSXElement(node) {
4140
const attributes = node.openingElement.attributes;
4241
const initialScope = context.sourceCode.getScope(node);
43-
const hasChildren = hasChildrenWithin(node) || ER.hasAttribute(context, "children", attributes, initialScope);
42+
const hasChildren = node.children.some(isSignificantChildren)
43+
|| ER.hasAttribute(context, "children", attributes, initialScope);
4444
if (hasChildren && ER.hasAttribute(context, dangerouslySetInnerHTML, attributes, initialScope)) {
4545
context.report({
4646
messageId: "noDangerouslySetInnerhtmlWithChildren",
@@ -51,8 +51,26 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
5151
};
5252
}
5353

54-
function hasChildrenWithin(node: TSESTree.JSXElement): boolean {
55-
return node.children.length > 0
56-
&& node.children[0] != null
57-
&& !AST.isLineBreak(node.children[0]);
54+
/**
55+
* Check if a Literal or JSXText node is whitespace
56+
* @param node The AST node to check
57+
* @returns boolean `true` if the node is whitespace
58+
*/
59+
function isWhiteSpace(node: TSESTree.JSXText | TSESTree.Literal) {
60+
return typeof node.value === "string" && node.raw.trim() === "";
61+
}
62+
63+
/**
64+
* Check if a Literal or JSXText node is padding spaces
65+
* @param node The AST node to check
66+
* @returns boolean
67+
*/
68+
function isPaddingSpaces(node: TSESTree.Node) {
69+
return ER.isJsxText(node)
70+
&& isWhiteSpace(node)
71+
&& node.raw.includes("\n");
72+
}
73+
74+
function isSignificantChildren(node: TSESTree.JSXElement["children"][number]) {
75+
return node.type !== AST_NODE_TYPES.JSXText || !isPaddingSpaces(node);
5876
}

pnpm-lock.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)