From 68e6f6f005bc5efb7467158bbd03aec3ce4ec381 Mon Sep 17 00:00:00 2001 From: connorshea <2977353+connorshea@users.noreply.github.com> Date: Mon, 9 Mar 2026 18:08:52 +0000 Subject: [PATCH] feat(linter): Implement `react/no-react-children` rule. (#20104) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AI Disclosure: I used Claude Code for the implementation of the rule's `run` and `is_imported_from_react` functions. All other code (diagnostics, tests, rule docs, etc.) were written by me. Fixes #20015. Part of #1022, I guess? This rule is based on the following rules from `@eslint-react/eslint-plugin`: - [`@eslint-react/no-children-count`](https://www.eslint-react.xyz/docs/rules/no-children-count) - [`@eslint-react/no-children-for-each`](https://www.eslint-react.xyz/docs/rules/no-children-for-each) - [`@eslint-react/no-children-map`](https://www.eslint-react.xyz/docs/rules/no-children-map) - [`@eslint-react/no-children-only`](https://www.eslint-react.xyz/docs/rules/no-children-only) - [`@eslint-react/no-children-to-array`](https://www.eslint-react.xyz/docs/rules/no-children-to-array) The goal of the rule is to prevent all usage of [`React.Children`](https://react.dev/reference/react/Children), which is discouraged by the React docs. Examples of rule violations: ```jsx import { Children } from 'react'; Children.toArray(children); Children.map(children, child =>
{child}
); Children.only(children); Children.count(children); Children.forEach(children, (child, index) => {}); // --- import React from 'react'; React.Children.toArray(children); ``` A few things to note, in terms of what this rule intentionally does not catch right now: 1. This rule will not catch CommonJS imports like `const { Children } = require('react');`. It didn't really seem worth the trouble. 2. This does not catch alias imports like `import { Children as ReactChildren } from 'react';`. I considered adding that case, but it would make the rule more complex for an edge case, and I wasn't sure that was really worthwhile. The original rule(s) from `@eslint-react/eslint-plugin` also didn't handle this. 3. There are a few other ways to get around this, like destructuring from `React` after importing it: ```jsx import React from 'react'; const { Children } = React; ``` I don't know that that is worth protecting against? We can always add support for complex cases like that in subsequent PRs if desired. I tried to keep it as simple as possible, which means there will be some holes like the above, but it will be much easier to review and validate as a result, and the above patterns are generally uncommon anyway. ----- ecosystem-ci run, no new failures (not that any are even really possible, given this isn't a default rule / doesn't have a fixer): https://github.com/oxc-project/oxc-ecosystem-ci/actions/runs/22804771151 For validation purposes, it does add 7 new errors to the bsky results, before: ``` Found 0 warnings and 76795 errors. Finished in 1.5s on 1555 files with 629 rules using 4 threads. ``` after: ``` Found 0 warnings and 76802 errors. Finished in 1.4s on 1555 files with 630 rules using 4 threads. ``` Running the rule on the bsky codebase, all 7 results are correctly-detected violations:
oxlint results with this rule ``` × eslint-plugin-react(no-react-children): `React.Children` should not be used. ╭─[src/view/com/pager/Pager.web.tsx:90:8] 89 │ })} 90 │ {Children.map(children, (child, i) => ( · ──────────── 91 │ { 52 │ return Children.toArray(children).map((node, i) => { · ──────────────── 53 │ if (i === 0 && isValidElement(node)) { ╰──── help: `React.Children` is uncommon and leads to fragile React components. note: https://react.dev/reference/react/Children#alternatives × eslint-plugin-react(no-react-children): `React.Children` should not be used. ╭─[src/components/forms/InputGroup.tsx:11:20] 10 │ const t = useTheme() 11 │ const children = React.Children.toArray(props.children) · ────────────────────── 12 │ const total = children.length ╰──── help: `React.Children` is uncommon and leads to fragile React components. note: https://react.dev/reference/react/Children#alternatives × eslint-plugin-react(no-react-children): `React.Children` should not be used. ╭─[src/components/Tooltip/index.web.tsx:113:13] 112 │ export function TextBubble({children}: {children: React.ReactNode}) { 113 │ const c = Children.toArray(children) · ──────────────── 114 │ return ( ╰──── help: `React.Children` is uncommon and leads to fragile React components. note: https://react.dev/reference/react/Children#alternatives × eslint-plugin-react(no-react-children): `React.Children` should not be used. ╭─[src/components/Tooltip/index.tsx:455:13] 454 │ export function TextBubble({children}: {children: React.ReactNode}) { 455 │ const c = Children.toArray(children) · ──────────────── 456 │ return ( ╰──── help: `React.Children` is uncommon and leads to fragile React components. note: https://react.dev/reference/react/Children#alternatives × eslint-plugin-react(no-react-children): `React.Children` should not be used. ╭─[src/alf/typography.tsx:71:3] 70 │ let hasEmoji = false 71 │ Children.forEach(children, child => { · ──────────────── 72 │ if (typeof child === 'string' && createEmojiRegex().test(child)) { ╰──── help: `React.Children` is uncommon and leads to fragile React components. note: https://react.dev/reference/react/Children#alternatives × eslint-plugin-react(no-react-children): `React.Children` should not be used. ╭─[src/alf/typography.tsx:87:10] 86 │ } 87 │ return Children.map(children, child => { · ──────────── 88 │ if (typeof child !== 'string') return child ╰──── help: `React.Children` is uncommon and leads to fragile React components. note: https://react.dev/reference/react/Children#alternatives Found 0 warnings and 7 errors. Finished in 226ms on 1555 files with 1 rules using 8 threads. ```
--- .../src/generated/rule_runner_impls.rs | 6 + crates/oxc_linter/src/generated/rules_enum.rs | 26 +- crates/oxc_linter/src/rules.rs | 1 + .../src/rules/react/no_react_children.rs | 236 ++++++++++++++++++ .../snapshots/react_no_react_children.snap | 181 ++++++++++++++ 5 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 crates/oxc_linter/src/rules/react/no_react_children.rs create mode 100644 crates/oxc_linter/src/snapshots/react_no_react_children.snap diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 7368d4d76b64c..48bf306c9c99d 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -2549,6 +2549,12 @@ impl RuleRunner for crate::rules::react::no_namespace::NoNamespace { const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } +impl RuleRunner for crate::rules::react::no_react_children::NoReactChildren { + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::CallExpression])); + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; +} + impl RuleRunner for crate::rules::react::no_redundant_should_component_update::NoRedundantShouldComponentUpdate { diff --git a/crates/oxc_linter/src/generated/rules_enum.rs b/crates/oxc_linter/src/generated/rules_enum.rs index c163e843bfac0..5fa6a8705cbf6 100644 --- a/crates/oxc_linter/src/generated/rules_enum.rs +++ b/crates/oxc_linter/src/generated/rules_enum.rs @@ -416,6 +416,7 @@ pub use crate::rules::react::no_find_dom_node::NoFindDomNode as ReactNoFindDomNo pub use crate::rules::react::no_is_mounted::NoIsMounted as ReactNoIsMounted; pub use crate::rules::react::no_multi_comp::NoMultiComp as ReactNoMultiComp; pub use crate::rules::react::no_namespace::NoNamespace as ReactNoNamespace; +pub use crate::rules::react::no_react_children::NoReactChildren as ReactNoReactChildren; pub use crate::rules::react::no_redundant_should_component_update::NoRedundantShouldComponentUpdate as ReactNoRedundantShouldComponentUpdate; pub use crate::rules::react::no_render_return_value::NoRenderReturnValue as ReactNoRenderReturnValue; pub use crate::rules::react::no_set_state::NoSetState as ReactNoSetState; @@ -1114,6 +1115,7 @@ pub enum RuleEnum { ReactNoIsMounted(ReactNoIsMounted), ReactNoMultiComp(ReactNoMultiComp), ReactNoNamespace(ReactNoNamespace), + ReactNoReactChildren(ReactNoReactChildren), ReactNoRedundantShouldComponentUpdate(ReactNoRedundantShouldComponentUpdate), ReactNoRenderReturnValue(ReactNoRenderReturnValue), ReactNoSetState(ReactNoSetState), @@ -1859,7 +1861,8 @@ const REACT_NO_FIND_DOM_NODE_ID: usize = REACT_NO_DIRECT_MUTATION_STATE_ID + 1us const REACT_NO_IS_MOUNTED_ID: usize = REACT_NO_FIND_DOM_NODE_ID + 1usize; const REACT_NO_MULTI_COMP_ID: usize = REACT_NO_IS_MOUNTED_ID + 1usize; const REACT_NO_NAMESPACE_ID: usize = REACT_NO_MULTI_COMP_ID + 1usize; -const REACT_NO_REDUNDANT_SHOULD_COMPONENT_UPDATE_ID: usize = REACT_NO_NAMESPACE_ID + 1usize; +const REACT_NO_REACT_CHILDREN_ID: usize = REACT_NO_NAMESPACE_ID + 1usize; +const REACT_NO_REDUNDANT_SHOULD_COMPONENT_UPDATE_ID: usize = REACT_NO_REACT_CHILDREN_ID + 1usize; const REACT_NO_RENDER_RETURN_VALUE_ID: usize = REACT_NO_REDUNDANT_SHOULD_COMPONENT_UPDATE_ID + 1usize; const REACT_NO_SET_STATE_ID: usize = REACT_NO_RENDER_RETURN_VALUE_ID + 1usize; @@ -2667,6 +2670,7 @@ impl RuleEnum { Self::ReactNoIsMounted(_) => REACT_NO_IS_MOUNTED_ID, Self::ReactNoMultiComp(_) => REACT_NO_MULTI_COMP_ID, Self::ReactNoNamespace(_) => REACT_NO_NAMESPACE_ID, + Self::ReactNoReactChildren(_) => REACT_NO_REACT_CHILDREN_ID, Self::ReactNoRedundantShouldComponentUpdate(_) => { REACT_NO_REDUNDANT_SHOULD_COMPONENT_UPDATE_ID } @@ -3469,6 +3473,7 @@ impl RuleEnum { Self::ReactNoIsMounted(_) => ReactNoIsMounted::NAME, Self::ReactNoMultiComp(_) => ReactNoMultiComp::NAME, Self::ReactNoNamespace(_) => ReactNoNamespace::NAME, + Self::ReactNoReactChildren(_) => ReactNoReactChildren::NAME, Self::ReactNoRedundantShouldComponentUpdate(_) => { ReactNoRedundantShouldComponentUpdate::NAME } @@ -4291,6 +4296,7 @@ impl RuleEnum { Self::ReactNoIsMounted(_) => ReactNoIsMounted::CATEGORY, Self::ReactNoMultiComp(_) => ReactNoMultiComp::CATEGORY, Self::ReactNoNamespace(_) => ReactNoNamespace::CATEGORY, + Self::ReactNoReactChildren(_) => ReactNoReactChildren::CATEGORY, Self::ReactNoRedundantShouldComponentUpdate(_) => { ReactNoRedundantShouldComponentUpdate::CATEGORY } @@ -5108,6 +5114,7 @@ impl RuleEnum { Self::ReactNoIsMounted(_) => ReactNoIsMounted::FIX, Self::ReactNoMultiComp(_) => ReactNoMultiComp::FIX, Self::ReactNoNamespace(_) => ReactNoNamespace::FIX, + Self::ReactNoReactChildren(_) => ReactNoReactChildren::FIX, Self::ReactNoRedundantShouldComponentUpdate(_) => { ReactNoRedundantShouldComponentUpdate::FIX } @@ -6003,6 +6010,7 @@ impl RuleEnum { Self::ReactNoIsMounted(_) => ReactNoIsMounted::documentation(), Self::ReactNoMultiComp(_) => ReactNoMultiComp::documentation(), Self::ReactNoNamespace(_) => ReactNoNamespace::documentation(), + Self::ReactNoReactChildren(_) => ReactNoReactChildren::documentation(), Self::ReactNoRedundantShouldComponentUpdate(_) => { ReactNoRedundantShouldComponentUpdate::documentation() } @@ -7558,6 +7566,8 @@ impl RuleEnum { .or_else(|| ReactNoMultiComp::schema(generator)), Self::ReactNoNamespace(_) => ReactNoNamespace::config_schema(generator) .or_else(|| ReactNoNamespace::schema(generator)), + Self::ReactNoReactChildren(_) => ReactNoReactChildren::config_schema(generator) + .or_else(|| ReactNoReactChildren::schema(generator)), Self::ReactNoRedundantShouldComponentUpdate(_) => { ReactNoRedundantShouldComponentUpdate::config_schema(generator) .or_else(|| ReactNoRedundantShouldComponentUpdate::schema(generator)) @@ -8814,6 +8824,7 @@ impl RuleEnum { Self::ReactNoIsMounted(_) => "react", Self::ReactNoMultiComp(_) => "react", Self::ReactNoNamespace(_) => "react", + Self::ReactNoReactChildren(_) => "react", Self::ReactNoRedundantShouldComponentUpdate(_) => "react", Self::ReactNoRenderReturnValue(_) => "react", Self::ReactNoSetState(_) => "react", @@ -10401,6 +10412,9 @@ impl RuleEnum { Self::ReactNoNamespace(_) => { Ok(Self::ReactNoNamespace(ReactNoNamespace::from_configuration(value)?)) } + Self::ReactNoReactChildren(_) => { + Ok(Self::ReactNoReactChildren(ReactNoReactChildren::from_configuration(value)?)) + } Self::ReactNoRedundantShouldComponentUpdate(_) => { Ok(Self::ReactNoRedundantShouldComponentUpdate( ReactNoRedundantShouldComponentUpdate::from_configuration(value)?, @@ -11755,6 +11769,7 @@ impl RuleEnum { Self::ReactNoIsMounted(rule) => rule.to_configuration(), Self::ReactNoMultiComp(rule) => rule.to_configuration(), Self::ReactNoNamespace(rule) => rule.to_configuration(), + Self::ReactNoReactChildren(rule) => rule.to_configuration(), Self::ReactNoRedundantShouldComponentUpdate(rule) => rule.to_configuration(), Self::ReactNoRenderReturnValue(rule) => rule.to_configuration(), Self::ReactNoSetState(rule) => rule.to_configuration(), @@ -12457,6 +12472,7 @@ impl RuleEnum { Self::ReactNoIsMounted(rule) => rule.run(node, ctx), Self::ReactNoMultiComp(rule) => rule.run(node, ctx), Self::ReactNoNamespace(rule) => rule.run(node, ctx), + Self::ReactNoReactChildren(rule) => rule.run(node, ctx), Self::ReactNoRedundantShouldComponentUpdate(rule) => rule.run(node, ctx), Self::ReactNoRenderReturnValue(rule) => rule.run(node, ctx), Self::ReactNoSetState(rule) => rule.run(node, ctx), @@ -13157,6 +13173,7 @@ impl RuleEnum { Self::ReactNoIsMounted(rule) => rule.run_once(ctx), Self::ReactNoMultiComp(rule) => rule.run_once(ctx), Self::ReactNoNamespace(rule) => rule.run_once(ctx), + Self::ReactNoReactChildren(rule) => rule.run_once(ctx), Self::ReactNoRedundantShouldComponentUpdate(rule) => rule.run_once(ctx), Self::ReactNoRenderReturnValue(rule) => rule.run_once(ctx), Self::ReactNoSetState(rule) => rule.run_once(ctx), @@ -13927,6 +13944,7 @@ impl RuleEnum { Self::ReactNoIsMounted(rule) => rule.run_on_jest_node(jest_node, ctx), Self::ReactNoMultiComp(rule) => rule.run_on_jest_node(jest_node, ctx), Self::ReactNoNamespace(rule) => rule.run_on_jest_node(jest_node, ctx), + Self::ReactNoReactChildren(rule) => rule.run_on_jest_node(jest_node, ctx), Self::ReactNoRedundantShouldComponentUpdate(rule) => { rule.run_on_jest_node(jest_node, ctx) } @@ -14657,6 +14675,7 @@ impl RuleEnum { Self::ReactNoIsMounted(rule) => rule.should_run(ctx), Self::ReactNoMultiComp(rule) => rule.should_run(ctx), Self::ReactNoNamespace(rule) => rule.should_run(ctx), + Self::ReactNoReactChildren(rule) => rule.should_run(ctx), Self::ReactNoRedundantShouldComponentUpdate(rule) => rule.should_run(ctx), Self::ReactNoRenderReturnValue(rule) => rule.should_run(ctx), Self::ReactNoSetState(rule) => rule.should_run(ctx), @@ -15521,6 +15540,7 @@ impl RuleEnum { Self::ReactNoIsMounted(_) => ReactNoIsMounted::IS_TSGOLINT_RULE, Self::ReactNoMultiComp(_) => ReactNoMultiComp::IS_TSGOLINT_RULE, Self::ReactNoNamespace(_) => ReactNoNamespace::IS_TSGOLINT_RULE, + Self::ReactNoReactChildren(_) => ReactNoReactChildren::IS_TSGOLINT_RULE, Self::ReactNoRedundantShouldComponentUpdate(_) => { ReactNoRedundantShouldComponentUpdate::IS_TSGOLINT_RULE } @@ -16458,6 +16478,7 @@ impl RuleEnum { Self::ReactNoIsMounted(_) => ReactNoIsMounted::HAS_CONFIG, Self::ReactNoMultiComp(_) => ReactNoMultiComp::HAS_CONFIG, Self::ReactNoNamespace(_) => ReactNoNamespace::HAS_CONFIG, + Self::ReactNoReactChildren(_) => ReactNoReactChildren::HAS_CONFIG, Self::ReactNoRedundantShouldComponentUpdate(_) => { ReactNoRedundantShouldComponentUpdate::HAS_CONFIG } @@ -17220,6 +17241,7 @@ impl RuleEnum { Self::ReactNoIsMounted(rule) => rule.types_info(), Self::ReactNoMultiComp(rule) => rule.types_info(), Self::ReactNoNamespace(rule) => rule.types_info(), + Self::ReactNoReactChildren(rule) => rule.types_info(), Self::ReactNoRedundantShouldComponentUpdate(rule) => rule.types_info(), Self::ReactNoRenderReturnValue(rule) => rule.types_info(), Self::ReactNoSetState(rule) => rule.types_info(), @@ -17920,6 +17942,7 @@ impl RuleEnum { Self::ReactNoIsMounted(rule) => rule.run_info(), Self::ReactNoMultiComp(rule) => rule.run_info(), Self::ReactNoNamespace(rule) => rule.run_info(), + Self::ReactNoReactChildren(rule) => rule.run_info(), Self::ReactNoRedundantShouldComponentUpdate(rule) => rule.run_info(), Self::ReactNoRenderReturnValue(rule) => rule.run_info(), Self::ReactNoSetState(rule) => rule.run_info(), @@ -18708,6 +18731,7 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( RuleEnum::ReactNoIsMounted(ReactNoIsMounted::default()), RuleEnum::ReactNoMultiComp(ReactNoMultiComp::default()), RuleEnum::ReactNoNamespace(ReactNoNamespace::default()), + RuleEnum::ReactNoReactChildren(ReactNoReactChildren::default()), RuleEnum::ReactNoRedundantShouldComponentUpdate( ReactNoRedundantShouldComponentUpdate::default(), ), diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 52bc6fedbc335..fa6df03bc148d 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -416,6 +416,7 @@ pub(crate) mod react { pub mod no_is_mounted; pub mod no_multi_comp; pub mod no_namespace; + pub mod no_react_children; pub mod no_redundant_should_component_update; pub mod no_render_return_value; pub mod no_set_state; diff --git a/crates/oxc_linter/src/rules/react/no_react_children.rs b/crates/oxc_linter/src/rules/react/no_react_children.rs new file mode 100644 index 0000000000000..cb064a5adda07 --- /dev/null +++ b/crates/oxc_linter/src/rules/react/no_react_children.rs @@ -0,0 +1,236 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; + +use crate::{AstNode, context::LintContext, rule::Rule}; + +fn no_react_children_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("`React.Children` should not be used.") + .with_help("`React.Children` is uncommon and leads to fragile React components.") + .with_label(span) + .with_note("https://react.dev/reference/react/Children#alternatives") +} + +#[derive(Debug, Default, Clone)] +pub struct NoReactChildren; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallows the usage of `React.Children`, as it is considered a bad practice. + /// + /// ### Why is this bad? + /// + /// Using `React.Children` is + /// [discouraged by the React documentation](https://react.dev/reference/react/Children). + /// It is an uncommon pattern and can lead to fragile code. + /// + /// It is recommended to use alternative approaches for handling children. See the + /// [React documentation](https://react.dev/reference/react/Children#alternatives) for + /// more information. + /// + /// ::: tip + /// Don't confuse `React.Children` with using the `children` prop (lowercase `c`), which is + /// good and encouraged. + /// ::: + /// + /// Note that this rule is based on a combination of multiple rules from `@eslint-react/eslint-plugin`, + /// including [`@eslint-react/no-children-count`](https://www.eslint-react.xyz/docs/rules/no-children-count) + /// and [`@eslint-react/no-children-for-each`](https://www.eslint-react.xyz/docs/rules/no-children-for-each). + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```jsx + /// import { Children } from 'react'; + /// + /// Children.toArray(children); + /// Children.map(children, child =>
{child}
); + /// Children.only(children); + /// Children.count(children); + /// Children.forEach(children, (child, index) => {}); + /// ``` + /// + /// ```jsx + /// import React from 'react'; + /// + /// function Table({ children }) { + /// const mappedChildren = React.Children.map(children, (child) => + /// {child} + /// ); + /// + /// return {mappedChildren}
; + /// } + /// ``` + /// + /// ```jsx + /// import { Children } from 'react'; + /// + /// function RowList({ children }) { + /// return ( + /// <> + ///

Total rows: {Children.count(children)}

+ /// + /// ); + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```jsx + /// function Card({ children }) { + /// return ( + ///
+ /// {children} + ///
+ /// ); + /// } + /// ``` + NoReactChildren, + react, + restriction, + none +); + +impl Rule for NoReactChildren { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + + let Some(member_expr) = call_expr.callee.get_member_expr() else { + return; + }; + + let object = member_expr.object().get_inner_expression(); + + // Pattern 1: Children.method() where Children is imported from 'react' + if let Some(ident) = object.get_identifier_reference() + && ident.name == "Children" + && is_imported_from_react(ident.name.as_str(), ctx) + { + ctx.diagnostic(no_react_children_diagnostic(member_expr.span())); + return; + } + + // Pattern 2: React.Children.method() where React is imported from 'react' + if let Some(inner_member) = object.as_member_expression() + && inner_member.static_property_name() == Some("Children") + && let Some(ident) = + inner_member.object().get_inner_expression().get_identifier_reference() + && is_imported_from_react(ident.name.as_str(), ctx) + { + ctx.diagnostic(no_react_children_diagnostic(member_expr.span())); + } + } +} + +fn is_imported_from_react(local_name: &str, ctx: &LintContext) -> bool { + ctx.module_record().import_entries.iter().any(|entry| { + entry.module_request.name() == "react" && entry.local_name.name() == local_name + }) +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "import React from 'react';", + "const children = []; children.map(x => x)", + "const Children = { map: () => {} }; Children.map()", + "import React from 'react'; React.createElement('div')", + // Violations in comments do not count. + "const foo = []; /* React.Children */", + "const foo = []; /* import { Children } from 'react'; */", + "
Children
", + "Children", + "import React from 'react'; return React.Children", + r#""React.Children""#, + "import { Children } from 'something-else'; Children.toArray(children)", + // Unresolved Children reference (no import) - not flagged + "function Box({ children }) { const element = Children.only(children); }", + // Local React object, not an import + "const React = { Children: { map: () => {} } }; React.Children.map()", + "{children}", + // Usage of `children` in the props for a React component is fine. + r#"import React from 'react'; + function Card({ children }) { + return ( +
+ {children} +
+ ); + }"#, + ]; + + let fail = vec![ + // Named import { Children } + each method + "import { Children } from 'react'; Children.toArray(children)", + "import { Children } from 'react'; Children.map(children, child =>
{child}
)", + "import { Children } from 'react'; Children.only(children)", + "import { Children } from 'react'; Children.count(children)", + "import { Children } from 'react'; Children.forEach(children, (child, index) => {})", + // Default import React + React.Children.* + "import React from 'react'; React.Children.toArray(children)", + "import React from 'react'; React.Children.map(children, child =>
{child}
)", + "import React from 'react'; React.Children.only(children)", + "import React from 'react'; React.Children.count(children)", + "import React from 'react'; React.Children.forEach(children, (child, index) => {})", + // Wildcard import * as React + "import * as React from 'react'; React.Children.map(children, child =>
{child}
)", + // Combined import React, { Children } + "import React, { Children } from 'react'; Children.toArray(children)", + // Various complex examples + "import { Children } from 'react'; + function RowList({ children }) { + return ( + <>

Total rows: {Children.count(children)}

+ ); + }", + "import React from 'react'; + export const Table = ({ children }) => { + const mappedChildren = React.Children.map(children, (child) => + {child} + ); + return {mappedChildren}
; + }", + "import { Children } from 'react'; + function SeparatorList({ children }) { + const result = []; + Children.forEach(children, (child, index) => { + result.push(child); + result.push(
); + }); + }", + r#"import { Children } from 'react'; + function RowList({ children }) { + return ( +
+ {Children.map(children, child => +
+ {child} +
+ )} +
+ ); + }"#, + "import * as React from 'react'; + function SeparatorList({ children }) { + const result = []; + React.Children.forEach(children, (child, index) => { + result.push(child); + result.push(
); + }); + // ... + }", + // Optional chaining + "import React from 'react'; React.Children?.map(children, child => child)", + // Parenthesized expressions + "import { Children } from 'react'; (Children).map(children, child => child)", + "import React from 'react'; (React.Children).map(children, child => child)", + "import React from 'react'; (React.Children as any).map(children, child => child)", + ]; + + Tester::new(NoReactChildren::NAME, NoReactChildren::PLUGIN, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/react_no_react_children.snap b/crates/oxc_linter/src/snapshots/react_no_react_children.snap new file mode 100644 index 0000000000000..54f8783ed1723 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/react_no_react_children.snap @@ -0,0 +1,181 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:35] + 1 │ import { Children } from 'react'; Children.toArray(children) + · ──────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:35] + 1 │ import { Children } from 'react'; Children.map(children, child =>
{child}
) + · ──────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:35] + 1 │ import { Children } from 'react'; Children.only(children) + · ───────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:35] + 1 │ import { Children } from 'react'; Children.count(children) + · ────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:35] + 1 │ import { Children } from 'react'; Children.forEach(children, (child, index) => {}) + · ──────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:28] + 1 │ import React from 'react'; React.Children.toArray(children) + · ────────────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:28] + 1 │ import React from 'react'; React.Children.map(children, child =>
{child}
) + · ────────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:28] + 1 │ import React from 'react'; React.Children.only(children) + · ─────────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:28] + 1 │ import React from 'react'; React.Children.count(children) + · ──────────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:28] + 1 │ import React from 'react'; React.Children.forEach(children, (child, index) => {}) + · ────────────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:33] + 1 │ import * as React from 'react'; React.Children.map(children, child =>
{child}
) + · ────────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:42] + 1 │ import React, { Children } from 'react'; Children.toArray(children) + · ──────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:4:33] + 3 │ return ( + 4 │ <>

Total rows: {Children.count(children)}

+ · ────────────── + 5 │ ); + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:3:35] + 2 │ export const Table = ({ children }) => { + 3 │ const mappedChildren = React.Children.map(children, (child) => + · ────────────────── + 4 │ {child} + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:4:12] + 3 │ const result = []; + 4 │ Children.forEach(children, (child, index) => { + · ──────────────── + 5 │ result.push(child); + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:5:19] + 4 │
+ 5 │ {Children.map(children, child => + · ──────────── + 6 │
+ ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:4:12] + 3 │ const result = []; + 4 │ React.Children.forEach(children, (child, index) => { + · ────────────────────── + 5 │ result.push(child); + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:28] + 1 │ import React from 'react'; React.Children?.map(children, child => child) + · ─────────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:35] + 1 │ import { Children } from 'react'; (Children).map(children, child => child) + · ────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:28] + 1 │ import React from 'react'; (React.Children).map(children, child => child) + · ──────────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives + + ⚠ eslint-plugin-react(no-react-children): `React.Children` should not be used. + ╭─[no_react_children.tsx:1:28] + 1 │ import React from 'react'; (React.Children as any).map(children, child => child) + · ─────────────────────────── + ╰──── + help: `React.Children` is uncommon and leads to fragile React components. + note: https://react.dev/reference/react/Children#alternatives