-
-
Notifications
You must be signed in to change notification settings - Fork 714
fix(lint/useOptionalChain): fix incorrect suggestions for typeof
checks on global objects
#7529
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
…ecks on global objects
🦋 Changeset detectedLatest commit: ca8fc46 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 |
WalkthroughThe 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
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests
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.
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. 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: 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]
butbar
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. Declaringbar
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: declarebar
forfoo[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
: Declarebar
used infoo[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: bindbar
forfoo[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 coveringwindow
and Yodadocument
.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
: Handletypeof (expr)
by omitting parentheses.Small robustness tweak: normalise
typeof
operands with.omit_parentheses()
sotypeof (foo)
is treated the same astypeof foo
.
286-289
: Limit visibility of helper API.
extract_optional_chain_like
looks internal to this rule. Preferpub(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
⛔ 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.
CodSpeed Performance ReportMerging #7529 will improve performances by 6.61%Comparing Summary
Benchmarks breakdown
|
|
||
/// 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) | ||
} |
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.
I moved these methods to avoid creating a dependency from the syntax layer to the semantics layer.
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, | ||
}) | ||
} |
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.
I added a method that checks bindings when typeof is used.
Could you review this PR when you have a moment? |
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.
Looks good. Thank you!
Summary
Closes #7517
The
useOptionalChain
rule no longer suggest changes for typeof checks on global objects.Instead of identifying globals by keywords, assume nodes that can’t be resolved are global objects.
See also:
typescript-eslint
https://typescript-eslint.io/play/#ts=5.9.2&fileType=.ts&code=C4TwDgpg9gZgBAdwJYDsAmUFwIQF5dwDkAruhDKhGoXAGS2KoYIB0ANlAMYCGwSUKANwAoUJFiN0mdl178UOfEVJpylanQbIprDjz4CWYKACdgI7c0UEValFU2TmM-fJEAKS5jgAfH3ABvAF8AShc5ARFhTgEAZ2A4DE5iAFsIFASCYME4AHpcuAAeAFo4WIALbmZUAHNRcGh4JNT0hLwCEjIKew16RK4WjJZmtIyAVQAlAEkRMUb%2B5NHgYYGlyanrZS71Gj6R1pXF1vX2dJrgcpF9jM3bboc91YPr4HWPF99-YLCXt%2BEgA&eslintrc=N4KABGBEBOCuA2BTAzpAXGUEKQAIBcBPABxQGNoBLY-AWhXkoDt8B6Y6RAM0WloHsalfkwCG8WmQAWo5uii9o-aJHBgAviHVA&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eAcgK6qoDCAFutAGsylBm3TgwAXxCSgA&tokens=false
codebase
https://github.com/typescript-eslint/typescript-eslint/blob/77056f77e6fdca54b66ec692e5cefbd9f7a626dd/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts#L158-L165
Test Plan
snap tests
Docs
changeset