/** * ESLint rule to enforce single export per file * * Each file should export exactly one named export (or default export). * This ensures clear, focused modules. */ module.exports = { meta: { type: 'problem', docs: { description: 'Enforce single export per file', category: 'Best Practices', recommended: true, }, fixable: null, schema: [], messages: { multipleExports: 'File should have exactly one export. Found {{count}} exports: {{exports}}', noExports: 'File should have at least one export', }, }, create(context) { let exportCount = 0; const exportNames = []; return { // Track named exports ExportNamedDeclaration(node) { if (node.declaration) { // Exporting a class, function, const, etc. if (node.declaration.id) { exportCount++; exportNames.push(node.declaration.id.name); } else if (node.declaration.declarations) { // Multiple const exports node.declaration.declarations.forEach(decl => { if (decl.id && decl.id.name) { exportCount++; exportNames.push(decl.id.name); } }); } } else if (node.specifiers) { // Re-exports node.specifiers.forEach(spec => { exportCount++; exportNames.push(spec.exported.name); }); } }, // Track default exports ExportDefaultDeclaration(node) { exportCount++; if (node.declaration && node.declaration.id) { exportNames.push(`default (${node.declaration.id.name})`); } else { exportNames.push('default'); } }, 'Program:exit'() { if (exportCount === 0) { context.report({ node: context.getSourceCode().ast, messageId: 'noExports', }); } else if (exportCount > 1) { context.report({ node: context.getSourceCode().ast, messageId: 'multipleExports', data: { count: exportCount, exports: exportNames.join(', '), }, }); } }, }; }, };