{"record":{"id":"f61c281e6b74297c","repo":"oxc-project/oxc","slug":"unexpected-assignment-to-exports-f61c28","errorCode":null,"errorMessage":"Unexpected assignment to 'exports'.","messagePattern":"Unexpected assignment to 'exports'\\.","errorType":"validation","errorClass":"OxcDiagnostic","httpStatus":null,"severity":"error","filePath":"crates/oxc_linter/src/rules/node/no_exports_assign.rs","lineNumber":14,"sourceCode":"use oxc_ast::{AstKind, ast::Expression};\nuse oxc_diagnostics::OxcDiagnostic;\nuse oxc_macros::declare_oxc_lint;\nuse oxc_span::{GetSpan, Span};\n\nuse crate::{\n    AstNode,\n    context::LintContext,\n    rule::Rule,\n    utils::{is_global_exports_assignment_target, is_global_module_exports},\n};\n\nfn no_exports_assign(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"Unexpected assignment to 'exports'.\")\n        .with_label(span)\n        .with_help(\"Use 'module.exports' instead.\")\n}\n\n#[derive(Debug, Default, Clone)]\npub struct NoExportsAssign;\n\ndeclare_oxc_lint!(\n    /// ### What it does\n    ///\n    /// Disallows assignment to `exports`.\n    ///\n    /// ### Why is this bad?\n    ///\n    /// Directly using `exports = {}` can lead to confusion and potential bugs\n    /// because it reassigns the `exports` object, which may break module\n    /// exports. It is more predictable and clearer to use `module.exports`\n    /// directly or in conjunction with `exports`.","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/oxc-project/oxc/blob/e1e7af627c8843ab64044ed466b128fcc21a035b/crates/oxc_linter/src/rules/node/no_exports_assign.rs#L1-L32","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nexports = function add(a, b) { return a + b; };\n\n// after\nmodule.exports = function add(a, b) { return a + b; };","handlingStrategy":"validation","validationCode":"// .oxlintrc.json\n\"rules\": { \"node/no-exports-assign\": \"error\" }\n\nnpx oxlint -c .oxlintrc.json --deny-warning .","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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 ='"],"tags":["node","commonjs","exports","bug","oxlint"],"backgroundTag":"exports-reassignment","analyzedSha":"e1e7af627c8843ab64044ed466b128fcc21a035b","analyzedAt":"2026-08-20T07:01:07.079Z","contentChangedAt":"2026-08-20T07:01:07.079Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}