+
Skip to content

Conversation

Copy link

changeset-bot bot commented Sep 17, 2025

🦋 Changeset detected

Latest commit: ca8fc46

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-Linter Area: linter A-Parser Area: parser L-JavaScript Language: JavaScript and super languages labels Sep 17, 2025
Copy link
Contributor

coderabbitai bot commented Sep 17, 2025

Walkthrough

The useOptionalChain lint rule now operates with a SemanticModel, changing its Query to Semantic. The rule introduces semantic-aware extraction of optional-chain-like patterns, particularly for typeof checks, to detect unbound references and avoid transforming global guards. A new public function extract_optional_chain_like(binary, model) is added in the rule, while the previous extract_optional_chain_like and helpers are removed from JsBinaryExpression in biome_js_syntax. Multiple new test fixtures cover global typeof guard patterns (including Yoda forms) and valid cases for window/document. Some existing test files now declare const foo = {}. A changeset is added for a patch release referencing #7517.

Possibly related PRs

Suggested labels

A-Linter, L-JavaScript, A-Resolver

Suggested reviewers

  • ematipico
  • arendjr
  • dyc3

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning Most edits (semantic checks, rule refactor, and added tests) are in scope, but the removal of the public method pub fn extract_optional_chain_like in crates/biome_js_syntax/src/expr_ext.rs is an API break that is not described by the PR objectives and could impact downstream consumers. This deletion should be justified, documented as a breaking change, or reverted to avoid unexpected breakage. Either restore the removed public method or explicitly document and justify the breaking change in the PR (update changelog, note migration steps, and run cross-crate compatibility tests); if removal is intentional, mark it as a breaking change in the changeset.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title is concise and directly describes the primary change: preventing useOptionalChain from suggesting fixes for typeof checks on global objects; it names the affected lint rule and the problem, so a reviewer scanning history will understand the main intent.
Linked Issues Check ✅ Passed The patch implements the objective from #7517 by switching the rule to semantic analysis and treating unresolved references as globals, avoiding unsafe fixes for typeof-guarded global access; the code changes and new tests (e.g. preserving typeof window/document checks) directly mitigate the reported ReferenceError risk. The PR does not implement a configurable blacklist as suggested in the issue, but the semantic-resolution approach achieves the primary safety goal.
Description Check ✅ Passed The description directly addresses and closes #7517, explains the behavioural change with an example, links to relevant references, and notes tests and a changeset, so it is clearly related to the changeset and satisfies the lenient requirement.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

🧹 Nitpick comments (11)
.changeset/tidy-teams-mix.md (2)

5-5: Grammar nit: “suggests”, not “suggest”.

Small subject–verb agreement fix.

