oxc-project/oxc · error · OxcDiagnostic

Unexpected assignment to 'exports'.

Error message

Unexpected assignment to 'exports'.

What it means

Diagnostic from oxlint rule node/no-exports-assign. It disallows assignment to the module-scope global `exports`: in CommonJS, `exports` starts as an alias of module.exports, so rebinding it exports nothing — consumers require() the module and receive the original (empty) object while your assigned value is garbage-collected. The bug is silent at runtime, which is exactly why the rule exists as a dedicated net alongside exports-style's assignment diagnostic.

Source

Thrown at crates/oxc_linter/src/rules/node/no_exports_assign.rs:14

use oxc_ast::{AstKind, ast::Expression};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};

use crate::{
    AstNode,
    context::LintContext,
    rule::Rule,
    utils::{is_global_exports_assignment_target, is_global_module_exports},
};

fn no_exports_assign(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Unexpected assignment to 'exports'.")
        .with_label(span)
        .with_help("Use 'module.exports' instead.")
}

#[derive(Debug, Default, Clone)]
pub struct NoExportsAssign;

declare_oxc_lint!(
    /// ### What it does
    ///
    /// Disallows assignment to `exports`.
    ///
    /// ### Why is this bad?
    ///
    /// Directly using `exports = {}` can lead to confusion and potential bugs
    /// because it reassigns the `exports` object, which may break module
    /// exports. It is more predictable and clearer to use `module.exports`
    /// directly or in conjunction with `exports`.

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Write 'module.exports = value' instead — the one true export target
  2. Add properties when extending: 'exports.key = value'
  3. Keep the rule enabled (it ships in the node preset) so the silent regression is caught in CI rather than production

Example fix

// before
exports = function add(a, b) { return a + b; };

// after
module.exports = function add(a, b) { return a + b; };
Defensive patterns

Strategy: validation

Validate before calling

// .oxlintrc.json
"rules": { "node/no-exports-assign": "error" }

npx oxlint -c .oxlintrc.json --deny-warning .

Prevention

When it happens

Trigger: Any assignment whose target is resolved as the global `exports` binding via the shared util is_global_exports_assignment_target — plain reassignment (exports = obj), compound forms, and reassignment through the alias in conditional initialization (exports = process.env.X ? a : b). Property writes (exports.key = v) are fine and not reported.

Common situations: ESM-to-CJS transpilation or manual porting where 'export default x' becomes 'exports = x'; code from outdated tutorials; the same line also flagged by node/exports-style's assignment diagnostic when both rules are on — dedupe by keeping just this rule for the bug class.

Related errors


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