4
4
*/
5
5
"use strict"
6
6
7
+ /**
8
+ * @typedef {import('estree').Node & { parent?: Node } } Node
9
+ */
10
+
7
11
/*istanbul ignore next */
8
12
/**
9
13
* This function is copied from https://github.com/eslint/eslint/blob/2355f8d0de1d6732605420d15ddd4f1eee3c37b6/lib/ast-utils.js#L648-L684
10
14
*
11
- * @param {import('eslint').Rule. Node } node - The node to get.
15
+ * @param {Node } node - The node to get.
12
16
* @returns {string | null | undefined } The property name if static. Otherwise, null.
13
17
* @private
14
18
*/
15
19
function getStaticPropertyName ( node ) {
20
+ /** @type {import('estree').Expression | import('estree').PrivateIdentifier | null } */
16
21
let prop = null
17
22
18
23
switch ( node ?. type ) {
19
24
case "Property" :
20
25
case "MethodDefinition" :
21
- prop = /** @type { import('estree').Property } */ ( node ) . key
26
+ prop = node . key
22
27
break
23
28
24
29
case "MemberExpression" :
25
- prop = /** @type {import('estree').MemberExpression } */ ( node )
26
- . property
30
+ prop = node . property
27
31
break
28
32
29
33
// no default
30
34
}
31
35
32
36
switch ( prop ?. type ) {
33
37
case "Literal" :
34
- return String ( /** @type { import('estree').Literal } */ ( prop ) . value )
38
+ return String ( prop . value )
35
39
36
40
case "TemplateLiteral" :
37
- if (
38
- /** @type {import('estree').TemplateLiteral } */ ( prop )
39
- . expressions . length === 0 &&
40
- /** @type {import('estree').TemplateLiteral } */ ( prop ) . quasis
41
- . length === 1
42
- ) {
43
- return /** @type {import('estree').TemplateLiteral } */ ( prop )
44
- . quasis [ 0 ] . value . cooked
41
+ if ( prop . expressions . length === 0 && prop . quasis . length === 1 ) {
42
+ return prop . quasis [ 0 ] . value . cooked
45
43
}
46
44
break
47
45
48
46
case "Identifier" :
49
- if ( ! node . computed ) {
47
+ if (
48
+ ! (
49
+ /** @type {import('estree').MemberExpression } */ ( node )
50
+ . computed
51
+ )
52
+ ) {
50
53
return prop . name
51
54
}
52
55
break
@@ -60,12 +63,13 @@ function getStaticPropertyName(node) {
60
63
/**
61
64
* Checks whether the given node is assignee or not.
62
65
*
63
- * @param {import('eslint').Rule. Node } node - The node to check.
66
+ * @param {Node } node - The node to check.
64
67
* @returns {boolean } `true` if the node is assignee.
65
68
*/
66
69
function isAssignee ( node ) {
67
70
return (
68
- node . parent . type === "AssignmentExpression" && node . parent . left === node
71
+ node . parent ?. type === "AssignmentExpression" &&
72
+ node . parent . left === node
69
73
)
70
74
}
71
75
@@ -75,15 +79,15 @@ function isAssignee(node) {
75
79
* This is used to distinguish 2 assignees belong to the same assignment.
76
80
* If the node is not an assignee, this returns null.
77
81
*
78
- * @param {import('eslint').Rule. Node } leafNode - The node to get.
79
- * @returns {import('eslint').Rule. Node|null } The top assignment expression node, or null.
82
+ * @param {Node } leafNode - The node to get.
83
+ * @returns {Node|null } The top assignment expression node, or null.
80
84
*/
81
85
function getTopAssignment ( leafNode ) {
82
86
let node = leafNode
83
87
84
88
// Skip MemberExpressions.
85
89
while (
86
- node . parent . type === "MemberExpression" &&
90
+ node . parent ? .type === "MemberExpression" &&
87
91
node . parent . object === node
88
92
) {
89
93
node = node . parent
@@ -95,7 +99,7 @@ function getTopAssignment(leafNode) {
95
99
}
96
100
97
101
// Find the top.
98
- while ( node . parent . type === "AssignmentExpression" ) {
102
+ while ( node . parent ? .type === "AssignmentExpression" ) {
99
103
node = node . parent
100
104
}
101
105
@@ -105,31 +109,33 @@ function getTopAssignment(leafNode) {
105
109
/**
106
110
* Gets top assignment nodes of the given node list.
107
111
*
108
- * @param {import('eslint').Rule. Node[] } nodes - The node list to get.
109
- * @returns {import('eslint').Rule. Node[] } Gotten top assignment nodes.
112
+ * @param {Node[] } nodes - The node list to get.
113
+ * @returns {Node[] } Gotten top assignment nodes.
110
114
*/
111
115
function createAssignmentList ( nodes ) {
112
- return /** @type {import('eslint').Rule.Node[] } */ (
113
- nodes . map ( getTopAssignment ) . filter ( Boolean )
114
- )
116
+ return /** @type {Node[] } */ ( nodes . map ( getTopAssignment ) . filter ( Boolean ) )
115
117
}
116
118
117
119
/**
118
120
* Gets the reference of `module.exports` from the given scope.
119
121
*
120
122
* @param {import('eslint').Scope.Scope } scope - The scope to get.
121
- * @returns {import('eslint').Rule. Node[] } Gotten MemberExpression node list.
123
+ * @returns {Node[] } Gotten MemberExpression node list.
122
124
*/
123
125
function getModuleExportsNodes ( scope ) {
124
126
const variable = scope . set . get ( "module" )
125
127
if ( variable == null ) {
126
128
return [ ]
127
129
}
128
130
return variable . references
129
- . map ( reference => reference . identifier . parent )
131
+ . map (
132
+ reference =>
133
+ /** @type {Node & { parent: Node } } */ ( reference . identifier )
134
+ . parent
135
+ )
130
136
. filter (
131
137
node =>
132
- node . type === "MemberExpression" &&
138
+ node ? .type === "MemberExpression" &&
133
139
getStaticPropertyName ( node ) === "exports"
134
140
)
135
141
}
@@ -149,6 +155,11 @@ function getExportsNodes(scope) {
149
155
return variable . references . map ( reference => reference . identifier )
150
156
}
151
157
158
+ /**
159
+ * @param {Node } property
160
+ * @param {import('eslint').SourceCode } sourceCode
161
+ * @returns {string | null }
162
+ */
152
163
function getReplacementForProperty ( property , sourceCode ) {
153
164
if ( property . type !== "Property" || property . kind !== "init" ) {
154
165
// We don't have a nice syntax for adding these directly on the exports object. Give up on fixing the whole thing:
@@ -162,7 +173,7 @@ function getReplacementForProperty(property, sourceCode) {
162
173
}
163
174
164
175
let fixedValue = sourceCode . getText ( property . value )
165
- if ( property . method ) {
176
+ if ( property . value . type === "FunctionExpression" && property . method ) {
166
177
fixedValue = `function${
167
178
property . value . generator ? "*" : ""
168
179
} ${ fixedValue } `
@@ -172,6 +183,7 @@ function getReplacementForProperty(property, sourceCode) {
172
183
}
173
184
const lines = sourceCode
174
185
. getCommentsBefore ( property )
186
+ // @ts -expect-error getText supports both BaseNode and BaseNodeWithoutComments
175
187
. map ( comment => sourceCode . getText ( comment ) )
176
188
if ( property . key . type === "Literal" || property . computed ) {
177
189
// String or dynamic key:
@@ -190,28 +202,43 @@ function getReplacementForProperty(property, sourceCode) {
190
202
lines . push (
191
203
...sourceCode
192
204
. getCommentsAfter ( property )
205
+ // @ts -expect-error getText supports both BaseNode and BaseNodeWithoutComments
193
206
. map ( comment => sourceCode . getText ( comment ) )
194
207
)
195
208
return lines . join ( "\n" )
196
209
}
197
210
198
- // Check for a top level module.exports = { ... }
211
+ /**
212
+ * Check for a top level module.exports = { ... }
213
+ * @param {Node } node
214
+ * @returns {node is {parent: import('estree').AssignmentExpression & {parent: import('estree').ExpressionStatement, right: import('estree').ObjectExpression}} }
215
+ */
199
216
function isModuleExportsObjectAssignment ( node ) {
200
217
return (
201
- node . parent . type === "AssignmentExpression" &&
202
- node . parent . parent . type === "ExpressionStatement" &&
203
- node . parent . parent . parent . type === "Program" &&
218
+ node . parent ? .type === "AssignmentExpression" &&
219
+ node . parent ? .parent ? .type === "ExpressionStatement" &&
220
+ node . parent . parent . parent ? .type === "Program" &&
204
221
node . parent . right . type === "ObjectExpression"
205
222
)
206
223
}
207
224
208
- // Check for module.exports.foo or module.exports.bar reference or assignment
225
+ /**
226
+ * Check for module.exports.foo or module.exports.bar reference or assignment
227
+ * @param {Node } node
228
+ * @returns {node is import('estree').MemberExpression }
229
+ */
209
230
function isModuleExportsReference ( node ) {
210
231
return (
211
- node . parent . type === "MemberExpression" && node . parent . object === node
232
+ node . parent ? .type === "MemberExpression" && node . parent . object === node
212
233
)
213
234
}
214
235
236
+ /**
237
+ * @param {Node } node
238
+ * @param {import('eslint').SourceCode } sourceCode
239
+ * @param {import('eslint').Rule.RuleFixer } fixer
240
+ * @returns {import('eslint').Rule.Fix | null }
241
+ */
215
242
function fixModuleExports ( node , sourceCode , fixer ) {
216
243
if ( isModuleExportsReference ( node ) ) {
217
244
return fixer . replaceText ( node , "exports" )
@@ -280,14 +307,16 @@ module.exports = {
280
307
* module.exports = foo
281
308
* ^^^^^^^^^^^^^^^^
282
309
*
283
- * @param {import('eslint').Rule. Node } node - The node of `exports`/`module.exports`.
284
- * @returns {Location } The location info of reports.
310
+ * @param {Node } node - The node of `exports`/`module.exports`.
311
+ * @returns {import('estree').SourceLocation } The location info of reports.
285
312
*/
286
313
function getLocation ( node ) {
287
314
const token = sourceCode . getTokenAfter ( node )
288
315
return {
289
- start : node . loc . start ,
290
- end : token . loc . end ,
316
+ start : /** @type {import('estree').SourceLocation } */ ( node . loc )
317
+ . start ,
318
+ end : /** @type {import('estree').SourceLocation } */ ( token ?. loc )
319
+ ?. end ,
291
320
}
292
321
}
293
322
@@ -306,9 +335,11 @@ module.exports = {
306
335
307
336
for ( const node of exportsNodes ) {
308
337
// Skip if it's a batch assignment.
338
+ const topAssignment = getTopAssignment ( node )
309
339
if (
340
+ topAssignment &&
310
341
assignList . length > 0 &&
311
- assignList . indexOf ( getTopAssignment ( node ) ) !== - 1
342
+ assignList . indexOf ( topAssignment ) !== - 1
312
343
) {
313
344
continue
314
345
}
@@ -340,7 +371,10 @@ module.exports = {
340
371
for ( const node of moduleExportsNodes ) {
341
372
// Skip if it's a batch assignment.
342
373
if ( assignList . length > 0 ) {
343
- const found = assignList . indexOf ( getTopAssignment ( node ) )
374
+ const topAssignment = getTopAssignment ( node )
375
+ const found = topAssignment
376
+ ? assignList . indexOf ( topAssignment )
377
+ : - 1
344
378
if ( found !== - 1 ) {
345
379
batchAssignList . push ( assignList [ found ] )
346
380
assignList . splice ( found , 1 )
@@ -366,8 +400,12 @@ module.exports = {
366
400
continue
367
401
}
368
402
403
+ const topAssignment = getTopAssignment ( node )
369
404
// Check if it's a batch assignment.
370
- if ( batchAssignList . indexOf ( getTopAssignment ( node ) ) !== - 1 ) {
405
+ if (
406
+ topAssignment &&
407
+ batchAssignList . indexOf ( topAssignment ) !== - 1
408
+ ) {
371
409
continue
372
410
}
373
411
0 commit comments