Skip to content

Conversation

@dibashthapa
Copy link
Contributor

@dibashthapa dibashthapa commented Nov 19, 2025

Yes, i used claude and chatgpt to help me with

  • Writing Diagnostic Messages
  • Fixing the issues with some code that was not passing the test cases

I only used them as assistant and not for writing the feature on their own.

Summary

This PR ports the no-leaked-render rule from Eslint React

Fixes: #7659

Test Plan

I have added test cases based on validStrategies. Test cases for coerce is inside coerce folder and ternary is inside ternary folder.

But there is one code that is not throwing diagnositcs, and am still looking into it. It should throw error when the validStrategy is ["coerce"]

const MyComponent3 = () => {
	return <Something checked={isIndeterminate ? false : isChecked} />;
};

Docs

@changeset-bot
Copy link

changeset-bot bot commented Nov 19, 2025

🦋 Changeset detected

Latest commit: ce85fe2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis labels Nov 19, 2025
@dibashthapa dibashthapa marked this pull request as ready for review November 19, 2025 17:38
@codspeed-hq
Copy link

codspeed-hq bot commented Nov 19, 2025

CodSpeed Performance Report

Merging #8171 will not alter performance

Comparing dibashthapa:no-leaked-conditional-rendering (ce85fe2) with main (cd2edd7)

Summary

✅ 58 untouched
⏩ 95 skipped1

Footnotes

  1. 95 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@coderabbitai

This comment has been minimized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
.changeset/calm-shrimps-study.md (1)

1-5: Clarify rule naming in the changeset

To avoid mixing the ESLint and Biome names, consider something like:

