Path: blob/main/src/rules/group-exports.js
829 views
import docsUrl from '../docsUrl';1import values from 'object.values';2import flat from 'array.prototype.flat';34const meta = {5type: 'suggestion',6docs: {7url: docsUrl('group-exports'),8},9};10/* eslint-disable max-len */11const errors = {12ExportNamedDeclaration: 'Multiple named export declarations; consolidate all named exports into a single export declaration',13AssignmentExpression: 'Multiple CommonJS exports; consolidate all exports into a single assignment to `module.exports`',14};15/* eslint-enable max-len */1617/**18* Returns an array with names of the properties in the accessor chain for MemberExpression nodes19*20* Example:21*22* `module.exports = {}` => ['module', 'exports']23* `module.exports.property = true` => ['module', 'exports', 'property']24*25* @param {Node} node AST Node (MemberExpression)26* @return {Array} Array with the property names in the chain27* @private28*/29function accessorChain(node) {30const chain = [];3132do {33chain.unshift(node.property.name);3435if (node.object.type === 'Identifier') {36chain.unshift(node.object.name);37break;38}3940node = node.object;41} while (node.type === 'MemberExpression');4243return chain;44}4546function create(context) {47const nodes = {48modules: {49set: new Set(),50sources: {},51},52types: {53set: new Set(),54sources: {},55},56commonjs: {57set: new Set(),58},59};6061return {62ExportNamedDeclaration(node) {63const target = node.exportKind === 'type' ? nodes.types : nodes.modules;64if (!node.source) {65target.set.add(node);66} else if (Array.isArray(target.sources[node.source.value])) {67target.sources[node.source.value].push(node);68} else {69target.sources[node.source.value] = [node];70}71},7273AssignmentExpression(node) {74if (node.left.type !== 'MemberExpression') {75return;76}7778const chain = accessorChain(node.left);7980// Assignments to module.exports81// Deeper assignments are ignored since they just modify what's already being exported82// (ie. module.exports.exported.prop = true is ignored)83if (chain[0] === 'module' && chain[1] === 'exports' && chain.length <= 3) {84nodes.commonjs.set.add(node);85return;86}8788// Assignments to exports (exports.* = *)89if (chain[0] === 'exports' && chain.length === 2) {90nodes.commonjs.set.add(node);91return;92}93},9495'Program:exit': function onExit() {96// Report multiple `export` declarations (ES2015 modules)97if (nodes.modules.set.size > 1) {98nodes.modules.set.forEach(node => {99context.report({100node,101message: errors[node.type],102});103});104}105106// Report multiple `aggregated exports` from the same module (ES2015 modules)107flat(values(nodes.modules.sources)108.filter(nodesWithSource => Array.isArray(nodesWithSource) && nodesWithSource.length > 1))109.forEach((node) => {110context.report({111node,112message: errors[node.type],113});114});115116// Report multiple `export type` declarations (FLOW ES2015 modules)117if (nodes.types.set.size > 1) {118nodes.types.set.forEach(node => {119context.report({120node,121message: errors[node.type],122});123});124}125126// Report multiple `aggregated type exports` from the same module (FLOW ES2015 modules)127flat(values(nodes.types.sources)128.filter(nodesWithSource => Array.isArray(nodesWithSource) && nodesWithSource.length > 1))129.forEach((node) => {130context.report({131node,132message: errors[node.type],133});134});135136// Report multiple `module.exports` assignments (CommonJS)137if (nodes.commonjs.set.size > 1) {138nodes.commonjs.set.forEach(node => {139context.report({140node,141message: errors[node.type],142});143});144}145},146};147}148149module.exports = {150meta,151create,152};153154155