-
-
Notifications
You must be signed in to change notification settings - Fork 730
fix(useOptionalChain): support optional chaining using typeof
#7415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🦋 Changeset detectedLatest commit: 0ba5afc The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
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 |
WalkthroughRefactors optional-chain detection in crates/biome_js_syntax/src/expr_ext.rs by splitting JsBinaryExpression::extract_optional_chain_like into two private helpers (nullish and typeof-based extraction). Adds support for typeof-based guards in both normal and Yoda order (e.g., typeof foo !== "undefined" and "undefined" !== typeof foo). Adds numerous tests under crates/biome_js_analyze/tests/specs/complexity/useOptionalChain covering typeof guards (normal/Yoda, single/double/template quotes, member/element access, calls). Adds changeset documenting fix for issue #7212. No public API signatures changed. Assessment against linked issues
Possibly related PRs
Suggested reviewers
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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). (9)
✨ Finishing Touches🧪 Generate unit tests
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. Comment |
There was a problem hiding this 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
🧹 Nitpick comments (10)
.changeset/lazy-animals-burn.md (1)
5-6: Polish the changeset: trailing period, locale‑neutral docs link, and add a tiny example.Keeps release notes crisp and consistent with the repo’s conventions.
-Fixed [#7212](https://github.com/biomejs/biome/issues/7212), now the [`useOptionalChain`](https://biomejs.dev/ja/linter/rules/use-optional-chain/) rule recognizes optional chaining using `typeof` (e.g., `typeof foo !== 'undefined' && foo.bar`). +Fixed [#7212](https://github.com/biomejs/biome/issues/7212), now the [`useOptionalChain`](https://biomejs.dev/linter/rules/use-optional-chain/) rule recognizes optional chaining using `typeof` (e.g., `typeof foo !== 'undefined' && foo.bar`). + +#### Example + +```ts +// Before +typeof foo !== "undefined" && foo.bar; +// After +foo?.bar; +```crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_validCases.ts (1)
46-48: Add parenthesised Yoda variants to harden parsing/normalisation.Catches spacing/precedence quirks while remaining “valid” (always true) guards.
undefined !== typeof foo && foo.bar; undefined != typeof foo && foo.bar; + (undefined !== typeof foo) && foo.bar; + (undefined != typeof foo) && foo.bar;crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/validCases.ts (1)
46-48: Mirror with parenthesised typeof variants (also always true).Prevents accidental regressions in token handling.
typeof foo !== undefined && foo.bar; typeof foo != undefined && foo.bar; + (typeof foo !== undefined) && foo.bar; + (typeof foo != undefined) && foo.bar;crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases1.ts (1)
1-8: Add a couple of parenthesised forms for lhs evaluation variance.These help ensure we don’t overfit to a specific token layout.
typeof foo !== 'undefined' && foo.bar; typeof foo.bar !== 'undefined' && foo.bar.baz; + (typeof foo !== 'undefined') && foo.bar; + typeof (foo) !== 'undefined' && foo();crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases2.ts (1)
1-8: Non‑strict variants: add parenthesised forms too.Keeps parity with the strict set and exercises parser edges.
typeof foo != 'undefined' && foo.bar; typeof foo.bar != 'undefined' && foo.bar.baz; + (typeof foo != 'undefined') && foo.bar; + typeof (foo) != 'undefined' && foo();crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases2.ts (1)
1-54: Great Yoda-style coverage; add a few parenthesised operands.To guard against regressions when users wrap the typeof operand, add variants with parentheses.
+// parenthesised typeof operands +'undefined' != typeof (foo) && foo.bar; +'undefined' != typeof (foo.bar) && foo.bar.baz; +'undefined' != typeof (foo[bar]) && foo[bar].baz;crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases1.ts (1)
1-54: Solid set; mirror strict-inequality cases with parentheses.Parity with the loose-inequality fixture helps. Add parenthesised forms.
+// parenthesised typeof operands +'undefined' !== typeof (foo) && foo.bar; +'undefined' !== typeof (foo.bar) && foo.bar.baz; +'undefined' !== typeof (foo[bar]) && foo[bar].baz;crates/biome_js_syntax/src/expr_ext.rs (3)
349-391: Minor simplification opportunity in nullish extractor.The two branches can be expressed as an XOR, returning the non-nullish side. Purely cosmetic.
- 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())); - } + let left_is_nullish = is_nullish(left); + let right_is_nullish = is_nullish(right); + if left_is_nullish ^ right_is_nullish { + return Ok(Some(if left_is_nullish { right.clone() } else { left.clone() })); + }
392-404: Docs: replace smart quotes with ASCII quotes.The example line uses a curly quote, which isn’t valid JS and may confuse readers.
- /// ”undefined” != typeof foo; // -> Some(foo) + /// "undefined" != typeof foo; // -> Some(foo)
414-422: Normalisetypeofargument by stripping parentheses.Returning the unparenthesised argument avoids spurious mismatches downstream.
- fn typeof_argument(expression: &AnyJsExpression) -> SyntaxResult<Option<AnyJsExpression>> { + 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()?), + JsUnaryOperator::Typeof => { + let arg = unary.argument()?; + Some(arg.omit_parentheses()) + } _ => None, }); } Ok(None) }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases1.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases2.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/validCases.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases1.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases2.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_validCases.ts.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (8)
.changeset/lazy-animals-burn.md(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_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_validCases.ts(1 hunks)crates/biome_js_syntax/src/expr_ext.rs(2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
.changeset/*.md
📄 CodeRabbit inference engine (CONTRIBUTING.md)
.changeset/*.md: Create changesets withjust new-changeset; store them in.changeset/with correct frontmatter (package keys and change type).
In changeset descriptions, follow content conventions: user-facing changes only; past tense for what you did; present tense for current behavior; link issues for fixes; link rules/assists; include representative code blocks; end every sentence with a period.
When adding headers in a changeset, only use #### or ##### levels.
Files:
.changeset/lazy-animals-burn.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/typeofLogicalAndCases1.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases2.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_validCases.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/validCases.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases2.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases1.tscrates/biome_js_syntax/src/expr_ext.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases1.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases2.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_validCases.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/validCases.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases2.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases1.tscrates/biome_js_syntax/src/expr_ext.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/typeofLogicalAndCases1.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases2.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_validCases.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/validCases.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases2.tscrates/biome_js_analyze/tests/specs/complexity/useOptionalChain/yoda_expressions_typeofLogicalAndCases1.ts
**/*.{rs,toml}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format Rust and TOML files before committing (use
just f/just format).
Files:
crates/biome_js_syntax/src/expr_ext.rs
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:50:12.090Z
Learning: Applies to crates/biome_js_type_info/src/**/*.rs : Use TypeData::Unknown for unimplemented inference and TypeData::UnknownKeyword for the explicit TypeScript unknown keyword; treat them semantically the same but keep them distinct for measurement
⏰ 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: Test Node.js API
- GitHub Check: autofix
- GitHub Check: End-to-end tests
- GitHub Check: Documentation
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Check Dependencies
- 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: Parser conformance
🔇 Additional comments (2)
crates/biome_js_analyze/tests/specs/complexity/useOptionalChain/typeofLogicalAndCases1.ts (1)
1-8: Nothing to see—Yoda already quotes “undefined” in yoda_expressions_typeofLogicalAndCases1.ts and 2.ts.crates/biome_js_syntax/src/expr_ext.rs (1)
323-347: Refactor reads clean and keeps behaviour scoped to !=/!==.Delegating to specialised helpers is tidy and easy to extend. No functional concerns here.
Summary
Closes #7212
Now the
useOptionalChainrule recognizes optional chaining usingtypeof(e.g.,typeof foo !== 'undefined' && foo.bar).Test Plan
Added snap tests.
Docs
changeset only