-Fixed [#7659](https://github.com/biomejs/biome/issues/7659): Added the new rule [`jsx-no-leaked-render`](https://biomejs.dev/linter/rules/no-leaked-render) from ESLint
+Fixed [#7659](https://github.com/biomejs/biome/issues/7659): Added the new rule [`no-leaked-render`](https://biomejs.dev/linter/rules/no-leaked-render), ported from ESLint’s `jsx-no-leaked-render`

Keeps the origin clear whilst matching the Biome rule slug.

crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.options.json (1)

1-16: Coerce/invalid options are correct; consider adding the known edge-case fixture

The configuration itself looks good (validStrategies: ["coerce"], level error).

Given you called out the checked={isIndeterminate ? false : isChecked} case as a known gap for the coerce strategy, it might be worth adding that as an extra invalid JSX example in the coerce suite once the implementation handles it, so we lock in coverage.

crates/biome_rule_options/src/no_leaked_render.rs (1)

3-15: Options wiring looks consistent with other rules

The NoLeakedRenderOptions struct and Merge impl are straightforward: valid_strategies cleanly overrides when provided, and the serde configuration matches the expected validStrategies shape in tests. Only minor thought: if a user ever passes an empty validStrategies array, it will effectively disable both strategies rather than falling back to the default pair, which is probably fine but worth being aware of.

crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (2)

168-244: Logical && handling mostly matches the intended coerce semantics

The JsLogicalExpression branch correctly:

  • Restricts the rule to &&.
  • Treats unary/call/binary left sides (and nested logicals of those) as “coerce-safe”.
  • Special-cases identifiers initialised to true/false via the semantic model, which handles boolean flags like isOpen1/isOpen2 in the fixtures.
  • Leaves classic leaky cases (count && title, array.length && <List />, etc.) to fall through and emit diagnostics.

This aligns well with the tests in invalid.jsx, coerce/valid.jsx, coerce/invalid.jsx, and ternary/invalid.jsx.

One small observation: the is_literal && left.to_trimmed_text().is_empty() check looks like it may never be true in practice (string/number literals will still have non-empty source text, including quotes), so that branch is probably redundant. Not a blocker, but you might want to either remove it or adjust it to whatever literal special-case you originally had in mind.


310-325: Nested logical-expression helper is conservative (by design)

get_is_coerce_valid_nested_logical_expression only treats nested logical expressions as coerce-safe when both sides are unary/call/binary expressions (possibly parenthesised). That’s enough to cover cases like (!display || display === DISPLAY.WELCOME) && <span>foo</span> from the fixtures, while deliberately treating mixed identifier conditions as unsafe.

If, in future, you decide to allow more complex but still boolean-only conditions (e.g. isLoading || hasError) without diagnostics, this helper is a good place to extend that logic.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 575129a and b4706d8.

⛔ Files ignored due to path filters (11)
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/src/lint/nursery.rs is excluded by !**/nursery.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (14)
  • .changeset/calm-shrimps-study.md (1 hunks)
  • crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.options.json (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.options.json (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.options.json (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.options.json (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx (1 hunks)
  • crates/biome_rule_options/src/lib.rs (1 hunks)
  • crates/biome_rule_options/src/no_leaked_render.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-05T14:43:29.581Z
Learnt from: dyc3
Repo: biomejs/biome PR: 7081
File: packages/@biomejs/biome/configuration_schema.json:7765-7781
Timestamp: 2025-08-05T14:43:29.581Z
Learning: The file `packages/biomejs/biome/configuration_schema.json` is auto-generated and should not be manually edited or reviewed for schema issues; any changes should be made at the code generation source.

Applied to files:

  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.options.json
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.options.json
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.options.json
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.options.json
🧬 Code graph analysis (7)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx (4)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx (6)
  • Component1 (13-15)
  • Component2 (17-19)
  • Component3 (21-23)
  • Component4 (25-29)
  • Component5 (31-33)
  • Component6 (35-37)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx (6)
  • Component1 (3-5)
  • Component2 (7-9)
  • Component3 (11-13)
  • Component4 (15-19)
  • Component5 (21-23)
  • Component6 (25-27)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx (1)
  • Component1 (3-5)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx (8)
  • Component1 (3-5)
  • Component2 (7-9)
  • Component3 (11-13)
  • Component4 (15-17)
  • isOpen1 (56-56)
  • Component5 (19-21)
  • isOpen2 (61-61)
  • Component6 (23-25)
crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
  • semantic (43-45)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx (3)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx (6)
  • Component1 (13-15)
  • Component2 (17-19)
  • Component3 (21-23)
  • Component4 (25-29)
  • Component5 (31-33)
  • Component6 (35-37)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx (8)
  • Component1 (3-5)
  • Component2 (7-14)
  • Component3 (16-25)
  • Component4 (27-29)
  • Component5 (32-34)
  • Component6 (37-39)
  • isOpen1 (31-31)
  • isOpen2 (36-36)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx (1)
  • Component1 (3-5)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx (2)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx (6)
  • Component1 (3-5)
  • Component2 (7-14)
  • Component3 (16-25)
  • Component4 (27-29)
  • Component5 (32-34)
  • Component6 (37-39)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx (7)
  • Component1 (3-5)
  • Component2 (7-9)
  • Component3 (11-13)
  • Component4 (15-17)
  • Component5 (19-21)
  • Component6 (23-25)
  • Component7 (27-29)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx (4)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx (7)
  • Component1 (3-5)
  • Component2 (7-9)
  • Component3 (11-13)
  • Component4 (15-19)
  • Component5 (21-23)
  • Component6 (25-27)
  • MyComponent2 (50-52)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx (6)
  • Component1 (3-5)
  • Component2 (7-14)
  • Component3 (16-25)
  • Component4 (27-29)
  • Component5 (32-34)
  • Component6 (37-39)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx (6)
  • Component1 (1-3)
  • Component2 (5-7)
  • Component3 (9-11)
  • Component4 (13-17)
  • Component5 (19-21)
  • Component6 (23-25)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx (1)
  • Component1 (3-5)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx (3)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx (1)
  • Component1 (13-15)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx (1)
  • Component1 (3-5)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx (1)
  • Component1 (1-3)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx (3)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx (6)
  • Component1 (3-5)
  • Component2 (7-14)
  • Component3 (16-25)
  • Component4 (27-29)
  • Component5 (32-34)
  • Component6 (37-39)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx (1)
  • Component1 (3-5)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx (12)
  • Component1 (3-5)
  • Component2 (7-9)
  • Component3 (11-13)
  • Component4 (15-17)
  • Component5 (19-21)
  • Component6 (23-25)
  • Component7 (27-29)
  • Component8 (31-33)
  • Component9 (35-37)
  • Component10 (39-41)
  • Component11 (43-50)
  • Component12 (52-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: autofix
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Check JS Files
  • GitHub Check: Documentation
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: End-to-end tests
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test Node.js API
🔇 Additional comments (12)
crates/biome_rule_options/src/lib.rs (1)

123-123: New rule options module export looks consistent

pub mod no_leaked_render; fits the existing naming and sort order for rule option modules; no issues from this side.

crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.options.json (1)

1-16: Config for coerce/valid matches expected shape

Schema reference, rule path, level, and validStrategies: ["coerce"] all look consistent with the rest of the suite.

crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx (1)

1-5: Good minimal valid ternary example

This nicely captures the “use a ternary, not &&” pattern the rule recommends; fine as a focused green-path fixture.

crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.options.json (1)

1-16: Ternary/valid options are consistent with coerce variant

Rule path, level, and validStrategies: ["ternary"] are all in line with the rest of the specs.

crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.options.json (1)

1-16: Ternary/invalid options wiring looks fine

Identical structure to the valid config, just paired with the red-path fixtures as expected.

crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx (1)

1-39: Coerce/valid fixtures exercise a good spread of safe patterns

These components cover the key “coerced to boolean” shapes (double negation, boolean comparisons, compound conditions, and boolean props) without leaking raw counts or lengths, which is exactly what we want under the coerce strategy. Looks solid.

crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx (1)

3-64: Valid fixture set looks comprehensive

This covers the main “safe” patterns (explicit coercion, comparisons, ternaries, boolean flags) the rule is meant to allow, including the Popover open cases; nothing here looks like it should trigger diagnostics under the default strategies.

crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx (1)

78-81: Nice edge case around numeric “open” flags

Using const isOpen = 0; here is a neat way to ensure the rule distinguishes boolean flags from numeric falsy values when guarding a prop. This should stay invalid under coerce-only strategy and matches the design of the Popover tests in coerce/valid.jsx.

crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx (1)

3-53: Good baseline invalid coverage for default options

These fixtures neatly capture the core leaked-render cases (falsy primitives, length checks, nested collections, and fragment-wrapped blocks) that should be reported when both strategies are enabled. They line up well with the examples in the rule docs.

crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx (1)

1-50: Fixtures clearly express “ternary-only” expectations

This file does a good job of asserting that all &&-based boolean coercion patterns (including !!count, comparisons, and compound conditions) remain invalid when validStrategies is ['ternary']. It pairs well with ternary/valid.jsx and with the coerce-specific fixtures.

crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (2)

141-156: Semantic lookup helper is a nice touch

get_variable_from_context walking ancestor scopes to find bindings and then checking boolean literals is a solid way to avoid false positives for flag-style variables (as used in the Popover examples). This is a good balance between correctness and complexity for a lint rule.


245-264: Proposal oversimplifies ternary handling; risks breaking valid coerce fixtures

You're correct that MyComponent3 (checked={isIndeterminate ? false : isChecked}) isn't currently being flagged—its alternate isChecked doesn't match the literal-only check. However, the proposed fix (flagging all ternaries when "ternary" isn't in strategies) would also flag Component4 in coerce/valid.jsx (count ? <List /> : <EmptyList />), which is marked as valid under ["coerce"].

The actual distinction likely hinges on ternary context:

  • Ternaries in JSX attributes (MyComponent3): intended as invalid under coerce
  • Ternaries conditionally rendering JSX (Component4): intended as valid

Before merging this change, please:

  1. Run the full noLeakedRender test suite with the proposed fix applied
  2. Confirm MyComponent3 now reports a diagnostic
  3. Verify coerce/valid.jsx (especially Component4 and Component2) remain diagnostics-free
  4. If Component4 starts failing, refine the logic to distinguish attribute ternaries from JSX-rendering ternaries

@github-actions github-actions bot added the A-CLI Area: CLI label Nov 19, 2025
@dibashthapa
Copy link
Contributor Author

dibashthapa commented Nov 19, 2025

These are some solid feedbacks. I will work on them tomorrow :)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (2)

15-121: Rule docs and validStrategies section still inconsistent and have minor formatting issues

The doc block is clear overall, but the earlier concerns are still present:

  • It calls ternary alternates like null, undefined, or false “problematic”, yet immediately shows user ? <Profile /> : null as a valid pattern without mentioning the validStrategies interaction.
  • The validStrategies section focuses on how fixes transform code (“will transform…”, “transforms the binary expression…”) instead of clearly stating that it controls which syntactic guard patterns are treated as valid and which are reported, per the upstream rule.
  • Typo + formatting:
    • "ternay""ternary" in the validStrategies description.
    • The stray closing code fence at Lines 118–119 will break rendered docs.

I’d recommend tightening this section to explicitly document:

  • Default: flags unsafe && guards; treats both coerce-style conditions and ternary guards as valid when their strategies are enabled.
  • With ["coerce"]: ternary guards like count ? title : null are reported.
  • With ["ternary"]: coerce-style && guards like !!count && title are reported.

And fix the typo and remove the extra ``` fence. The semantics section of the upstream react/jsx-no-leaked-render docs is a good model to mirror.

Confirm from the latest `eslint-plugin-react` documentation for `jsx-no-leaked-render` how `validStrategies` affects which patterns are considered valid vs. reported (beyond just controlling autofix behavior).

265-305: Diagnostic messages: spacing/capitalization/punctuation still need a quick polish

The diagnostics are helpful, but the earlier cosmetic issues remain:

  • "').React" should be "'). React".
  • "boolean.Use" should be "boolean. Use".
  • "This happens When" should be "This happens when".
  • "null,undefined" should be "null, undefined".
  • The second note should end with a period for consistency.

Concretely:

-                        "JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output."
+                        "JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output."
@@
-                        "Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression."
+                        "Make sure the condition is explicitly boolean. Use !!value, value > 0, or a ternary expression."
@@
-                        "This happens When you use ternary operators in JSX with alternate values like null,undefined, or false"
+                        "This happens when you use ternary operators in JSX with alternate values like null, undefined, or false."
@@
-                        "Replace with a safe alternate value like an empty string or another JSX element"
+                        "Replace it with a safe alternate value like an empty string or another JSX element."
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1)

132-153: Helper and defaults are reasonable; potential future guard on empty validStrategies

COERCE_STRATEGY, TERNARY_STRATEGY, DEFAULT_VALID_STRATEGIES, and get_variable_from_context are implemented cleanly and do what they need to (find a binding and check if it’s initialized with a literal boolean).

One small edge case to keep in mind: if users configure "validStrategies": [], the code will treat both strategies as disabled (contrary to the docs’ “at least 1 is required” note) and will effectively warn on all && and relevant ternaries. If you want to enforce “non-empty” here rather than elsewhere, you could normalize an empty vector back to DEFAULT_VALID_STRATEGIES.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4706d8 and 35683b8.

⛔ Files ignored due to path filters (1)
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**
📒 Files selected for processing (2)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs (1 hunks)
  • crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs (2)
packages/@biomejs/backend-jsonrpc/src/workspace.ts (1)
  • Nursery (1857-2107)
crates/biome_configuration/src/analyzer/linter/rules.rs (1)
  • group (866-1251)
crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1)
packages/@biomejs/backend-jsonrpc/src/workspace.ts (2)
  • RuleDomain (976-984)
  • NoLeakedRenderOptions (6249-6251)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: autofix
  • GitHub Check: Test Node.js API
  • GitHub Check: Check JS Files
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
🔇 Additional comments (6)
crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs (1)

2512-2523: LGTM! Migration logic correctly implemented.

The migration path for react/no-leaked-rendernursery.no_leaked_render follows the established pattern perfectly. The nursery check, group initialization, rule access, and level setting all match the conventions used throughout this file.

Note: Since this is a generated file (line 1), ensure the source codegen has been properly updated to persist these changes.

crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (5)

120-129: Rule metadata looks consistent with other React lint rules

version: "next", name: "noLeakedRender", language: "jsx", domains: &[RuleDomain::React], and the RuleSource::EslintReact("no-leaked-render") mapping all look correct and aligned with the upstream rule naming.


155-163: Query union and Rule type wiring look correct

The declare_node_union! for Query = JsLogicalExpression | JsConditionalExpression and the Rule associated types (Semantic<Query>, State = bool, Signals = Option<bool>, Options = NoLeakedRenderOptions) are wired correctly for the rule engine and match the intended scope of this lint.


165-241: Logical && handling matches intended “coerce” strategy behavior

The JsLogicalExpression branch:

  • Correctly filters to && only.
  • Respects validStrategies by only applying the coerce checks when "coerce" is present.
  • Treats unary/call/binary expressions and recursively nested logical expressions as “coerced”/safe.
  • Treats identifiers initialized to literal true/false as safe.
  • Leaves genuinely unsafe guards as diagnostics.

This broadly aligns with the upstream rule’s intent of requiring either explicit coercion or ternary guards to avoid leaking falsy values like 0 or NaN.

No functional issues spotted here.


307-323: Nested logical-expression helper is correct and termination-safe

get_is_coerce_valid_nested_logical_expression correctly:

  • Recurses into nested JsLogicalExpression and JsParenthesizedExpression.
  • Treats unary/call/binary expressions as valid “coerce” leaves.
  • Returns false for everything else, ensuring the recursion always terminates.

This lines up with the logic in the && branch and doesn’t present correctness or performance issues.


242-263: Ternary analysis misses problematic values in the consequent branch (known bug) and is a bit asymmetric

In the JsConditionalExpression branch:

  • The rule short-circuits when "ternary" is in valid_strategies, which is correct for the default behavior.
  • When "ternary" is not allowed (e.g. validStrategies: ["coerce"]), it only inspects expr.alternate() for "null" | "undefined" | "false".

This means cases like the one called out in the PR body:

const MyComponent3 = () => {
  return <Something checked={isIndeterminate ? false : isChecked} />;
};

are never reported, because the “problematic” sentinel (false) lives in the consequent, not the alternate. More generally, any test ? false | null | undefined : value pattern in JSX will be silently accepted under ["coerce"], which diverges from the expectations for this port.

A minimal fix is to treat both branches symmetrically and consider them problematic whenever they’re one of the sentinel values and not a JSX element, e.g.:

-                let alternate = expr.alternate().ok()?;
-                let is_problematic_alternate = TERNARY_INVALID_ALTERNATE_VALUES
-                    .iter()
-                    .any(|&s| alternate.to_trimmed_text() == s);
-
-                let is_jsx_element_alt = matches!(alternate, AnyJsExpression::JsxTagExpression(_));
-
-                if !is_problematic_alternate || is_jsx_element_alt {
-                    return None;
-                }
-
-                return Some(true);
+                let consequent = expr.consequent().ok()?;
+                let alternate = expr.alternate().ok()?;
+
+                let is_problematic_branch = |expr: &AnyJsExpression| {
+                    // JSX elements are always considered safe fallbacks.
+                    if matches!(expr, AnyJsExpression::JsxTagExpression(_)) {
+                        return false;
+                    }
+                    let text = expr.to_trimmed_text();
+                    TERNARY_INVALID_ALTERNATE_VALUES
+                        .iter()
+                        .any(|&sentinel| text == sentinel)
+                };
+
+                let has_problematic_branch =
+                    is_problematic_branch(&consequent) || is_problematic_branch(&alternate);
+
+                if !has_problematic_branch {
+                    return None;
+                }
+
+                Some(true)

This should make validStrategies: ["coerce"] flag both count ? title : null (as in the upstream docs) and attribute patterns like isIndeterminate ? false : isChecked, while still allowing ternaries that return a genuine fallback value.

Copy link
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be a great rule to have!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (2)

90-119: validStrategies docs: fix typo and stray code fence

The validStrategies section still has the “ternay” typo and an extra ``` fence at Lines 118–119, which will break the rendered docs. While you’re here, it’d be good to tighten the wording so it matches what the rule actually does and the upstream rule’s intent.


291-296: Tidy up diagnostic note grammar / spacing

Minor copy polish to make the diagnostics read cleanly:

- "JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output."
+ "JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output."
@@
- "Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression."
+ "Make sure the condition is explicitly boolean. Use !!value, value > 0, or a ternary expression."
@@
- "This happens When you use ternary operators in JSX with alternate values like null,undefined, or false"
+ "This happens when you use ternary operators in JSX with alternate values like null, undefined, or false."
@@
- "Replace with a safe alternate value like an empty string or another JSX element"
+ "Replace it with a safe alternate value like an empty string or another JSX element."

Also applies to: 308-313

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35683b8 and 6e87da0.

⛔ Files ignored due to path filters (6)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (2)
  • crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1 hunks)
  • crates/biome_rule_options/src/lib.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_rule_options/src/lib.rs
🧰 Additional context used
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
  • semantic (43-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Check Dependencies
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: End-to-end tests
  • GitHub Check: Check JS Files
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: autofix
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_formatter)

@dyc3 dyc3 requested a review from ematipico November 21, 2025 01:22
Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cheers! I found some code that we should optimise. Particuarly, we should remove the use of the recursion

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1)

245-245: Missing space after full stop in diagnostic note.

Line 245: "boolean.Use" should be "boolean. Use" (add a space after the period).

This was flagged in a previous review but appears to still be present in the code.

Apply this diff:

-                        "Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression."
+                        "Make sure the condition is explicitly boolean. Use !!value, value > 0, or a ternary expression."
🧹 Nitpick comments (4)
crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (4)

207-210: Unreachable literal check or unclear intent.

At line 208, you check if a literal's trimmed text is empty and return None if so. However, it's unclear what AST literal nodes would have empty text representation—most literals (0, null, "", etc.) have non-empty text. This either handles an edge case that needs a comment, or is dead code that can be removed.

Could you clarify the intent or add a comment explaining which literals this guards against?


178-205: Consider reducing nesting with if let chains or extracting a helper.

The semantic model resolution at lines 178-205 uses a long chain of and_then calls with nested closures. This is functional but hard to scan. Rust's if let chains (or a small helper function) would flatten the control flow and make the boolean literal check more readable.

For example:

if let Some(binding) = model.binding(&name)
    && let Some(declaration) = binding.tree().declaration()
    && let AnyJsBindingDeclaration::JsVariableDeclarator(declarator) = declaration
    && let Some(initializer) = declarator.initializer()
    && let Ok(expr) = initializer.expression()
    && let AnyJsExpression::AnyJsLiteralExpression(literal) = expr
    && let Ok(token) = literal.value_token()
    && matches!(token.text_trimmed(), "true" | "false")
{
    return None;
}

Non-blocking, but improves clarity.


216-221: Clarify ternary alternate logic with a comment or variable.

Lines 216-219 define two booleans and then return None if !is_alternate_identifier || is_jsx_element_alt. The double-negative (!is_alternate_identifier OR ...) makes it slightly harder to reason about at a glance.

Consider either:

  • Adding a comment explaining we only flag identifiers that aren't JSX elements, or
  • Inverting to if is_alternate_identifier && !is_jsx_element_alt { Some(true) } else { None }

to make the intent clearer.


270-276: Add a docstring to explain the function's purpose.

As noted in previous review feedback, helper functions should have docstrings explaining their business logic. Please add a doc comment describing that this function checks whether a node appears within a JSX expression context (as a child, attribute value, or tag expression) by walking the ancestor chain.

Based on past feedback from maintainer.

Example:

/// Returns true if the given node is used within a JSX expression context.
///
/// This checks whether the node appears as:
/// - A JSX expression child (e.g., `{expr}` inside JSX)
/// - A JSX expression attribute value (e.g., `attr={expr}`)
/// - A JSX tag expression
fn is_inside_jsx_expression(node: &JsSyntaxNode) -> bool {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3fde28 and 75b38bf.

📒 Files selected for processing (2)
  • .changeset/calm-shrimps-study.md (1 hunks)
  • crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .changeset/calm-shrimps-study.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-21T01:10:53.036Z
Learnt from: dyc3
Repo: biomejs/biome PR: 8171
File: crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs:125-137
Timestamp: 2025-11-21T01:10:53.036Z
Learning: In the Biome codebase, each lint rule has its own options type declaration (e.g., `type Options = RuleNameOptions`) as part of the codegen process, even if the options struct is empty or unused. This is standard practice and should not be flagged as an issue.

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: autofix
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Check Dependencies
  • GitHub Check: Test Node.js API
  • GitHub Check: Check JS Files

"This happens when you use ternary operators in JSX with alternate values that could be variables"
})
.note(markup! {
"Replace with a safe alternate value like an empty string , null or another JSX element"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix spacing and punctuation in diagnostic note.

Line 262 has two minor issues:

  • Extra space before comma: "string , null" should be "string, null"
  • Missing terminal punctuation at the end

Apply this diff:

-                        "Replace with a safe alternate value like an empty string , null or another JSX element"
+                        "Replace with a safe alternate value like an empty string, null, or another JSX element."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"Replace with a safe alternate value like an empty string , null or another JSX element"
"Replace with a safe alternate value like an empty string, null, or another JSX element."
🤖 Prompt for AI Agents
In crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs around line 262,
fix the diagnostic note string by removing the extra space before the comma and
adding terminal punctuation: change `"Replace with a safe alternate value like
an empty string , null or another JSX element"` to `"Replace with a safe
alternate value like an empty string, null, or another JSX element."` so spacing
and punctuation are correct.

Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome thank you! I think we're good. I left one minor improvement

@@ -0,0 +1,70 @@
// Invalid cases with default options (both 'coerce' and 'ternary' strategies)
Copy link
Member

@ematipico ematipico Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Invalid cases with default options (both 'coerce' and 'ternary' strategies)
// should generate diagnostics
// Invalid cases with default options (both 'coerce' and 'ternary' strategies)

FYI If you accept, this will fail the snapshot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will check

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed my old comment and replaced with should generate diagnostics

Comment on lines 141 to 149
AnyJsExpression::JsLogicalExpression(expr) => {
let left = expr.left().ok()?;
let right = expr.right().ok()?;
stack.push(left);
stack.push(right);
}
AnyJsExpression::JsParenthesizedExpression(expr) => {
stack.push(expr.expression().ok()?);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last thing. If left, right and expression() are AnyJsExpression, I suggest you go call omit_parentheses(), so we remove the parentheses.

In fact, we have a few tests with binary expressions that contain parentheses. Can we add more of them? Let's get creative and add absurd cases, e.g.:

const Component1 = ({ count, title }) => {
	return <div>{(((((count))))) && ((title))}</div>;
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried adding the code you suggested, but parentheses were automatically omitted by the formatter, haha

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx (1)

1-2: Consider a more descriptive comment.

The comment could specify that these are invalid cases for the noLeakedRender rule, helping future contributors understand the test's purpose.

-// should generate diagnostics
+// Invalid cases: should generate noLeakedRender diagnostics
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcc9fbb and af0ce0b.

⛔ Files ignored due to path filters (1)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (1)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx (1)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx (8)
  • Component1 (2-4)
  • Component2 (6-8)
  • Component3 (10-12)
  • Component4 (14-16)
  • Component5 (18-20)
  • Component6 (22-24)
  • isOpen1 (66-66)
  • Component7 (26-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Check JS Files
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Test Node.js API
  • GitHub Check: autofix
🔇 Additional comments (3)
crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx (3)

3-11: LGTM!

Clean test of literal falsy values that would leak into the DOM.


13-37: LGTM!

These components properly demonstrate leaked render patterns with props.


59-65: The unbound variables are intentional test fixtures, not defects.

The entire test file deliberately uses unbound identifiers (someCondition, maybeObject, elements, etc.) to trigger the rule's diagnostics. MyComponent4–5 use ternary patterns whilst others use AND logic, but mixing strategies within a single test file is acceptable—they all target the same rule with different code patterns. No fixes required; the code is fit for purpose.

Likely an incorrect or invalid review comment.

Comment on lines +39 to +57
const MyComponent1 = () => {
return (
<>
{someCondition && (
<div>
<p>hello</p>
</div>
)}
</>
);
};

const MyComponent2 = () => {
return <>{someCondition && <SomeComponent prop1={val1} prop2={val2} />}</>;
};

const MyComponent3 = () => {
return <div>{maybeObject && (isFoo ? <Aaa /> : <Bbb />)}</div>;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unbound identifiers prevent semantic testing.

someCondition, val1, val2, maybeObject, and isFoo are all undefined. Based on the past review of Component7, unbound identifiers cause the rule to diagnose the wrong issue (undefined reference) rather than testing the intended leaked-render pattern. Define these as constants or props to properly exercise semantic lookup.

+// Define test values to exercise semantic patterns
+const someCondition = 0; // or other falsy value
+const val1 = '';
+const val2 = NaN;
+const maybeObject = null;
+const isFoo = false;
+
 const MyComponent1 = () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const MyComponent1 = () => {
return (
<>
{someCondition && (
<div>
<p>hello</p>
</div>
)}
</>
);
};
const MyComponent2 = () => {
return <>{someCondition && <SomeComponent prop1={val1} prop2={val2} />}</>;
};
const MyComponent3 = () => {
return <div>{maybeObject && (isFoo ? <Aaa /> : <Bbb />)}</div>;
};
// Define test values to exercise semantic patterns
const someCondition = 0; // or other falsy value
const val1 = '';
const val2 = NaN;
const maybeObject = null;
const isFoo = false;
const MyComponent1 = () => {
return (
<>
{someCondition && (
<div>
<p>hello</p>
</div>
)}
</>
);
};
const MyComponent2 = () => {
return <>{someCondition && <SomeComponent prop1={val1} prop2={val2} />}</>;
};
const MyComponent3 = () => {
return <div>{maybeObject && (isFoo ? <Aaa /> : <Bbb />)}</div>;
};
🤖 Prompt for AI Agents
In crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx around
lines 39 to 57, unbound identifiers (someCondition, val1, val2, maybeObject,
isFoo) cause the test to fail semantic lookup and produce undefined-reference
diagnostics; fix by defining those identifiers (e.g., add const declarations at
the top of the test file or accept them as component props and pass mock values)
so the components compile and the leaked-render rule is exercised — add minimal
stable definitions (booleans/values or mocked objects) before the components to
ensure semantic testing targets the intended leaked-render pattern.

Comment on lines +67 to +70
const isOpen1 = 0;
const Component7 = () => {
return <Popover open={isOpen1 && items.length > 0} />;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unbound items identifier.

Whilst the isOpen1 binding is now correct, items remains undefined. This prevents the rule from testing the intended semantic pattern (constant zero in a logical AND). Define items to complete the fix from the previous review.

 const isOpen1 = 0;
+const items = [1, 2, 3]; // or appropriate test data
 const Component7 = () => {
 	return <Popover open={isOpen1 && items.length > 0} />;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isOpen1 = 0;
const Component7 = () => {
return <Popover open={isOpen1 && items.length > 0} />;
};
const isOpen1 = 0;
const items = [1, 2, 3]; // or appropriate test data
const Component7 = () => {
return <Popover open={isOpen1 && items.length > 0} />;
};
🤖 Prompt for AI Agents
In crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx around
lines 67-70, the JSX uses an unbound identifier `items`; add a local binding for
`items` (for example an array or whatever shape the tests expect) declared
before Component7 so `items` is in scope when evaluating `isOpen1 &&
items.length > 0`, keeping the test focused on the constant-zero logical AND
case.

- Added two test case with parentheses
- Added method to remove parentheses
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (2)

258-258: Fix spacing and add terminal punctuation.

Line 258 has an extra space before the comma ("string , null") and is missing a full stop at the end.

Apply this diff:

-                        "Replace with a safe alternate value like an empty string , null or another JSX element"
+                        "Replace with a safe alternate value like an empty string, null, or another JSX element."

241-241: Missing space after full stop in diagnostic message.

Line 241 has "boolean.Use" which should be "boolean. Use" for proper spacing.

Apply this diff:

-                        "Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression."
+                        "Make sure the condition is explicitly boolean. Use !!value, value > 0, or a ternary expression."
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af0ce0b and bab33ee.

⛔ Files ignored due to path filters (1)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (2)
  • crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-25T07:22:18.540Z
Learnt from: ematipico
Repo: biomejs/biome PR: 7852
File: crates/biome_css_parser/src/syntax/property/mod.rs:161-168
Timestamp: 2025-10-25T07:22:18.540Z
Learning: In the Biome CSS parser, lexer token emission should not be gated behind parser options like `is_tailwind_directives_enabled()`. The lexer must emit correct tokens regardless of parser options to enable accurate diagnostics and error messages when the syntax is used incorrectly.

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs
📚 Learning: 2025-11-21T01:10:53.036Z
Learnt from: dyc3
Repo: biomejs/biome PR: 8171
File: crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs:125-137
Timestamp: 2025-11-21T01:10:53.036Z
Learning: In the Biome codebase, each lint rule has its own options type declaration (e.g., `type Options = RuleNameOptions`) as part of the codegen process, even if the options struct is empty or unused. This is standard practice and should not be flagged as an issue.

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs

@dibashthapa
Copy link
Contributor Author

@ematipico i have added new test cases with nested parentheses, and also used omit_parentheses method

@ematipico ematipico added this pull request to the merge queue Nov 21, 2025
Merged via the queue into biomejs:main with commit 79adaea Nov 21, 2025
11 checks passed
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1)

238-242: Tidy spacing and punctuation in diagnostic notes

Two tiny text nits still sneaked through:

  • Missing space after the full stop: "boolean.Use".
  • Extra space before the comma and slightly awkward list: "empty string , null or another JSX element.".

A small tweak makes the messages read more naturally:

-                    .note(markup! {
-                        "Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression."
-                    })
+                    .note(markup! {
+                        "Make sure the condition is explicitly boolean. Use !!value, value > 0, or a ternary expression."
+                    })
@@
-                    .note(markup! {
-                        "Replace with a safe alternate value like an empty string , null or another JSX element."
-                    })
+                    .note(markup! {
+                        "Replace with a safe alternate value like an empty string, null, or another JSX element."
+                    })

Also applies to: 254-259

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8b0dd1 and 96d313d.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-25T07:22:18.540Z
Learnt from: ematipico
Repo: biomejs/biome PR: 7852
File: crates/biome_css_parser/src/syntax/property/mod.rs:161-168
Timestamp: 2025-10-25T07:22:18.540Z
Learning: In the Biome CSS parser, lexer token emission should not be gated behind parser options like `is_tailwind_directives_enabled()`. The lexer must emit correct tokens regardless of parser options to enable accurate diagnostics and error messages when the syntax is used incorrectly.

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs
📚 Learning: 2025-11-21T01:10:53.036Z
Learnt from: dyc3
Repo: biomejs/biome PR: 8171
File: crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs:125-137
Timestamp: 2025-11-21T01:10:53.036Z
Learning: In the Biome codebase, each lint rule has its own options type declaration (e.g., `type Options = RuleNameOptions`) as part of the codegen process, even if the options struct is empty or unused. This is standard practice and should not be flagged as an issue.

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Check JS Files
  • GitHub Check: Test Node.js API
  • GitHub Check: autofix
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_configuration)
🔇 Additional comments (1)
crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs (1)

16-96: Rule definition and docs look solid

Nice, the rule metadata and examples line up with the implemented behaviour (JSX‑only, React domain, .inspired() rather than .same() with ESLint). The invalid/valid snippets read clearly and match what the linter is looking for.

Comment on lines +120 to +207
let left = exp.left().ok()?;

let is_left_hand_side_safe = matches!(
left,
AnyJsExpression::JsUnaryExpression(_)
| AnyJsExpression::JsCallExpression(_)
| AnyJsExpression::JsBinaryExpression(_)
);

if is_left_hand_side_safe {
return None;
}

let mut is_nested_left_hand_side_safe = false;

let mut stack = vec![left.clone()];

// Traverse the expression tree iteratively using a stack
// This allows us to check nested expressions without recursion
while let Some(current) = stack.pop() {
match current {
AnyJsExpression::JsLogicalExpression(expr) => {
let left = expr.left().ok()?.omit_parentheses();
let right = expr.right().ok()?.omit_parentheses();
stack.push(left);
stack.push(right);
}
AnyJsExpression::JsParenthesizedExpression(expr) => {
stack.push(expr.expression().ok()?.omit_parentheses());
}
// If we find expressions that coerce to boolean (unary, call, binary),
// then the entire expression is considered safe
AnyJsExpression::JsUnaryExpression(_)
| AnyJsExpression::JsCallExpression(_)
| AnyJsExpression::JsBinaryExpression(_) => {
is_nested_left_hand_side_safe = true;
break;
}
_ => {}
}
}

if is_nested_left_hand_side_safe {
return None;
}

if let AnyJsExpression::JsIdentifierExpression(ident) = &left {
let name = ident.name().ok()?;

// Use the semantic model to resolve the variable binding and check
// if it's initialized with a boolean literal. This allows us to
// handle cases like:
// let isOpen = false; // This is safe
// return <div>{isOpen && <Content />}</div>; // This should pass
if let Some(binding) = model.binding(&name)
&& binding
.tree()
.declaration()
.and_then(|declaration| {
if let AnyJsBindingDeclaration::JsVariableDeclarator(declarator) =
declaration
{
Some(declarator)
} else {
None
}
})
.and_then(|declarator| declarator.initializer())
.and_then(|initializer| initializer.expression().ok())
.and_then(|expr| {
if let AnyJsExpression::AnyJsLiteralExpression(literal) = expr {
Some(literal)
} else {
None
}
})
.and_then(|literal| literal.value_token().ok())
.is_some_and(|token| matches!(token.text_trimmed(), "true" | "false"))
{
return None;
}
}

let is_literal = matches!(left, AnyJsExpression::AnyJsLiteralExpression(_));
if is_literal && left.to_trimmed_text().is_empty() {
return None;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalise the left‑hand side with omit_parentheses() to avoid false positives

At the moment left is taken as‑is from the AST, so cases like:

const isOpen = false;
return <div>{(isOpen) && <Content />}</div>;

will be treated differently from isOpen && <Content /> even though they’re semantically identical. The same applies to parenthesised literals.

You can fix this by stripping parentheses once up front:

-                let left = exp.left().ok()?;
+                let left = exp.left().ok()?.omit_parentheses();

This will also make the identifier, literal, and is_literal checks behave consistently for expressions wrapped in extra brackets.

@github-actions github-actions bot mentioned this pull request Nov 21, 2025
ryan-m-walker pushed a commit to ryan-m-walker/biome that referenced this pull request Nov 23, 2025
…ejs#8171)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Emanuele Stoppa <[email protected]>
@github-actions github-actions bot mentioned this pull request Nov 25, 2025
@terrymun
Copy link

This is an amazing new rule to have 💯 thank you so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

📎 Port jsx-no-leaked-render from react rules

4 participants