{"record":{"id":"500dc6ee70057996","repo":"oxc-project/oxc","slug":"unsafe-arithmetic-operation-on-optional-chaining","errorCode":null,"errorMessage":"Unsafe arithmetic operation on optional chaining","messagePattern":"Unsafe arithmetic operation on optional chaining","errorType":"validation","errorClass":"OxcDiagnostic","httpStatus":null,"severity":"warning","filePath":"crates/oxc_linter/src/rules/eslint/no_unsafe_optional_chaining.rs","lineNumber":26,"sourceCode":"use oxc_span::Span;\nuse oxc_syntax::operator::LogicalOperator;\nuse schemars::JsonSchema;\nuse serde::Deserialize;\n\nuse crate::{\n    AstNode,\n    context::LintContext,\n    rule::{DefaultRuleConfig, Rule},\n};\n\nfn no_unsafe_optional_chaining_diagnostic(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"Unsafe usage of optional chaining\")\n        .with_help(\"If this short-circuits with 'undefined' the evaluation will throw TypeError\")\n        .with_label(span)\n}\n\nfn no_unsafe_arithmetic_diagnostic(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"Unsafe arithmetic operation on optional chaining\")\n        .with_help(\"This can result in NaN.\")\n        .with_label(span)\n}\n\n#[derive(Debug, Default, Clone, JsonSchema, Deserialize)]\n#[serde(rename_all = \"camelCase\", default, deny_unknown_fields)]\npub struct NoUnsafeOptionalChaining {\n    /// Disallow arithmetic operations on optional chaining expressions.\n    /// If this is true, this rule warns arithmetic operations on optional chaining expressions, which possibly result in NaN.\n    disallow_arithmetic_operators: bool,\n}\n\ndeclare_oxc_lint!(\n    /// ### What it does\n    ///\n    /// Disallow use of optional chaining in contexts where the `undefined` value is not allowed.\n    ///\n    /// ### Why is this bad?","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/oxc-project/oxc/blob/e1e7af627c8843ab64044ed466b128fcc21a035b/crates/oxc_linter/src/rules/eslint/no_unsafe_optional_chaining.rs#L8-L44","documentation":"Second diagnostic of no-unsafe-optional-chaining, emitted only when the disallowArithmeticOperators option is enabled. Arithmetic on a possibly-undefined optional chain yields NaN instead of throwing, silently corrupting numeric results (help text: 'This can result in NaN.').","triggerScenarios":"Set \"disallowArithmeticOperators\": true on the rule in .oxlintrc.json, then write cart?.total + shipping, count?.valueOf() * 2, or (opts?.retries) - 1 without a default.","commonSituations":"Teams enabling the stricter option over a codebase already doing arithmetic on optional API data; money/count aggregation over maybe-missing objects.","solutions":["Default the chain before arithmetic: (cart?.total ?? 0) + shipping.","Guard the object first: if (cart) total = cart.total + shipping.","If NaN propagation is acceptable in your domain, leave \"disallowArithmeticOperators\": false (default)."],"exampleFix":"// before\nconst grandTotal = cart?.total + shipping;\n// after\nconst grandTotal = (cart?.total ?? 0) + shipping;","handlingStrategy":"type-guard","validationCode":"const total = (cart?.total ?? 0) + shipping; // default before arithmetic","typeGuard":"const isNum = (v: unknown): v is number => typeof v === 'number' && !Number.isNaN(v);","tryCatchPattern":null,"preventionTips":["Wrap every optional chain in (chain ?? default) before arithmetic.","Enable \"disallowArithmeticOperators\": true when NaN propagation is unacceptable (money, counters, metrics).","Add Number.isNaN assertions in tests for aggregation code over optional data."],"tags":["eslint","oxlint","optional-chaining","nan","arithmetic"],"backgroundTag":"optional-chaining-nan","analyzedSha":"e1e7af627c8843ab64044ed466b128fcc21a035b","analyzedAt":"2026-08-20T07:01:07.079Z","contentChangedAt":"2026-08-20T07:01:07.079Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}