oxc-project/oxc · error · OxcDiagnostic
Unexpected assignment to `exports`.
Error message
Unexpected assignment to `exports`.
What it means
Diagnostic from oxlint rule node/exports-style, fired in BOTH modes by the shared helper is_global_exports_assignment_target: assigning to the `exports` binding itself is always wrong. In CommonJS `exports` is an alias created at module load; rebinding it (exports = { a: 1 }) exports nothing because require() returns module.exports, which still points at the original object. Unlike the two style diagnostics in this rule, this one catches a real bug and deserves prompt fixing.
Source
Thrown at crates/oxc_linter/src/rules/node/exports_style.rs:33
context::LintContext,
rule::{Rule, TupleRuleConfig},
utils::{is_global_exports_assignment_target, is_global_module_exports},
};
fn unexpected_exports_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Unexpected access to `exports`.")
.with_help("Use `module.exports` instead.")
.with_label(span)
}
fn unexpected_module_exports_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Unexpected access to `module.exports`.")
.with_help("Use `exports` instead.")
.with_label(span)
}
fn unexpected_assignment_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Unexpected assignment to `exports`.")
.with_help("Do not modify `exports` itself.")
.with_label(span)
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
enum ExportsStyleMode {
#[default]
#[serde(rename = "module.exports")]
/// Requires `module.exports` and disallows `exports`
ModuleExports,
/// Requires `exports` and disallows `module.exports`
Exports,
}
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
struct ExportsStyleOptions {View on GitHub (pinned to e1e7af627c)
Solutions
- Assign to the real export target: 'module.exports = { a: 1 }'
- If you only meant to add to what is exported, mutate instead: 'exports.a = 1' (or module.exports.a = 1)
- Keep node/no-exports-assign or this rule enabled in CI to catch regressions — the bug is silent at runtime
Example fix
// before
exports = { hello: () => {} }; // require()rs get the ORIGINAL empty object
// after
module.exports = { hello: () => {} }; Defensive patterns
Strategy: validation
Validate before calling
// .oxlintrc.json — the assignment diagnostic runs in both modes; one of these suffices
"rules": { "node/no-exports-assign": "error" }
npx oxlint -c .oxlintrc.json --deny-warning . Prevention
- Internalize the rule of thumb: `exports` is read/mutate-only; the only reassignable export target is module.exports
- Add a smoke test that require()s each entry-point module and asserts its exports are non-empty — catches reassignment bugs that slip past lint
- Keep the dedicated no-exports-assign rule enabled even when exports-style is off
When it happens
Trigger: An assignment whose target resolves to the module-scope global `exports` — exports = { a: 1 };, exports = something || {}; — regardless of whether the rule's mode option is 'module.exports' or 'exports'. Property writes like exports.a = 1 do NOT trigger this diagnostic (those go to the access diagnostics per mode).
Common situations: Developers porting ES-module habits ('export default' feels like assigning the export); scaffolds that write exports = {}; beginners learning CommonJS aliasing; note the dedicated node/no-exports-assign rule reports the same bug with a different message, so enabling both yields two reports on one line.
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/b40ff349553ed97a.
Report an issue: GitHub.