oxc-project/oxc · warning · OxcDiagnostic

Unsafe usage of optional chaining

Error message

Unsafe usage of optional chaining

What it means

Primary diagnostic of oxlint's no-unsafe-optional-chaining rule. An optional chain short-circuits to undefined, and when the chain result flows into a context that requires a non-undefined value (new, calls, tagged templates, destructuring, for-of RHS, with, instanceof, in, spread), the program throws TypeError at runtime.

Source

Thrown at crates/oxc_linter/src/rules/eslint/no_unsafe_optional_chaining.rs:20

    AstKind,
    ast::{AssignmentTarget, match_assignment_target_pattern},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::NodeId;
use oxc_span::Span;
use oxc_syntax::operator::LogicalOperator;
use schemars::JsonSchema;
use serde::Deserialize;

use crate::{
    AstNode,
    context::LintContext,
    rule::{DefaultRuleConfig, Rule},
};

fn no_unsafe_optional_chaining_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Unsafe usage of optional chaining")
        .with_help("If this short-circuits with 'undefined' the evaluation will throw TypeError")
        .with_label(span)
}

fn no_unsafe_arithmetic_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Unsafe arithmetic operation on optional chaining")
        .with_help("This can result in NaN.")
        .with_label(span)
}

#[derive(Debug, Default, Clone, JsonSchema, Deserialize)]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
pub struct NoUnsafeOptionalChaining {
    /// Disallow arithmetic operations on optional chaining expressions.
    /// If this is true, this rule warns arithmetic operations on optional chaining expressions, which possibly result in NaN.
    disallow_arithmetic_operators: bool,
}

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Pull the chain into a local and check it before the value-required position.
  2. Replace ?. with plain access where the value is required, so the failure is the intended one.
  3. Provide a default: (obj?.handler ?? defaultHandler)(event).
  4. Guard the chain: if (obj?.handler) { obj.handler(event); }.

Example fix

// before
const server = new (config?.Server)(port);
// after
const Server = config?.Server;
if (Server === undefined) throw new TypeError('config.Server is required');
const server = new Server(port);
Defensive patterns

Strategy: type-guard

Validate before calling

function requireDefined<T>(v: T | undefined, name: string): T {
  if (v === undefined) throw new TypeError(`${name} is required`);
  return v;
}
// const Server = requireDefined(config?.Server, 'config.Server'); new Server(port);

Type guard

const hasHandler = (o: unknown): o is { handler: (e: Event) => void } =>
  typeof o === 'object' && o !== null && typeof (o as { handler?: unknown }).handler === 'function';

Prevention

When it happens

Trigger: new (config?.Server)(port); (obj?.handler)(event); 1 in obj?.foo; with (obj?.foo); for (bar of obj?.foo); const { bar } = obj?.foo; bar instanceof obj?.foo.

Common situations: Migrating to optional chaining while assuming the value exists; config-driven dynamic constructors; invoking optional event handlers; destructuring API responses that may be missing.

Related errors


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/8bc330df1a6f8d9d. Report an issue: GitHub.