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
- Write 'module.exports = value' instead — the one true export target
- Add properties when extending: 'exports.key = value'
- 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
- Never assign to `exports` — treat it as a frozen alias of module.exports
- Write a require-smoke test asserting entry points export what consumers expect, since the runtime bug is silent
- When porting ESM 'export default', map it to 'module.exports =' by habit, not 'exports ='
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
- Unexpected assignment to `exports`.
- Unexpected access to `exports`.
- Unexpected access to `module.exports`.
- Unexpected require().
- Do not mix 'require' and other declarations.
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/f61c281e6b74297c.
Report an issue: GitHub.