oxc-project/oxc · warning · OxcDiagnostic

Exporting mutable '{kind_str}' binding, use 'const' instead.

Error message

Exporting mutable '{kind_str}' binding, use 'const' instead.

What it means

Diagnostic from the oxlint rule import/no-mutable-exports (style category). It fires when a variable declaration with kind `let` or `var` is exported (`export let x` / `export var x`). Mutable export bindings are confusing: consumers see live bindings whose value can change at any time, and bundlers/CJS interop handle mutation inconsistently, so the rule demands immutable `const` exports. The message names the exact kind (var vs let) and the help repeats it in the fix suggestion.

Source

Thrown at crates/oxc_linter/src/rules/import/no_mutable_exports.rs:16

use oxc_ast::{
    AstKind,
    ast::{
        Declaration, Expression, ModuleExportName, VariableDeclaration, VariableDeclarationKind,
    },
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::ReferenceId;
use oxc_span::Span;

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

fn no_mutable_exports_diagnostic(span: Span, kind: VariableDeclarationKind) -> OxcDiagnostic {
    let kind_str = if kind == VariableDeclarationKind::Var { "var" } else { "let" };
    OxcDiagnostic::warn(format!("Exporting mutable '{kind_str}' binding, use 'const' instead."))
        .with_help(format!("Replace '{kind_str}' with 'const' to export an immutable binding."))
        .with_label(span)
}

// <https://github.com/import-js/eslint-plugin-import/blob/v2.31.0/docs/rules/no-mutable-exports.md>
#[derive(Debug, Default, Clone)]
pub struct NoMutableExports;

declare_oxc_lint!(
    /// ### What it does
    ///
    /// Forbids the use of mutable exports with var or let.
    ///
    /// ### Why is this bad?
    ///
    /// In general, we should always export constants
    ///
    /// ### Examples

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Change the declaration to `const` when the binding is never reassigned (mutating object properties is still fine)
  2. If mutation is the point, export a getter or functions instead: `export const getState = () => state` or export an object with methods
  3. Export an immutable snapshot and keep mutation internal to the module

Example fix

// before
export let config = { debug: false };

// after
export const config = { debug: false }; // mutate properties, never the binding
Defensive patterns

Strategy: validation

Validate before calling

// rg -n "^export\\s+(let|var)\\s" src
// .oxlintrc.json: { "rules": { "import/no-mutable-exports": "warn" } }

Prevention

When it happens

Trigger: `export let counter = 0;` or `export var config = {};` — exported VariableDeclarations whose kind is not Const. Function and class exports are not flagged. Emitted from no_mutable_exports_diagnostic at crates/oxc_linter/src/rules/import/no_mutable_exports.rs:16, which maps Var → "var" and everything else (let) → "let".

Common situations: State exported from modules (counters, caches, feature flags) that authors expected to mutate; converting CJS `module.exports.x = let-variable` to ESM; interop with bundlers that snapshot export values, causing the mutation to be invisible downstream.

Related errors


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