oxc-project/oxc · warning · OxcDiagnostic
Unexpected negation of the left operand of '{operator}' oper
Error message
Unexpected negation of the left operand of '{operator}' operator. What it means
Diagnostic from oxlint's no-unsafe-negation rule. Unary ! binds more tightly than the in and instanceof operators (and <, >, <=, >= when enforceForOrderingRelations is on), so !a in b computes (!a) in b, which is almost never intended. The {operator} placeholder names the offended operator; the fix is parentheses.
Source
Thrown at crates/oxc_linter/src/rules/eslint/no_unsafe_negation.rs:17
use oxc_ast::{AstKind, ast::Expression};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use oxc_syntax::operator::UnaryOperator;
use schemars::JsonSchema;
use serde::Deserialize;
use crate::{
AstNode,
context::LintContext,
fixer::RuleFixer,
rule::{DefaultRuleConfig, Rule},
};
fn no_unsafe_negation_diagnostic(operator: &str, span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn(format!(
"Unexpected negation of the left operand of '{operator}' operator."
))
.with_help(format!(
"Use `()` to negate the whole expression, as '!' binds more closely than '{operator}'"
))
.with_label(span)
}
#[derive(Debug, Default, Clone, JsonSchema, Deserialize)]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
pub struct NoUnsafeNegation {
/// The `enforceForOrderingRelations` option determines whether negation is allowed
/// on the left-hand side of ordering relational operators (<, >, <=, >=).
///
/// The purpose is to avoid expressions such as `!a < b` (which is equivalent to `(a ? 0 : 1) < b`)
/// when what is really intended is `!(a < b)`.
enforce_for_ordering_relations: bool,
}View on GitHub (pinned to e1e7af627c)
Solutions
- Wrap the whole comparison in parentheses: !(key in object), !(obj instanceof Ctor).
- For property membership prefer Object.hasOwn(obj, key) or !Object.hasOwn(...).
- Enable "enforceForOrderingRelations": true in the rule config to also cover <, >, <=, >=.
- For TS projects rely on or align with the compiler check instead of duplicating suppressions.
Example fix
// before
if (!key in config) { /* ... */ }
// after
if (!(key in config)) { /* ... */ } Defensive patterns
Strategy: validation
Validate before calling
ast-grep --lang js -p '!$A in $B' -p '!$A instanceof $B'
Prevention
- Code-review any in/instanceof whose left operand starts with '!' and add parentheses.
- Enable "enforceForOrderingRelations": true to also catch !a < b forms.
- Prefer Object.hasOwn or Map.has for membership checks, which avoid the precedence trap entirely.
When it happens
Trigger: if (!key in object) {}; if (!obj instanceof Ctor) {}; with the option enabled also !a < b (equivalent to (a ? 0 : 1) < b). The rule fires on a UnaryExpression ! as the left operand of these binary operators.
Common situations: Writing 'key is not in map' as !key in map; porting membership checks from other languages; TypeScript users may disable the rule since tsc also flags it.
Related errors
- Unexpected operator assignment ({operator}) shorthand.
- Assignment (=) can be replaced with operator assignment ({op
- Checking inequality with NaN will always return true
- Checking equality with NaN will always return false
- Comparison with NaN will always return false
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/0643509b86f6ee64.
Report an issue: GitHub.