oxc-project/oxc · warning · OxcDiagnostic

Confusing combination of non-null assertion and `{op_str}` o

Error message

Confusing combination of non-null assertion and `{op_str}` operator like `a! {op_str} b`, which might be misinterpreted as `!(a {op_str} b)`.

What it means

Operator variant of typescript/no-confusing-non-null-assertion: the left operand of `in` or `instanceof` is non-null asserted, as in `a! in obj` or `a! instanceof C`. The text can be misread as the negation `!(a in obj)`, and the precedence of `!` here is assertion, not logical not, which is the trap.

Source

Thrown at crates/oxc_linter/src/rules/typescript/no_confusing_non_null_assertion.rs:84

    OxcDiagnostic::warn(format!(
        r"Confusing combinations of non-null assertion and equal test like `a! {op_str} b`, which looks very similar to not equal `a !{op_str} b`."
    ))
    .with_help(
        r"Wrap left-hand side in parentheses to avoid putting non-null assertion `!` and `=` together.",
    )
    .with_label(span)
}

fn confusing_non_null_assignment_assertion_diagnostic(op_str: &str, span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn(format!(
        r"Confusing combinations of non-null assertion and assignment like `a! {op_str} b`, which looks very similar to not equal `a !{op_str} b`."
    ))
    .with_help(r"Remove the `!`, or wrap the left-hand side in parentheses.")
    .with_label(span)
}

fn confusing_non_null_operator_diagnostic(op_str: &str, span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn(format!(
        "Confusing combination of non-null assertion and `{op_str}` operator like `a! {op_str} b`, which might be misinterpreted as `!(a {op_str} b)`."
    ))
    .with_help("Remove the `!`, or wrap the left-hand side in parentheses.")
    .with_label(span)
}

fn get_depth_ends_in_bang(expr: &Expression<'_>) -> Option<u32> {
    match expr {
        Expression::TSNonNullExpression(_) => Some(0),
        Expression::ChainExpression(chain_expr) => {
            matches!(&chain_expr.expression, ChainElement::TSNonNullExpression(_)).then_some(0)
        }
        Expression::BinaryExpression(binary_expr) => {
            get_depth_ends_in_bang(&binary_expr.right).map(|x| x + 1)
        }
        Expression::UnaryExpression(unary_expr) => {
            get_depth_ends_in_bang(&unary_expr.argument).map(|x| x + 1)
        }

View on GitHub (pinned to 36ec0ef2ba)

Solutions

  1. Remove the `!` if the operand cannot be null/undefined: `key in cache`
  2. If you meant logical negation, write it explicitly: `!(key in cache)`
  3. If the assertion is intentional, wrap the operand: `(key!) in cache`

Example fix

// before
if (key! in cache) {}

// after
if (key in cache) {}
// or, if negation was intended:
if (!(key in cache)) {}
Defensive patterns

Strategy: type-guard

Validate before calling

// .oxlintrc.json
{
  "rules": { "typescript/no-confusing-non-null-assertion": "warn" }
}

Type guard

function isNonEmptyString(v: string | null | undefined): v is string {
  return typeof v === 'string' && v.length > 0;
}

// instead of `key! in cache`, narrow or drop the assertion
if (isNonEmptyString(key) && key in cache) { /* ... */ }
// negation must be written explicitly:
if (!(key in cache)) { /* ... */ }

Prevention

When it happens

Trigger: Any BinaryExpression with operator `in` or `instanceof` whose left side ends in `!`, e.g. `key! in cache`, `err! instanceof TypeError`; the run() special-cases these two operators separately from the equality variants.

Common situations: Type-guard-style code checking keys or error types after an assertion; developers intending `!(a in obj)` but writing `a! in obj`; ported JS code that used `!` as logical not before these operators.

Related errors


AI-assisted analysis of oxc-project/oxc@36ec0ef2ba (2026-08-20). Data as JSON: /api/errors/252677f4fc7afe7e. Report an issue: GitHub.