oxc-project/oxc · info · OxcDiagnostic
Unexpected access to `module.exports`.
Error message
Unexpected access to `module.exports`.
What it means
Diagnostic from oxlint rule node/exports-style (style category), the mirror of its sibling: when the rule is configured in 'exports' mode, the codebase standardizes on the short `exports` alias, and any reference to `module.exports` is flagged with the suggestion to use `exports`. Same rationale — one idiom per codebase — and same caveat: only safe because the other diagnostic in this rule forbids reassigning `exports` itself.
Source
Thrown at crates/oxc_linter/src/rules/node/exports_style.rs:27
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
ast_util::iter_outer_expressions,
config::GlobalValue,
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`View on GitHub (pinned to e1e7af627c)
Solutions
- Rewrite the access: 'module.exports.foo = 42' becomes 'exports.foo = 42'
- If the file must use module.exports (e.g. it reassigns the export target), keep it and scope the rule to mode "module.exports" for that directory, or disable inline
- Normalize the whole module to one form so both diagnostics stay silent
Example fix
// before (mode: exports)
module.exports.hello = function () {};
// after
exports.hello = function () {}; Defensive patterns
Strategy: validation
Validate before calling
// .oxlintrc.json
"rules": { "node/exports-style": ["error", "exports"] }
npx oxlint -c .oxlintrc.json --deny-warning . Prevention
- When standardizing on the exports alias, always keep node/no-exports-assign on — the short form is only safe while `exports` itself is never rebound
- Encode the chosen idiom in the shared config, not per-file pragmas, so drift is visible in review
- Codemod existing module.exports.x to exports.x in one commit before enabling
When it happens
Trigger: Config "node/exports-style": ["error", "exports"] (the ExportsStyleMode::Exports serde rename) and the source references the global `module.exports` — e.g. module.exports.foo = 42; — resolved through the same is_global_module_exports helper used for the sibling diagnostics.
Common situations: Codebases that adopted the shorter exports style (common in older Node libs) now linted with oxlint; mode flipped in shared config while some files still use the long form; migrations from eslint-plugin-n/exports-style with the same option value.
Related errors
- Unexpected access to `exports`.
- Unexpected assignment to `exports`.
- Unexpected require().
- Unexpected assignment to 'exports'.
- Do not mix 'require' and other declarations.
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/17c7d58475183211.
Report an issue: GitHub.