-Fixed [#7517](https://github.com/biomejs/biome/issues/7517): the [`useOptionalChain`](https://biomejs.dev/linter/rules/use-optional-chain/) rule no longer suggest changes for typeof checks on global objects.
+Fixed [#7517](https://github.com/biomejs/biome/issues/7517): the [`useOptionalChain`](https://biomejs.dev/linter/rules/use-optional-chain/) rule no longer suggests changes for typeof checks on global objects.

7-10: Optional: add a Yoda-style example to mirror tests.

Consider adding "undefined" !== typeof document && document.body; under the code fence for symmetry with the test suite.

crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases1.ts (1)

1-1: Bind the computed key used in element-access examples.

Several cases use foo[bar] but bar isn’t declared. If the rule’s semantic check ever looks past the head object, this could be (mis)classified as involving an unbound reference. Declaring bar keeps the intent crisp.

 const foo = {};
+const bar = "key";
crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases2.ts (1)

1-1: Same note: declare bar for foo[bar] cases.

Prevents any accidental “unbound” inference should analysis broaden later.

 const foo = {};
+const bar = "key";
crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases2.ts (1)

1-1: Declare bar used in foo[bar] element access.

Keeps fixtures semantically tidy while still exercising the rule.

 const foo = {};
+const bar = "key";
crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases1.ts (1)

1-1: Ditto: bind bar for foo[bar] examples.

Minor test hygiene; avoids any future semantic surprises.

 const foo = {};
+const bar = "key";
crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/validCases.ts (1)

49-51: Nice additions covering window and Yoda document.

Consider adding Node/Worker variants to broaden coverage:

  • typeof process !== "undefined" && process.env;
  • typeof self !== "undefined" && self.location;
crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs (4)

376-384: Handle typeof (expr) by omitting parentheses.

Small robustness tweak: normalise typeof operands with .omit_parentheses() so typeof (foo) is treated the same as typeof foo.


286-289: Limit visibility of helper API.

extract_optional_chain_like looks internal to this rule. Prefer pub(crate) to avoid accidental external coupling.

-pub fn extract_optional_chain_like(
+pub(crate) fn extract_optional_chain_like(

353-365: Doc nit: curly quotes.

”undefined” uses typographic quotes. Use ASCII quotes consistently to avoid confusion in examples.


77-86: Version metadata check.

Guidelines suggest updating version when rules change. If Biome’s policy is to keep introduction version stable for bugfixes, ignore; otherwise bump to "next" in this PR and finalise at release.

📜 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 3f06e19 and d4313c1.

⛔ Files ignored due to path filters (9)
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases1.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases2.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases1.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases2.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases1.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases2.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/validCases.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases1.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases2.ts.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (12)
  • .changeset/tidy-teams-mix.md (1 hunks)
  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs (9 hunks)
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases1.ts (1 hunks)
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases2.ts (1 hunks)
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases1.ts (1 hunks)
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases2.ts (1 hunks)
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases1.ts (1 hunks)
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases2.ts (1 hunks)
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/validCases.ts (1 hunks)
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases1.ts (1 hunks)
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases2.ts (1 hunks)
  • crates/biome_js_syntax/src/expr_ext.rs (0 hunks)
💤 Files with no reviewable changes (1)
  • crates/biome_js_syntax/src/expr_ext.rs
🧰 Additional context used
📓 Path-based instructions (5)
.changeset/*.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

.changeset/*.md: In changeset files, only use #### or ##### headers; avoid other header levels
Changeset descriptions should use past tense for what you did (e.g., "Added...")
Describe current Biome behavior in present tense within changesets (e.g., "Biome now supports...")
For bug fixes in changesets, start with a link to the issue (e.g., "Fixed #1234: ...")
When referencing rules or assists in changesets, include links to their documentation pages
Include a minimal code block in the changeset when applicable to demonstrate the change
End every sentence in the changeset description with a period

Files:

  • .changeset/tidy-teams-mix.md
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases1.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/validCases.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases1.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases1.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases1.ts
  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
crates/biome_*/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases1.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/validCases.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases1.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases1.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases1.ts
  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
**/tests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place test files under a tests/ directory in each crate

Files:

  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases1.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/validCases.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases1.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases2.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases1.ts
  • crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases1.ts
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Format all Rust source files before committing (just f)

Files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/{lint,assist}/**/*.rs : When banning globals (e.g., `noConsoleLog`), check the semantic model to avoid false positives from locally shadowed bindings
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/{lint,assist}/**/*.rs : When banning globals (e.g., `noConsoleLog`), check the semantic model to avoid false positives from locally shadowed bindings

Applied to files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/**/src/{lint,assist}/**/*.rs : Avoid deep indentation and panics; prefer `?`, `ok()?`, and combinators (`map`, `and_then`, `filter`) over nested `if let`/`unwrap`/`expect`

Applied to files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/**/src/{lint,assist}/**/*.rs : If a rule returns a code action (implements `action`), add `fix_kind` in `declare_lint_rule!` and use `ctx.metadata().applicability()` when building the action

Applied to files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/**/src/{lint,assist}/**/*.rs : Code blocks in rule docs must specify language; invalid snippets require `expect_diagnostic`; use `options`/`full_options`/`use_options` markers as appropriate

Applied to files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/crates/biome_rule_options/lib/**/*.rs : Define per-rule options types in `biome_rule_options` crate (one file per rule, e.g., `lib/use_my_rule.rs`)

Applied to files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/**/src/{lint,assist}/**/*.rs : Recommended rules with domains are enabled only when users enable the matching domains; use `domains` metadata judiciously

Applied to files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/**/src/{lint,assist}/**/*.rs : Follow rule naming conventions: use `no<Concept>` to forbid and `use<Concept>` to mandate; prefer consistent prefixes (e.g., `noDuplicate<Concept>`, `useConsistent<Concept>`)

Applied to files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/**/src/{lint,assist}/**/*.rs : In declare_lint_rule! macros, set `version: "next"` for new or updated rules

Applied to files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/**/src/{lint,assist}/**/*.rs : Set the `language` field in `declare_lint_rule!` to the primary language (`js`, `jsx`, `ts`, or `tsx`) the rule targets

Applied to files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/tests/quick_test.rs : Quick test: edit `tests/quick_test.rs`, remove or comment `#[ignore]`, set `SOURCE`, and adjust `RuleFilter`

Applied to files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/**/src/{lint,assist}/**/*.rs : When deprecating a rule, add `deprecated: "<reason>"` to `declare_lint_rule!`

Applied to files:

  • crates/biome_js_analyze/src/lint/complexity/use_optional_chain.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). (25)
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Documentation
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Check Dependencies
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Parser conformance
  • GitHub Check: autofix
🔇 Additional comments (4)
crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases2.ts (1)

1-54: Good breadth across operators and literal forms.

Covers !=/strictness and quotes/backticks well. No further nits.

crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases1.ts (1)

1-54: LGTM on global/Yoda coverage.

Solid matrix of member, element access and calls with Yoda guards.

crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_typeofLogicalAndCases1.ts (1)

1-54: Good coverage for real‑world guard patterns.

Nice spread of chained members, calls, element access, quotes, and “jump” cases. Consider adding:

  • A parenthesised typeof operand case to harden parsing: typeof (foo) !== 'undefined' && foo.bar;
  • A local binding counter‑example to prove fixes still apply when the identifier is bound:
    const foo = {};
    typeof foo !== 'undefined' && foo.bar;
    
crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/global_yoda_expressions_typeofLogicalAndCases2.ts (1)

1-54: Yoda cases look solid; add a parenthesised operand.

Please add 'undefined' !== typeof (foo) && foo.bar; to mirror the non‑Yoda suite and exercise the same edge.

Copy link

codspeed-hq bot commented Sep 17, 2025

CodSpeed Performance Report

Merging #7529 will improve performances by 6.61%

Comparing qraqras:dev/7517 (ca8fc46) with main (3f06e19)

Summary

⚡ 1 improvement
✅ 132 untouched

Benchmarks breakdown

Benchmark BASE HEAD Change
json_analyzer[big5-added_15586211152145260264.json] 463.5 µs 434.7 µs +6.61%

@qraqras qraqras marked this pull request as draft September 18, 2025 00:11
Comment on lines -322 to -434

/// Extract the left or right operand of an optional chain-like expression.
/// ```js
/// foo !== undefined;
/// typeof foo !== 'undefined';
///```
pub fn extract_optional_chain_like(&self) -> SyntaxResult<Option<AnyJsExpression>> {
if matches!(
self.operator(),
Ok(JsBinaryOperator::StrictInequality | JsBinaryOperator::Inequality)
) {
let left = self.left()?;
let right = self.right()?;
// nullish check: `foo !== undefined` -> return foo
if let Some(expr) = Self::extract_optional_chain_like_nullish(&left, &right)? {
return Ok(Some(expr));
}
// typeof check: `typeof foo !== 'undefined'` -> return foo
if let Some(expr) = Self::extract_optional_chain_like_typeof(&left, &right)? {
return Ok(Some(expr));
}
Ok(None)
} else {
Ok(None)
}
}

/// Extract the left or right operand of an optional chain-like expression comparing nullish.
/// ```js
/// foo !== undefined; // -> Some(foo)
/// foo != undefined; // -> Some(foo)
/// foo !== null; // -> Some(foo)
/// foo != null; // -> Some(foo)
/// undefined !== foo; // -> Some(foo)
/// undefined != foo; // -> Some(foo)
/// null !== foo; // -> Some(foo)
/// null != foo; // -> Some(foo)
/// foo !== bar; // -> None
/// foo != bar; // -> None
/// undefined !== null; // -> None
/// undefined != null; // -> None
/// null !== undefined; // -> None
/// null != undefined; // -> None
/// undefined !== undefined; // -> None
/// undefined != undefined; // -> None
/// null !== null; // -> None
/// null != null; // -> None
///```
fn extract_optional_chain_like_nullish(
left: &AnyJsExpression,
right: &AnyJsExpression,
) -> SyntaxResult<Option<AnyJsExpression>> {
fn is_nullish(expression: &AnyJsExpression) -> bool {
expression
.as_static_value()
.is_some_and(|x| x.is_null_or_undefined())
}
let left_is_nullish = is_nullish(left);
let right_is_nullish = is_nullish(right);
// right only nullish: `foo !== undefined` -> return foo (left)
if !left_is_nullish && right_is_nullish {
return Ok(Some(left.clone()));
}
// left only nullish: `undefined !== foo` -> return foo (right)
if left_is_nullish && !right_is_nullish {
return Ok(Some(right.clone()));
}
Ok(None)
}

/// Extract the left or right operand of an optional chain-like expression using `typeof`.
/// ```js
/// typeof foo !== 'undefined'; // -> Some(foo)
/// typeof foo != 'undefined'; // -> Some(foo)
/// 'undefined' !== typeof foo; // -> Some(foo)
/// 'undefined' != typeof foo; // -> Some(foo)
/// ”undefined” != typeof foo; // -> Some(foo)
/// `undefined` != typeof foo; // -> Some(foo)
/// typeof foo !== undefined; // -> None
/// typeof foo != undefined; // -> None
/// undefined !== typeof foo; // -> None
/// undefined != typeof foo; // -> None
///```
fn extract_optional_chain_like_typeof(
left: &AnyJsExpression,
right: &AnyJsExpression,
) -> SyntaxResult<Option<AnyJsExpression>> {
fn is_string_literal_undefined(expression: &AnyJsExpression) -> bool {
expression
.as_static_value()
.is_some_and(|x| matches!(x.as_string_constant(), Some(s) if s == "undefined"))
}
fn typeof_argument(expression: &AnyJsExpression) -> SyntaxResult<Option<AnyJsExpression>> {
if let Some(unary) = expression.as_js_unary_expression() {
return Ok(match unary.operator()? {
JsUnaryOperator::Typeof => Some(unary.argument()?),
_ => None,
});
}
Ok(None)
}
let left_is_string_undefined = is_string_literal_undefined(left);
let right_is_string_undefined = is_string_literal_undefined(right);
// `typeof foo !== "undefined"` -> return foo
if !left_is_string_undefined && right_is_string_undefined {
return typeof_argument(left);
}
// `"undefined" !== typeof foo` -> return foo
if left_is_string_undefined && !right_is_string_undefined {
return typeof_argument(right);
}
Ok(None)
}
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 moved these methods to avoid creating a dependency from the syntax layer to the semantics layer.

Comment on lines +385 to +402
fn is_unbound_root(model: &SemanticModel, expr: &AnyJsExpression) -> SyntaxResult<bool> {
let mut current = expr.clone().omit_parentheses();
loop {
current = match current {
AnyJsExpression::JsStaticMemberExpression(e) => e.object()?,
AnyJsExpression::JsComputedMemberExpression(e) => e.object()?,
AnyJsExpression::JsCallExpression(e) => e.callee()?,
AnyJsExpression::JsParenthesizedExpression(e) => e.expression()?,
_ => {
break;
}
}
}
Ok(match current.as_js_reference_identifier() {
Some(ident) => ident.binding(model).is_none(),
None => false,
})
}
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 added a method that checks bindings when typeof is used.

@qraqras qraqras marked this pull request as ready for review September 18, 2025 15:33
@qraqras
Copy link
Contributor Author

qraqras commented Sep 18, 2025

Could you review this PR when you have a moment?

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.

Looks good. Thank you!

@dyc3 dyc3 merged commit fea905f into biomejs:main Sep 28, 2025
30 checks passed
@github-actions github-actions bot mentioned this pull request Sep 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter A-Parser Area: parser L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

💅 useOptionalChain causes crashes when applied to global variables

2 participants

点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载