oxc-project/oxc · critical
Unexpected re-assignment of `const` variable {name}.
Error message
Unexpected re-assignment of `const` variable {name}. What it means
Diagnostic from the oxlint rule `no-const-assign` (crates/oxc_linter/src/rules/eslint/no_const_assign.rs). It fires when a variable declared with `const` is later the target of an assignment (including compound assignment, increment/decrement, and for-in/for-of assignment targets). The diagnostic is two-label: `'{name} is declared here as const.'` plus `'{name} is re-assigned here.'`. At runtime this throws `TypeError: Assignment to constant variable`, so the rule is correctness-critical.
Source
Thrown at crates/oxc_linter/src/rules/eslint/no_const_assign.rs:11
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_ecmascript::BoundNames;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::{AstNode, SymbolId};
use oxc_span::Span;
use crate::{ast_util::variable_declaration_kind, context::LintContext, rule::Rule};
fn no_const_assign_diagnostic(name: &str, decl_span: Span, assign_span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn(format!("Unexpected re-assignment of `const` variable {name}."))
.with_help("Use `let` instead of `const` if you need to reassign this variable.")
.with_labels([
decl_span.label(format!("{name} is declared here as `const`.")),
assign_span.label(format!("{name} is re-assigned here.")),
])
}
#[derive(Debug, Default, Clone)]
pub struct NoConstAssign;
declare_oxc_lint!(
/// ### What it does
///
/// Disallow reassigning `const` variables.
///
/// ### Why is this bad?
///
/// We cannot modify variables that are declared using the `const` keyword,View on GitHub (pinned to e1e7af627c)
Solutions
- Change the declaration from `const` to `let` if reassignment is intended: `let x = 1; x = 2;`.
- If immutability is intended, change the reassignment into a mutation (for objects/arrays: `items.push(...)` instead of `items = [...]`) or use a new binding.
- For accumulators, restructure with `reduce` or a mutable local so the const stays const.
- Never widen to `var` — that hides rather than fixes the mutation.
Example fix
// before
const total = 0;
for (const n of nums) {
total += n; // TypeError: Assignment to constant variable
}
// after
let total = 0;
for (const n of nums) {
total += n;
} Defensive patterns
Strategy: validation
Validate before calling
// Prefer the AST-based rule; textual heuristic for hooks:
const decls = [...src.matchAll(/\bconst\s+(\w+)/g)].map(m => m[1]);
const reassigned = decls.filter(n => new RegExp(`\\b${n}\\s*(=[^=]|\\+\+|--|\\*=|\\+=)`).test(src)); Try / catch
// Wrap risky third-party-driven mutation if you cannot lint it:
try { moduleHotReload(); } catch (e) {
if (e instanceof TypeError && /Assignment to constant variable/.test(e.message)) {
reportConstReassignment(e);
} else throw e;
} Prevention
- Let prefer-const and no-const-assign run together in CI so the pair stays consistent.
- When converting let to const, search for all writes to the binding first.
- Prefer mutating container contents (push/spread) over rebinding.
When it happens
Trigger: An AssignmentExpression / UpdateExpression / ForIn-Of target Reference whose SymbolId resolves to a declaration with `const` kind (checked via `variable_declaration_kind` over bound names) — e.g. `const x = 1; x = 2;`, `x += 1;`, `x++;`, `for (x of ys)` when x is const.
Common situations: Refactoring `let` to `const` for 'prefer-const' and missing a later mutation; adding retry/reset logic (`count = 0`) to a const accumulator; destructured `const {a} = obj` later reassigned; this always hard-crashes in strict-mode ESM, so the lint report precedes a real runtime TypeError.
Related errors
- Expected a conditional expression and instead saw an assignm
- '{name}' is a function.
- Unexpected assignment to read-only global variable.
- Do not assign to imported bindings
- Do not use `new` on `Promise.{static_name}`
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/9313d0e4408cdb37.
Report an issue: GitHub.