@@ -2,96 +2,107 @@ const meta = {}
2
2
/* eslint-disable max-len */
3
3
const errors = {
4
4
ExportNamedDeclaration : 'Multiple named export declarations; consolidate all named exports into a single export declaration' ,
5
- MemberExpression : 'Multiple CommonJS exports; consolidate all exports into a single assignment to `module.exports`' ,
5
+ AssignmentExpression : 'Multiple CommonJS exports; consolidate all exports into a single assignment to `module.exports`' ,
6
6
}
7
7
/* eslint-enable max-len */
8
- const parents = [
9
- 'AssignmentExpression' ,
10
- 'MemberExpression' ,
11
- ]
12
8
13
9
/**
14
- * Determine how many property accesses precede this node
10
+ * Returns an array with names of the properties in the accessor chain for MemberExpression nodes
15
11
*
16
- * For example, `module.exports` = 1, `module.exports.something` = 2 and so on.
12
+ * Example:
17
13
*
18
- * @param {Object } node The node being visited
19
- * @return {Number }
14
+ * `module.exports = {}` => ['module', 'exports']
15
+ * `module.exports.property = true` => ['module', 'exports', 'property']
16
+ *
17
+ * @param {Node } node AST Node (MemberExpression)
18
+ * @return {Array } Array with the property names in the chain
20
19
* @private
21
20
*/
22
- function accessorDepth ( node ) {
23
- let depth = 0
21
+ function accessorChain ( node ) {
22
+ const chain = [ ]
24
23
25
- while ( node . type === 'MemberExpression' ) {
26
- depth ++
27
- node = node . parent
28
- }
24
+ do {
25
+ chain . unshift ( node . property . name )
26
+
27
+ if ( node . object . type === 'Identifier' ) {
28
+ chain . unshift ( node . object . name )
29
+ break
30
+ }
29
31
30
- return depth
32
+ node = node . object
33
+ } while ( node . type === 'MemberExpression' )
34
+
35
+ return chain
31
36
}
32
37
33
38
function create ( context ) {
34
- const named = new Set ( )
35
- const commonjs = {
36
- defaultExportIsObject : false ,
39
+ const nodes = {
40
+ modules : new Set ( ) ,
41
+ commonjs : {
42
+ named : new Set ( ) ,
43
+ default : null ,
44
+ } ,
37
45
}
38
46
39
47
return {
40
48
ExportNamedDeclaration ( node ) {
41
- named . add ( node )
49
+ nodes . modules . add ( node )
42
50
} ,
43
51
44
- MemberExpression ( node ) {
45
- const parent = node . parent
46
-
47
- // These are not the parents you are looking for
48
- if ( parents . indexOf ( parent . type ) === - 1 ) {
52
+ AssignmentExpression ( node ) {
53
+ if ( node . left . type !== 'MemberExpression' ) {
49
54
return
50
55
}
51
56
52
- if ( parent . type === 'AssignmentExpression' ) {
53
- // Member expressions on the right side of the assignment do not interest us
54
- if ( parent . left !== node ) {
57
+ const chain = accessorChain ( node . left )
58
+
59
+ // Assignments to module.exports
60
+ if ( chain [ 0 ] === 'module' && chain [ 1 ] === 'exports' ) {
61
+ // Adding properties to module.exports (module.exports.* = *)
62
+ if ( chain . length === 3 ) {
63
+ nodes . commonjs . named . add ( node )
55
64
return
56
65
}
57
66
58
- // Special case: when assigning an object literal to `module.exports`, treat it as a named
59
- // export declaration
60
- if ( parent . right . type === 'ObjectExpression' ) {
61
- commonjs . defaultExportIsObject = true
62
- named . add ( node )
67
+ // Direct assignments to module.exports (module.exports = *)
68
+ if ( chain . length === 2 ) {
69
+ // Assigning an object literal is treated as a named export declaration
70
+ if ( node . right . type === 'ObjectExpression' ) {
71
+ nodes . commonjs . named . add ( node )
72
+ return
73
+ }
74
+
75
+ // Assigning anything else (string, function, bool) is treated as a default export
76
+ // declaration
77
+ nodes . commonjs . default = node
63
78
return
64
79
}
65
- }
66
80
67
- const depth = accessorDepth ( node )
68
-
69
- // Assignments to module.exports
70
- // Only treat assignments to `module.exports` object as named exports, ie.
71
- // module.exports.named = true
72
- // And ignore direct assignments and assignments more deep, ie.
73
- // module.exports = {}
74
- // module.exports.named.property = true
75
- if ( node . object . name === 'module' && node . property . name === 'exports' && depth === 2 ) {
76
- named . add ( node )
77
- return
81
+ // Deeper assignments are ignored since they just modify what's already being exported
78
82
}
79
83
80
- // Assignments to exports
81
- if ( node . object . name === 'exports' && depth === 1 ) {
82
- named . add ( node )
84
+ // Assignments to exports (exports.* = *)
85
+ if ( chain [ 0 ] === 'exports' && chain . length === 2 ) {
86
+ nodes . commonjs . named . add ( node )
83
87
return
84
88
}
85
89
} ,
86
90
87
91
'Program:exit' : function onExit ( ) {
88
- if ( named . size > 1 ) {
89
- for ( const node of named ) {
90
- // If the "default" CommonJS export was not an object literal, do not report anything
91
- if ( node . type !== 'ExportNamedDeclaration' && ! commonjs . defaultExportIsObject ) {
92
- continue
93
- }
92
+ // Report multiple `export` declarations (ES2015 modules)
93
+ if ( nodes . modules . size > 1 ) {
94
+ for ( const node of nodes . modules ) {
95
+ context . report ( {
96
+ node,
97
+ message : errors [ node . type ] ,
98
+ } )
99
+ }
100
+ }
94
101
102
+ // Report multiple `module.exports` assignments (CommonJS) unless the module contains a
103
+ // "default" export (ie. it exports something else than object literal - a string, fn, etc.)
104
+ if ( ! nodes . commonjs . default && nodes . commonjs . named . size > 1 ) {
105
+ for ( const node of nodes . commonjs . named ) {
95
106
context . report ( {
96
107
node,
97
108
message : errors [ node . type ] ,
0 commit comments